Browse Source

Merge pull request #801 from Budibase/dropdown-components

Dropdown components
master
Andrew Kingston 6 years ago
committed by GitHub
parent
commit
09b0f07d64
  1. 35
      packages/builder/cypress/integration/createAutomation.spec.js
  2. 96
      packages/builder/cypress/integration/createComponents.spec.js
  3. 21
      packages/builder/cypress/integration/createTable.spec.js
  4. 62
      packages/builder/cypress/integration/createView.spec.js
  5. 10
      packages/builder/cypress/support/commands.js
  6. 8
      packages/builder/src/budibase.css
  7. 6
      packages/builder/src/builderStore/fetchBindableProperties.js
  8. 4
      packages/builder/src/builderStore/getNewComponentName.js
  9. 2
      packages/builder/src/builderStore/store/index.js
  10. 85
      packages/builder/src/components/automation/AutomationBuilder/AutomationBuilder.svelte
  11. 136
      packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte
  12. 0
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/Arrow.svelte
  13. 0
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/AutomationBlockTagline.svelte
  14. 32
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
  15. 17
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
  16. 0
      packages/builder/src/components/automation/AutomationBuilder/flowchart/api.js
  17. 0
      packages/builder/src/components/automation/AutomationBuilder/flowchart/index.js
  18. 34
      packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte
  19. 79
      packages/builder/src/components/automation/AutomationPanel/AutomationList/AutomationList.svelte
  20. 68
      packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte
  21. 82
      packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte
  22. 48
      packages/builder/src/components/automation/AutomationPanel/BlockList/BlockList.svelte
  23. 0
      packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
  24. 88
      packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
  25. 3
      packages/builder/src/components/automation/AutomationPanel/index.js
  26. 82
      packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
  27. 8
      packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
  28. 167
      packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte
  29. 2
      packages/builder/src/components/automation/SetupPanel/TableSelector.svelte
  30. 1
      packages/builder/src/components/automation/SetupPanel/index.js
  31. 7
      packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte
  32. 2
      packages/builder/src/components/automation/Shared/WebhookDisplay.svelte
  33. 3
      packages/builder/src/components/automation/index.js
  34. 1
      packages/builder/src/components/backend/DataTable/DataTable.svelte
  35. 1
      packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
  36. 96
      packages/builder/src/components/backend/DataTable/Table.svelte
  37. 24
      packages/builder/src/components/backend/DataTable/buttons/DeleteRowsButton.svelte
  38. 2
      packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
  39. 5
      packages/builder/src/components/backend/DataTable/modals/DeleteRow.svelte
  40. 8
      packages/builder/src/components/backend/DataTable/modals/EditRow.svelte
  41. 3
      packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte
  42. 116
      packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte
  43. 46
      packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
  44. 97
      packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte
  45. 85
      packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte
  46. 70
      packages/builder/src/components/common/ActionButton.svelte
  47. 171
      packages/builder/src/components/common/Button.svelte
  48. 15
      packages/builder/src/components/common/ButtonGroup.svelte
  49. 13
      packages/builder/src/components/common/Dropdowns/DropdownContainer.svelte
  50. 70
      packages/builder/src/components/common/Dropdowns/DropdownItem.svelte
  51. 2
      packages/builder/src/components/common/Dropdowns/index.js
  52. 6
      packages/builder/src/components/common/Dropzone.svelte
  53. 109
      packages/builder/src/components/common/NavItem.svelte
  54. 36
      packages/builder/src/components/common/NumberBox.svelte
  55. 76
      packages/builder/src/components/common/Switcher.svelte
  56. 3
      packages/builder/src/components/common/core.js
  57. 13
      packages/builder/src/components/common/eventHandlers.js
  58. 24
      packages/builder/src/components/settings/Link.svelte
  59. 13
      packages/builder/src/components/settings/TabTitle.svelte
  60. 3
      packages/builder/src/components/start/CreateAppModal.svelte
  61. 2
      packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte
  62. 2
      packages/builder/src/components/userInterface/BindableInput.svelte
  63. 8
      packages/builder/src/components/userInterface/CategoryTab.svelte
  64. 149
      packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte
  65. 56
      packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
  66. 106
      packages/builder/src/components/userInterface/ComponentSelectionList.svelte
  67. 92
      packages/builder/src/components/userInterface/ComponentsHierarchy.svelte
  68. 83
      packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte
  69. 13
      packages/builder/src/components/userInterface/DesignView.svelte
  70. 8
      packages/builder/src/components/userInterface/DetailScreenSelect.svelte
  71. 11
      packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte
  72. 5
      packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte
  73. 41
      packages/builder/src/components/userInterface/Feedback/FeedbackNavLink.svelte
  74. 13
      packages/builder/src/components/userInterface/FlatButton.svelte
  75. 35
      packages/builder/src/components/userInterface/FrontendNavigatePane.svelte
  76. 582
      packages/builder/src/components/userInterface/IconSelect/IconSelect.svelte
  77. 50
      packages/builder/src/components/userInterface/ItemTab/Item.svelte
  78. 52
      packages/builder/src/components/userInterface/ItemTab/Tab.svelte
  79. 19
      packages/builder/src/components/userInterface/OptionSelect.svelte
  80. 65
      packages/builder/src/components/userInterface/PageLayout.svelte
  81. 13
      packages/builder/src/components/userInterface/PagesList.svelte
  82. 6
      packages/builder/src/components/userInterface/PropertyControl.svelte
  83. 32
      packages/builder/src/components/userInterface/PropertyGroup.svelte
  84. 2
      packages/builder/src/components/userInterface/PropertyPanelControls/Input.svelte
  85. 75
      packages/builder/src/components/userInterface/ScreenDropdownMenu.svelte
  86. 182
      packages/builder/src/components/userInterface/ScreenSelect.svelte
  87. 94
      packages/builder/src/components/userInterface/SettingsView.svelte
  88. 2
      packages/builder/src/components/userInterface/TableSelect.svelte
  89. 326
      packages/builder/src/components/userInterface/TableViewSelect.svelte
  90. 193
      packages/builder/src/components/userInterface/UserInterfaceRoot.svelte
  91. 2
      packages/builder/src/components/userInterface/pagesParsing/searchComponents.js
  92. 2
      packages/builder/src/components/userInterface/pagesParsing/splitRootComponentName.js
  93. 1833
      packages/builder/src/components/userInterface/temporaryPanelStructure.js
  94. 6
      packages/builder/src/helpers.js
  95. 2
      packages/builder/src/index.html
  96. 52
      packages/builder/src/pages/[application]/_reset.svelte
  97. 32
      packages/builder/src/pages/[application]/automate/_layout.svelte
  98. 2
      packages/builder/src/pages/[application]/automate/index.svelte
  99. 21
      packages/builder/src/pages/[application]/data/_layout.svelte
  100. 5
      packages/builder/src/pages/[application]/data/table/[selectedTable]/index.svelte

35
packages/builder/cypress/integration/createAutomation.spec.js

@ -14,35 +14,26 @@ context("Create a automation", () => {
cy.createTestTableWithData() cy.createTestTableWithData()
cy.contains("automate").click() cy.contains("automate").click()
cy.contains("Create New Automation").click() cy.get("[data-cy=new-automation]").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {
cy.get("input").type("Add Row") cy.get("input").type("Add Row")
cy.get(".buttons") cy.get(".buttons").contains("Create").click()
.contains("Create")
.click()
}) })
// Add trigger // Add trigger
cy.get("[data-cy=add-automation-component]").click() cy.contains("Trigger").click()
cy.get("[data-cy=ROW_SAVED]").click() cy.contains("Row Saved").click()
cy.get("[data-cy=automation-block-setup]").within(() => { cy.get(".setup").within(() => {
cy.get("select") cy.get("select").first().select("dog")
.first()
.select("dog")
}) })
// Create action // Create action
cy.get("[data-cy=CREATE_ROW]").click() cy.contains("Action").click()
cy.get("[data-cy=automation-block-setup]").within(() => { cy.contains("Create Row").click()
cy.get("select") cy.get(".setup").within(() => {
.first() cy.get("select").first().select("dog")
.select("dog") cy.get("input").first().type("goodboy")
cy.get("input") cy.get("input").eq(1).type("11")
.first()
.type("goodboy")
cy.get("input")
.eq(1)
.type("11")
}) })
// Save // Save
@ -50,7 +41,7 @@ context("Create a automation", () => {
// Activate Automation // Activate Automation
cy.get("[data-cy=activate-automation]").click() cy.get("[data-cy=activate-automation]").click()
cy.get(".stop-button.highlighted").should("be.visible") cy.get(".ri-stop-circle-fill.highlighted").should("be.visible")
}) })
it("should add row when a new row is added", () => { it("should add row when a new row is added", () => {

96
packages/builder/cypress/integration/createComponents.spec.js

@ -1,54 +1,60 @@
xcontext('Create Components', () => { xcontext("Create Components", () => {
before(() => {
cy.server()
cy.visit("localhost:4001/_builder")
// https://on.cypress.io/type
cy.createApp("Table App", "Table App Description")
cy.createTable("dog", "name", "age")
cy.addRow("bob", "15")
})
before(() => { // https://on.cypress.io/interacting-with-elements
cy.server() it("should add a container", () => {
cy.visit('localhost:4001/_builder') cy.navigateToFrontend()
// https://on.cypress.io/type cy.get(".switcher > :nth-child(2)").click()
cy.createApp('Table App', 'Table App Description') cy.contains("Container").click()
cy.createTable('dog', 'name', 'age') })
cy.addRow('bob', '15') it("should add a headline", () => {
}) cy.addHeadlineComponent("An Amazing headline!")
// https://on.cypress.io/interacting-with-elements getIframeBody().contains("An Amazing headline!")
it('should add a container', () => { })
cy.contains('frontend').click() it("change the font size of the headline", () => {
cy.get('.switcher > :nth-child(2)').click() cy.contains("Typography").click()
cy.get("[data-cy=font-size-prop-control]").click()
cy.contains("60px").click()
cy.contains("Design").click()
cy.contains('Container').click() getIframeBody()
}) .contains("An Amazing headline!")
it('should add a headline', () => { .should("have.css", "font-size", "60px")
cy.addHeadlineComponent('An Amazing headline!') })
getIframeBody().contains('An Amazing headline!')
})
it('change the font size of the headline', () => {
cy.contains('Typography').click()
cy.get('[data-cy=font-size-prop-control]').click()
cy.contains("60px").click()
cy.contains('Design').click()
getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px')
})
}) })
const getIframeDocument = () => { const getIframeDocument = () => {
return cy return (
.get('iframe') cy
// Cypress yields jQuery element, which has the real .get("iframe")
// DOM element under property "0". // Cypress yields jQuery element, which has the real
// From the real DOM iframe element we can get // DOM element under property "0".
// the "document" element, it is stored in "contentDocument" property // From the real DOM iframe element we can get
// Cypress "its" command can access deep properties using dot notation // the "document" element, it is stored in "contentDocument" property
// https://on.cypress.io/its // Cypress "its" command can access deep properties using dot notation
.its('0.contentDocument').should('exist') // https://on.cypress.io/its
.its("0.contentDocument")
.should("exist")
)
} }
const getIframeBody = () => { const getIframeBody = () => {
// get the document // get the document
return getIframeDocument() return (
// automatically retries until body is loaded getIframeDocument()
.its('body').should('not.be.undefined') // automatically retries until body is loaded
// wraps "body" DOM element to allow .its("body")
// chaining more Cypress commands, like ".find(...)" .should("not.be.undefined")
.then(cy.wrap) // wraps "body" DOM element to allow
} // chaining more Cypress commands, like ".find(...)"
.then(cy.wrap)
)
}

21
packages/builder/cypress/integration/createTable.spec.js

@ -8,7 +8,7 @@ context("Create a Table", () => {
cy.createTable("dog") cy.createTable("dog")
// Check if Table exists // Check if Table exists
cy.get(".title span").should("have.text", "dog") cy.get(".table-title h1").should("have.text", "dog")
}) })
it("adds a new column to the table", () => { it("adds a new column to the table", () => {
@ -26,9 +26,7 @@ context("Create a Table", () => {
.trigger("mouseover") .trigger("mouseover")
.find(".ri-pencil-line") .find(".ri-pencil-line")
.click({ force: true }) .click({ force: true })
cy.get(".actions input") cy.get(".actions input").first().type("updated")
.first()
.type("updated")
// Unset table display column // Unset table display column
cy.contains("display column").click() cy.contains("display column").click()
cy.contains("Save Column").click() cy.contains("Save Column").click()
@ -36,7 +34,8 @@ context("Create a Table", () => {
}) })
it("edits a row", () => { it("edits a row", () => {
cy.get("button").contains("Edit").click() cy.contains("button", "Edit").click({ force: true })
cy.wait(1000)
cy.get(".modal input").type("Updated") cy.get(".modal input").type("Updated")
cy.contains("Save").click() cy.contains("Save").click()
cy.contains("RoverUpdated").should("have.text", "RoverUpdated") cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
@ -50,10 +49,10 @@ context("Create a Table", () => {
}) })
it("deletes a column", () => { it("deletes a column", () => {
cy cy.contains("header", "name")
.contains("header", "name") .trigger("mouseover")
.trigger("mouseover") .find(".ri-pencil-line")
.find(".ri-pencil-line").click({ force: true }) .click({ force: true })
cy.contains("Delete").click() cy.contains("Delete").click()
cy.wait(50) cy.wait(50)
cy.get(".buttons").contains("Delete").click() cy.get(".buttons").contains("Delete").click()
@ -61,9 +60,7 @@ context("Create a Table", () => {
}) })
it("deletes a table", () => { it("deletes a table", () => {
cy.contains("div", "dog") cy.contains(".nav-item", "dog").get(".actions").invoke("show").click()
.get(".ri-more-line")
.click()
cy.get("[data-cy=delete-table]").click() cy.get("[data-cy=delete-table]").click()
cy.contains("Delete Table").click() cy.contains("Delete Table").click()
cy.contains("dog").should("not.exist") cy.contains("dog").should("not.exist")

62
packages/builder/cypress/integration/createView.spec.js

@ -22,10 +22,12 @@ context("Create a View", () => {
cy.get("input").type("Test View") cy.get("input").type("Test View")
cy.contains("Save View").click() cy.contains("Save View").click()
}) })
cy.get(".title").contains("Test View") cy.get(".table-title h1").contains("Test View")
cy.get("[data-cy=table-header]").then($headers => { cy.get("[data-cy=table-header]").then($headers => {
expect($headers).to.have.length(3) expect($headers).to.have.length(3)
const headers = Array.from($headers).map(header => header.textContent.trim()) const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
expect(headers).to.deep.eq(["group", "age", "rating"]) expect(headers).to.deep.eq(["group", "age", "rating"])
}) })
}) })
@ -33,14 +35,8 @@ context("Create a View", () => {
it("filters the view by age over 10", () => { it("filters the view by age over 10", () => {
cy.contains("Filter").click() cy.contains("Filter").click()
cy.contains("Add Filter").click() cy.contains("Add Filter").click()
cy.get(".menu-container") cy.get(".menu-container").find("select").first().select("age")
.find("select") cy.get(".menu-container").find("select").eq(1).select("More Than")
.first()
.select("age")
cy.get(".menu-container")
.find("select")
.eq(1)
.select("More Than")
cy.get(".menu-container").find("input").type(18) cy.get(".menu-container").find("input").type(18)
cy.contains("Save").click() cy.contains("Save").click()
cy.get("[role=rowgroup] .ag-row").get($values => { cy.get("[role=rowgroup] .ag-row").get($values => {
@ -53,20 +49,16 @@ context("Create a View", () => {
cy.viewport("macbook-15") cy.viewport("macbook-15")
cy.contains("Calculate").click() cy.contains("Calculate").click()
cy.get(".menu-container") cy.get(".menu-container").find("select").eq(0).select("Statistics")
.find("select")
.eq(0)
.select("Statistics")
cy.wait(50) cy.wait(50)
cy.get(".menu-container") cy.get(".menu-container").find("select").eq(1).select("age")
.find("select")
.eq(1)
.select("age")
cy.contains("Save").click() cy.contains("Save").click()
cy.get(".ag-center-cols-viewport").scrollTo("100%") cy.get(".ag-center-cols-viewport").scrollTo("100%")
cy.get("[data-cy=table-header]").then($headers => { cy.get("[data-cy=table-header]").then($headers => {
expect($headers).to.have.length(7) expect($headers).to.have.length(7)
const headers = Array.from($headers).map(header => header.textContent.trim()) const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
expect(headers).to.deep.eq([ expect(headers).to.deep.eq([
"field", "field",
"sum", "sum",
@ -78,16 +70,10 @@ context("Create a View", () => {
]) ])
}) })
cy.get(".ag-cell").then($values => { cy.get(".ag-cell").then($values => {
const values = Array.from($values).map(header => header.textContent.trim()) const values = Array.from($values).map(header =>
expect(values).to.deep.eq([ header.textContent.trim()
"age", )
"155", expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"])
"20",
"49",
"5",
"5347",
"31",
])
}) })
}) })
@ -102,8 +88,7 @@ context("Create a View", () => {
cy.contains("Students").should("be.visible") cy.contains("Students").should("be.visible")
cy.contains("Teachers").should("be.visible") cy.contains("Teachers").should("be.visible")
cy cy.get(".ag-row[row-index=0]")
.get(".ag-row[row-index=0]")
.find(".ag-cell") .find(".ag-cell")
.then($values => { .then($values => {
const values = Array.from($values).map(value => value.textContent) const values = Array.from($values).map(value => value.textContent)
@ -120,10 +105,11 @@ context("Create a View", () => {
}) })
it("renames a view", () => { it("renames a view", () => {
cy.contains("[data-cy=table-nav-item]", "Test View") cy.contains(".nav-item", "Test View")
.find(".ri-more-line") .find(".actions")
.invoke("show")
.click() .click()
cy.contains("Edit").click() cy.get("[data-cy=edit-view]").click()
cy.get(".menu-container").within(() => { cy.get(".menu-container").within(() => {
cy.get("input").type(" Updated") cy.get("input").type(" Updated")
cy.contains("Save").click() cy.contains("Save").click()
@ -132,11 +118,11 @@ context("Create a View", () => {
}) })
it("deletes a view", () => { it("deletes a view", () => {
cy.contains("[data-cy=table-nav-item]", "Test View Updated").click() cy.contains(".nav-item", "Test View Updated")
cy.contains("[data-cy=table-nav-item]", "Test View Updated") .find(".actions")
.find(".ri-more-line") .invoke("show")
.click() .click()
cy.contains("Delete").click() cy.get("[data-cy=delete-view]").click()
cy.contains("Delete View").click() cy.contains("Delete View").click()
cy.contains("TestView Updated").should("not.be.visible") cy.contains("TestView Updated").should("not.be.visible")
}) })

10
packages/builder/cypress/support/commands.js

@ -51,7 +51,7 @@ Cypress.Commands.add("createApp", name => {
.click() .click()
.type("test") .type("test")
cy.contains("Submit").click() cy.contains("Submit").click()
cy.contains("Create New Table", { cy.get("[data-cy=new-table]", {
timeout: 20000, timeout: 20000,
}).should("be.visible") }).should("be.visible")
}) })
@ -65,7 +65,7 @@ Cypress.Commands.add("createTestTableWithData", () => {
Cypress.Commands.add("createTable", tableName => { Cypress.Commands.add("createTable", tableName => {
// Enter table name // Enter table name
cy.contains("Create New Table").click() cy.get("[data-cy=new-table]").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {
cy.get("input") cy.get("input")
.first() .first()
@ -79,9 +79,7 @@ Cypress.Commands.add("createTable", tableName => {
Cypress.Commands.add("addColumn", (tableName, columnName, type) => { Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
// Select Table // Select Table
cy.get(".root") cy.contains(".nav-item", tableName).click()
.contains(tableName)
.click()
cy.contains("Create New Column").click() cy.contains("Create New Column").click()
// Configure column // Configure column
@ -155,7 +153,7 @@ Cypress.Commands.add("navigateToFrontend", () => {
}) })
Cypress.Commands.add("createScreen", (screenName, route) => { Cypress.Commands.add("createScreen", (screenName, route) => {
cy.contains("Create New Screen").click() cy.get("[data-cy=new-screen]").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {
cy.get("input") cy.get("input")
.eq(0) .eq(0)

8
packages/builder/src/budibase.css

@ -85,14 +85,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
.preview-pane {
grid-column: 2;
margin: 80px 60px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
}
.budibase__table { .budibase__table {
border: 1px solid var(--grey-4); border: 1px solid var(--grey-4);
background: #fff; background: #fff;

6
packages/builder/src/builderStore/fetchBindableProperties.js

@ -106,11 +106,15 @@ const contextToBindables = (tables, walkResult) => context => {
} }
} }
const stringType = { type: "string" }
return ( return (
Object.entries(schema) Object.entries(schema)
.map(newBindable) .map(newBindable)
// add _id and _rev fields - not part of schema, but always valid // add _id and _rev fields - not part of schema, but always valid
.concat([newBindable(["_id", "string"]), newBindable(["_rev", "string"])]) .concat([
newBindable(["_id", stringType]),
newBindable(["_rev", stringType]),
])
) )
} }

4
packages/builder/src/builderStore/getNewComponentName.js

@ -2,7 +2,9 @@ import { walkProps } from "./storeUtils"
import { get_capitalised_name } from "../helpers" import { get_capitalised_name } from "../helpers"
export default function(component, state) { export default function(component, state) {
const capitalised = get_capitalised_name(component) const capitalised = get_capitalised_name(
component.name || component._component
)
const matchingComponents = [] const matchingComponents = []

2
packages/builder/src/builderStore/store/index.js

@ -371,7 +371,7 @@ const addChildComponent = store => (componentToAdd, presetProps = {}) => {
const component = getComponentDefinition(state, componentToAdd) const component = getComponentDefinition(state, componentToAdd)
const instanceId = get(backendUiStore).selectedDatabase._id const instanceId = get(backendUiStore).selectedDatabase._id
const instanceName = getNewComponentName(componentToAdd, state) const instanceName = getNewComponentName(component, state)
const newComponent = createProps( const newComponent = createProps(
component, component,

85
packages/builder/src/components/automation/AutomationBuilder/AutomationBuilder.svelte

@ -1,12 +1,12 @@
<script> <script>
import { afterUpdate } from "svelte"
import { automationStore, backendUiStore } from "builderStore" import { automationStore, backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import Flowchart from "./FlowChart/FlowChart.svelte"
import Flowchart from "./flowchart/FlowChart.svelte" import BlockList from "./BlockList.svelte"
$: automation = $automationStore.selectedAutomation?.automation $: automation = $automationStore.selectedAutomation?.automation
$: automationLive = automation?.live $: automationLive = automation?.live
$: instanceId = $backendUiStore.selectedDatabase._id $: instanceId = $backendUiStore.selectedDatabase._id
$: automationCount = $automationStore.automations?.length ?? 0
function onSelect(block) { function onSelect(block) {
automationStore.update(state => { automationStore.update(state => {
@ -14,80 +14,19 @@
return state return state
}) })
} }
function setAutomationLive(live) {
automation.live = live
automationStore.actions.save({ instanceId, automation })
if (live) {
notifier.info(`Automation ${automation.name} enabled.`)
} else {
notifier.danger(`Automation ${automation.name} disabled.`)
}
}
</script> </script>
<section> {#if automation}
<BlockList />
<Flowchart {automation} {onSelect} /> <Flowchart {automation} {onSelect} />
</section> {:else if automationCount === 0}
<footer> <i>Create your first automation to get started</i>
{#if automation} {:else}<i>Select an automation to edit</i>{/if}
<button
class:highlighted={automationLive}
class:hoverable={automationLive}
class="stop-button hoverable">
<i class="ri-stop-fill" on:click={() => setAutomationLive(false)} />
</button>
<button
class:highlighted={!automationLive}
class:hoverable={!automationLive}
class="play-button hoverable"
data-cy="activate-automation"
on:click={() => setAutomationLive(true)}>
<i class="ri-play-fill" />
</button>
{/if}
</footer>
<style> <style>
section { i {
display: flex; font-size: var(--font-size-m);
flex-direction: column; color: var(--grey-5);
justify-content: flex-start; margin-top: 2px;
align-items: center;
overflow: auto;
height: 100%;
position: relative;
}
footer {
position: absolute;
bottom: var(--spacing-xl);
right: 30px;
display: flex;
align-items: flex-end;
}
footer > button {
border-radius: 100%;
color: var(--white);
width: 76px;
height: 76px;
border: none;
background: #adaec4;
font-size: 45px;
display: flex;
align-items: center;
justify-content: center;
}
footer > button:first-child {
margin-right: var(--spacing-m);
}
.play-button.highlighted {
background: var(--purple);
}
.stop-button.highlighted {
background: var(--red);
} }
</style> </style>

136
packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte

@ -0,0 +1,136 @@
<script>
import { sortBy } from "lodash/fp"
import { automationStore } from "builderStore"
import { DropdownMenu, Modal } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
import analytics from "analytics"
import CreateWebhookModal from "../Shared/CreateWebhookModal.svelte"
$: hasTrigger = $automationStore.selectedAutomation.hasTrigger()
$: tabs = [
{
label: "Trigger",
value: "TRIGGER",
icon: "ri-organization-chart",
disabled: hasTrigger,
},
{
label: "Action",
value: "ACTION",
icon: "ri-flow-chart",
disabled: !hasTrigger,
},
{
label: "Logic",
value: "LOGIC",
icon: "ri-filter-line",
disabled: !hasTrigger,
},
]
let selectedIndex
let anchors = []
let popover
let webhookModal
$: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex]
$: blocks = sortBy(entry => entry[1].name)(
Object.entries($automationStore.blockDefinitions[selectedTab] ?? {})
)
function onChangeTab(idx) {
selectedIndex = idx
popover.show()
}
function closePopover() {
selectedIndex = null
popover.hide()
}
function addBlockToAutomation(stepId, blockDefinition) {
const newBlock = {
...blockDefinition,
inputs: blockDefinition.inputs || {},
stepId,
type: selectedTab,
}
automationStore.actions.addBlockToAutomation(newBlock)
analytics.captureEvent("Added Automation Block", {
name: blockDefinition.name,
})
closePopover()
if (stepId === "WEBHOOK") {
webhookModal.show()
}
}
</script>
<div class="tab-container">
{#each tabs as tab, idx}
<div
bind:this={anchors[idx]}
class="tab"
class:disabled={tab.disabled}
on:click={tab.disabled ? null : () => onChangeTab(idx)}
class:active={idx === selectedIndex}>
{#if tab.icon}<i class={tab.icon} />{/if}
<span>{tab.label}</span>
<i class="ri-arrow-down-s-line arrow" />
</div>
{/each}
</div>
<DropdownMenu
on:close={() => (selectedIndex = null)}
bind:this={popover}
{anchor}
align="left">
<DropdownContainer>
{#each blocks as [stepId, blockDefinition]}
<DropdownItem
icon={blockDefinition.icon}
title={blockDefinition.name}
subtitle={blockDefinition.description}
on:click={() => addBlockToAutomation(stepId, blockDefinition)} />
{/each}
</DropdownContainer>
</DropdownMenu>
<Modal bind:this={webhookModal} width="30%">
<CreateWebhookModal />
</Modal>
<style>
.tab-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-l);
min-height: 24px;
}
.tab {
color: var(--grey-7);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--font-size-xs);
}
.tab span {
font-weight: 500;
user-select: none;
}
.tab.active,
.tab:not(.disabled):hover {
color: var(--ink);
cursor: pointer;
}
.tab.disabled {
color: var(--grey-5);
}
.tab i:not(:last-child) {
font-size: 16px;
}
</style>

0
packages/builder/src/components/automation/AutomationBuilder/flowchart/Arrow.svelte → packages/builder/src/components/automation/AutomationBuilder/FlowChart/Arrow.svelte

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

0
packages/builder/src/components/automation/AutomationBuilder/flowchart/AutomationBlockTagline.svelte → packages/builder/src/components/automation/AutomationBuilder/FlowChart/AutomationBlockTagline.svelte

32
packages/builder/src/components/automation/AutomationBuilder/flowchart/FlowChart.svelte → packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte

@ -3,7 +3,6 @@
import Arrow from "./Arrow.svelte" import Arrow from "./Arrow.svelte"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fade, fly } from "svelte/transition" import { fade, fly } from "svelte/transition"
import { automationStore } from "builderStore"
export let automation export let automation
export let onSelect export let onSelect
@ -18,16 +17,9 @@
blocks = blocks.concat(automation.definition.steps || []) blocks = blocks.concat(automation.definition.steps || [])
} }
} }
$: automationCount = $automationStore.automations?.length ?? 0
</script> </script>
{#if automationCount === 0} {#if !blocks.length}<i>Add a trigger to your automation to get started</i>{/if}
<i>Create your first automation to get started</i>
{:else if automation == null}
<i>Select an automation to edit</i>
{:else if !blocks.length}
<i>Add some steps to your automation to get started</i>
{/if}
<section class="canvas"> <section class="canvas">
{#each blocks as block, idx (block.id)} {#each blocks as block, idx (block.id)}
<div <div
@ -44,19 +36,18 @@
</section> </section>
<style> <style>
i {
font-size: var(--font-size-xl);
color: var(--grey-4);
padding: var(--spacing-xl) 40px;
align-self: flex-start;
}
section { section {
position: absolute; margin: 0 -40px calc(-1 * var(--spacing-l)) -40px;
padding: 40px; padding: var(--spacing-l) 40px 0 40px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
overflow-y: auto;
flex: 1 1 auto;
}
/* Fix for firefox not respecting bottom padding in scrolling containers */
section > *:last-child {
padding-bottom: 40px;
} }
.block { .block {
@ -65,4 +56,9 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
} }
i {
font-size: var(--font-size-m);
color: var(--grey-5);
}
</style> </style>

17
packages/builder/src/components/automation/AutomationBuilder/flowchart/FlowItem.svelte → packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte

@ -10,6 +10,11 @@
$: steps = $: steps =
$automationStore.selectedAutomation?.automation?.definition?.steps ?? [] $automationStore.selectedAutomation?.automation?.definition?.steps ?? []
$: blockIdx = steps.findIndex(step => step.id === block.id) $: blockIdx = steps.findIndex(step => step.id === block.id)
$: allowDeleteTrigger = !steps.length
function deleteStep() {
automationStore.actions.deleteAutomationBlock(block)
}
</script> </script>
<div <div
@ -30,6 +35,9 @@
<div class="label"> <div class="label">
{#if block.type === 'TRIGGER'}Trigger{:else}Step {blockIdx + 1}{/if} {#if block.type === 'TRIGGER'}Trigger{:else}Step {blockIdx + 1}{/if}
</div> </div>
{#if block.type !== 'TRIGGER' || allowDeleteTrigger}
<i on:click|stopPropagation={deleteStep} class="delete ri-close-line" />
{/if}
</header> </header>
<hr /> <hr />
<p> <p>
@ -61,6 +69,7 @@
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
gap: var(--spacing-xs);
} }
header span { header span {
flex: 1 1 auto; flex: 1 1 auto;
@ -74,7 +83,13 @@
} }
header i { header i {
font-size: 20px; font-size: 20px;
margin-right: 5px; }
header i.delete {
opacity: 0.5;
}
header i.delete:hover {
cursor: pointer;
opacity: 1;
} }
.ACTION { .ACTION {

0
packages/builder/src/components/automation/AutomationBuilder/flowchart/api.js

0
packages/builder/src/components/automation/AutomationBuilder/flowchart/index.js

34
packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte

@ -0,0 +1,34 @@
<script>
import { onMount } from "svelte"
import { automationStore } from "builderStore"
import NavItem from "components/common/NavItem.svelte"
import EditAutomationPopover from "./EditAutomationPopover.svelte"
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
onMount(() => {
automationStore.actions.fetch()
})
</script>
<div class="automations-list">
{#each $automationStore.automations as automation, idx}
<NavItem
border={idx > 0}
icon="ri-stackshare-line"
text={automation.name}
selected={automation._id === selectedAutomationId}
on:click={() => automationStore.actions.select(automation)}>
<EditAutomationPopover {automation} />
</NavItem>
{/each}
</div>
<style>
.automations-list {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
</style>

79
packages/builder/src/components/automation/AutomationPanel/AutomationList/AutomationList.svelte

@ -1,79 +0,0 @@
<script>
import { onMount } from "svelte"
import { automationStore } from "builderStore"
import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Button, Modal } from "@budibase/bbui"
let modal
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
onMount(() => {
automationStore.actions.fetch()
})
</script>
<section>
<Button primary wide on:click={modal.show}>Create New Automation</Button>
<ul>
{#each $automationStore.automations as automation}
<li
class="automation-item"
class:selected={automation._id === selectedAutomationId}
on:click={() => automationStore.actions.select(automation)}>
<i class="ri-stackshare-line" class:live={automation.live} />
{automation.name}
</li>
{/each}
</ul>
</section>
<Modal bind:this={modal}>
<CreateAutomationModal />
</Modal>
<style>
section {
display: flex;
flex-direction: column;
}
ul {
list-style-type: none;
padding: 0;
margin: var(--spacing-xl) 0 0 0;
flex: 1;
}
i {
color: var(--grey-6);
}
i.live {
color: var(--ink);
}
li {
font-size: 14px;
}
.automation-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
border-radius: var(--border-radius-m);
padding: var(--spacing-s) var(--spacing-m);
margin-bottom: var(--spacing-xs);
color: var(--ink);
}
.automation-item i {
font-size: 24px;
margin-right: var(--spacing-m);
}
.automation-item:hover {
cursor: pointer;
background: var(--grey-1);
}
.automation-item.selected {
background: var(--grey-2);
}
</style>

68
packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte

@ -1,50 +1,44 @@
<script> <script>
import { automationStore } from "builderStore" import AutomationList from "./AutomationList.svelte"
import AutomationList from "./AutomationList/AutomationList.svelte" import CreateAutomationModal from "./CreateAutomationModal.svelte"
import BlockList from "./BlockList/BlockList.svelte" import { Modal } from "@budibase/bbui"
import { Heading } from "@budibase/bbui" import { automationStore, backendUiStore } from "builderStore"
import { Spacer } from "@budibase/bbui" import { notifier } from "builderStore/store/notifications"
let selectedTab = "AUTOMATIONS" let selectedTab = "AUTOMATIONS"
let modal
</script> </script>
<Heading black small> <div class="title">
<span <h1>Automations</h1>
data-cy="automation-list" <i
class="hoverable automation-header" on:click={modal.show}
class:selected={selectedTab === 'AUTOMATIONS'} data-cy="new-automation"
on:click={() => (selectedTab = 'AUTOMATIONS')}> class="ri-add-circle-fill" />
Automations </div>
</span> <AutomationList />
{#if $automationStore.selectedAutomation} <Modal bind:this={modal}>
<span <CreateAutomationModal />
data-cy="add-automation-component" </Modal>
class="hoverable"
class:selected={selectedTab === 'ADD'}
on:click={() => (selectedTab = 'ADD')}>
Steps
</span>
{/if}
</Heading>
<Spacer medium />
{#if selectedTab === 'AUTOMATIONS'}
<AutomationList />
{:else if selectedTab === 'ADD'}
<BlockList />
{/if}
<style> <style>
header { .title {
font-size: 18px;
font-weight: 600;
background: none;
display: flex; display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: var(--spacing-xl);
} }
.title h1 {
.automation-header { font-size: var(--font-size-m);
margin-right: var(--spacing-xl); font-weight: 500;
margin: 0;
}
.title i {
font-size: 20px;
}
.title i:hover {
cursor: pointer;
color: var(--blue);
} }
span:not(.selected) { span:not(.selected) {

82
packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte

@ -1,82 +0,0 @@
<script>
import { backendUiStore, automationStore } from "builderStore"
import CreateWebookModal from "../../Shared/CreateWebhookModal.svelte"
import analytics from "analytics"
import { Modal } from "@budibase/bbui"
export let blockDefinition
export let stepId
export let blockType
let modal
$: instanceId = $backendUiStore.selectedDatabase._id
$: automation = $automationStore.selectedAutomation?.automation
function addBlockToAutomation() {
automationStore.actions.addBlockToAutomation({
...blockDefinition,
inputs: blockDefinition.inputs || {},
stepId,
type: blockType,
})
if (stepId === "WEBHOOK") {
modal.show()
}
analytics.captureEvent("Added Automation Block", {
name: blockDefinition.name,
})
}
</script>
<div
class="automation-block hoverable"
on:click={addBlockToAutomation}
data-cy={stepId}>
<div><i class={blockDefinition.icon} /></div>
<div class="automation-text">
<h4>{blockDefinition.name}</h4>
<p>{blockDefinition.description}</p>
</div>
</div>
<Modal bind:this={modal} width="30%">
<CreateWebookModal />
</Modal>
<style>
.automation-block {
display: grid;
grid-template-columns: 20px auto;
align-items: center;
margin-top: var(--spacing-s);
padding: var(--spacing-m);
border-radius: var(--border-radius-m);
}
.automation-block:hover {
background-color: var(--grey-1);
}
.automation-block:first-child {
margin-top: 0;
}
i {
color: var(--grey-7);
font-size: 20px;
}
.automation-text {
margin-left: 16px;
}
.automation-text h4 {
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
margin-top: 0;
}
.automation-text p {
font-size: 12px;
line-height: 1.4;
color: var(--grey-7);
margin: 0;
}
</style>

48
packages/builder/src/components/automation/AutomationPanel/BlockList/BlockList.svelte

@ -1,48 +0,0 @@
<script>
import { sortBy } from "lodash/fp"
import { automationStore } from "builderStore"
import AutomationBlock from "./AutomationBlock.svelte"
import FlatButtonGroup from "components/userInterface/FlatButtonGroup.svelte"
let selectedTab = "TRIGGER"
let buttonProps = []
$: blocks = sortBy(entry => entry[1].name)(
Object.entries($automationStore.blockDefinitions[selectedTab])
)
$: {
if ($automationStore.selectedAutomation.hasTrigger()) {
buttonProps = [
{ value: "ACTION", text: "Action" },
{ value: "LOGIC", text: "Logic" },
]
if (selectedTab === "TRIGGER") {
selectedTab = "ACTION"
}
} else {
buttonProps = [{ value: "TRIGGER", text: "Trigger" }]
if (selectedTab !== "TRIGGER") {
selectedTab = "TRIGGER"
}
}
}
function onChangeTab(tab) {
selectedTab = tab
}
</script>
<section>
<FlatButtonGroup value={selectedTab} {buttonProps} onChange={onChangeTab} />
<div id="blocklist">
{#each blocks as [stepId, blockDefinition]}
<AutomationBlock {blockDefinition} {stepId} blockType={selectedTab} />
{/each}
</div>
</section>
<style>
#blocklist {
margin-top: var(--spacing-xl);
}
</style>

0
packages/builder/src/components/automation/AutomationPanel/AutomationList/CreateAutomationModal.svelte → packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte

88
packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte

@ -0,0 +1,88 @@
<script>
import { automationStore, backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
export let automation
let anchor
let dropdown
let confirmDeleteDialog
$: instanceId = $backendUiStore.selectedDatabase._id
function showModal() {
dropdown.hide()
confirmDeleteDialog.show()
}
async function deleteAutomation() {
await automationStore.actions.delete({
instanceId,
automation,
})
notifier.success("Automation deleted.")
}
</script>
<div on:click|stopPropagation>
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
<i class="ri-more-line" />
</div>
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
<DropdownContainer>
<DropdownItem
icon="ri-delete-bin-line"
title="Delete"
on:click={showModal} />
</DropdownContainer>
</DropdownMenu>
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
okText="Delete Automation"
onOk={deleteAutomation}
title="Confirm Deletion">
Are you sure you wish to delete the automation
<i>{automation.name}?</i>
This action cannot be undone.
</ConfirmDialog>
<style>
div.icon {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
div.icon i {
font-size: 16px;
}
ul {
list-style: none;
margin: 0;
padding: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0;
align-items: center;
cursor: pointer;
}
li:hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
</style>

3
packages/builder/src/components/automation/AutomationPanel/index.js

@ -1,3 +0,0 @@
export { default as AutomationPanel } from "./AutomationPanel.svelte"
export { default as BlockList } from "./BlockList/BlockList.svelte"
export { default as AutomationList } from "./AutomationList/AutomationList.svelte"

82
packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte

@ -1,10 +1,10 @@
<script> <script>
import TableSelector from "./ParamInputs/TableSelector.svelte" import TableSelector from "./TableSelector.svelte"
import RowSelector from "./ParamInputs/RowSelector.svelte" import RowSelector from "./RowSelector.svelte"
import { Button, Input, TextArea, Select, Label } from "@budibase/bbui" import { Button, Input, Select, Label } from "@budibase/bbui"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte" import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
import BindableInput from "../../userInterface/BindableInput.svelte" import BindableInput from "components/userInterface/BindableInput.svelte"
export let block export let block
export let webhookModal export let webhookModal
@ -47,43 +47,41 @@
} }
</script> </script>
<div class="container" data-cy="automation-block-setup"> <div class="block-label">{block.name}</div>
<div class="block-label">{block.name}</div> {#each inputs as [key, value]}
{#each inputs as [key, value]} <div class="block-field">
<div class="bb-margin-xl block-field"> <Label extraSmall grey>{value.title}</Label>
<Label extraSmall grey>{value.title}</Label> {#if value.type === 'string' && value.enum}
{#if value.type === 'string' && value.enum} <Select bind:value={block.inputs[key]} extraThin secondary>
<Select bind:value={block.inputs[key]} thin secondary> <option value="">Choose an option</option>
<option value="">Choose an option</option> {#each value.enum as option, idx}
{#each value.enum as option, idx} <option value={option}>
<option value={option}> {value.pretty ? value.pretty[idx] : option}
{value.pretty ? value.pretty[idx] : option} </option>
</option> {/each}
{/each} </Select>
</Select> {:else if value.customType === 'password'}
{:else if value.customType === 'password'} <Input type="password" extraThin bind:value={block.inputs[key]} />
<Input type="password" thin bind:value={block.inputs[key]} /> {:else if value.customType === 'table'}
{:else if value.customType === 'table'} <TableSelector bind:value={block.inputs[key]} />
<TableSelector bind:value={block.inputs[key]} /> {:else if value.customType === 'row'}
{:else if value.customType === 'row'} <RowSelector bind:value={block.inputs[key]} {bindings} />
<RowSelector bind:value={block.inputs[key]} {bindings} /> {:else if value.customType === 'webhookUrl'}
{:else if value.customType === 'webhookUrl'} <WebhookDisplay value={block.inputs[key]} />
<WebhookDisplay value={block.inputs[key]} /> {:else if value.type === 'string' || value.type === 'number'}
{:else if value.type === 'string' || value.type === 'number'} <BindableInput
<BindableInput type="string"
type="string" extraThin
thin bind:value={block.inputs[key]}
bind:value={block.inputs[key]} {bindings} />
{bindings} /> {/if}
{/if} </div>
</div> {/each}
{/each} {#if stepId === 'WEBHOOK'}
{#if stepId === 'WEBHOOK'} <Button wide secondary on:click={() => webhookModal.show()}>
<Button wide secondary on:click={() => webhookModal.show()}> Set Up Webhook
Setup webhook </Button>
</Button> {/if}
{/if}
</div>
<style> <style>
.block-field { .block-field {
@ -92,7 +90,7 @@
.block-label { .block-label {
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: var(--font-size-xs);
color: var(--grey-7); color: var(--grey-7);
} }

8
packages/builder/src/components/automation/SetupPanel/ParamInputs/RowSelector.svelte → packages/builder/src/components/automation/SetupPanel/RowSelector.svelte

@ -1,7 +1,7 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { Input, Select, Label } from "@budibase/bbui" import { Input, Select, Label } from "@budibase/bbui"
import BindableInput from "../../../userInterface/BindableInput.svelte" import BindableInput from "../../userInterface/BindableInput.svelte"
export let value export let value
export let bindings export let bindings
@ -19,7 +19,7 @@
</script> </script>
<div class="block-field"> <div class="block-field">
<Select bind:value={value.tableId} thin secondary> <Select bind:value={value.tableId} extraThin secondary>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each $backendUiStore.tables as table} {#each $backendUiStore.tables as table}
<option value={table._id}>{table.name}</option> <option value={table._id}>{table.name}</option>
@ -31,7 +31,7 @@
<div class="schema-fields"> <div class="schema-fields">
{#each schemaFields as [field, schema]} {#each schemaFields as [field, schema]}
{#if schemaHasOptions(schema)} {#if schemaHasOptions(schema)}
<Select label={field} thin secondary bind:value={value[field]}> <Select label={field} extraThin secondary bind:value={value[field]}>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each schema.constraints.inclusion as option} {#each schema.constraints.inclusion as option}
<option value={option}>{option}</option> <option value={option}>{option}</option>
@ -39,7 +39,7 @@
</Select> </Select>
{:else if schema.type === 'string' || schema.type === 'number'} {:else if schema.type === 'string' || schema.type === 'number'}
<BindableInput <BindableInput
thin extraThin
bind:value={value[field]} bind:value={value[field]}
label={field} label={field}
type="string" type="string"

167
packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte

@ -2,25 +2,27 @@
import { backendUiStore, automationStore } from "builderStore" import { backendUiStore, automationStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import AutomationBlockSetup from "./AutomationBlockSetup.svelte" import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
import { Button, Input, Label, Modal } from "@budibase/bbui" import { Button, Modal } from "@budibase/bbui"
import CreateWebookModal from "../Shared/CreateWebhookModal.svelte" import CreateWebookModal from "../Shared/CreateWebhookModal.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
let selectedTab = "SETUP"
let confirmDeleteDialog
let webhookModal let webhookModal
$: instanceId = $backendUiStore.selectedDatabase._id $: instanceId = $backendUiStore.selectedDatabase._id
$: automation = $automationStore.selectedAutomation?.automation $: automation = $automationStore.selectedAutomation?.automation
$: allowDeleteBlock =
$automationStore.selectedBlock?.type !== "TRIGGER" ||
!automation?.definition?.steps?.length
$: name = automation?.name ?? "" $: name = automation?.name ?? ""
$: automationLive = automation?.live
function deleteAutomationBlock() { function setAutomationLive(live) {
automationStore.actions.deleteAutomationBlock( if (automation.live === live) {
$automationStore.selectedBlock return
) }
automation.live = live
automationStore.actions.save({ instanceId, automation })
if (live) {
notifier.info(`Automation ${automation.name} enabled.`)
} else {
notifier.danger(`Automation ${automation.name} disabled.`)
}
} }
async function testAutomation() { async function testAutomation() {
@ -41,115 +43,78 @@
}) })
notifier.success(`Automation ${automation.name} saved.`) notifier.success(`Automation ${automation.name} saved.`)
} }
async function deleteAutomation() {
await automationStore.actions.delete({
instanceId,
automation,
})
notifier.success("Automation deleted.")
}
</script> </script>
<section> <div class="title">
<header> <h1>Setup</h1>
<span <i
class="hoverable" class:highlighted={automationLive}
class:selected={selectedTab === 'SETUP'} class:hoverable={automationLive}
on:click={() => (selectedTab = 'SETUP')}> on:click={() => setAutomationLive(false)}
Setup class="ri-stop-circle-fill" />
</span> <i
</header> class:highlighted={!automationLive}
<div class="content"> class:hoverable={!automationLive}
{#if $automationStore.selectedBlock} data-cy="activate-automation"
<AutomationBlockSetup on:click={() => setAutomationLive(true)}
bind:block={$automationStore.selectedBlock} class="ri-play-circle-fill" />
{webhookModal} /> </div>
{:else if $automationStore.selectedAutomation} {#if $automationStore.selectedBlock}
<div class="block-label">Automation <b>{automation.name}</b></div> <AutomationBlockSetup
<Button secondary wide on:click={testAutomation}>Test Automation</Button> bind:block={$automationStore.selectedBlock}
{/if} {webhookModal} />
</div> {:else if $automationStore.selectedAutomation}
<div class="buttons"> <div class="block-label">{automation.name}</div>
{#if $automationStore.selectedBlock} <Button secondary wide on:click={testAutomation}>Test Automation</Button>
<Button {/if}
green <Button
wide secondary
data-cy="save-automation-setup" wide
on:click={saveAutomation}> data-cy="save-automation-setup"
Save Automation on:click={saveAutomation}>
</Button> Save Automation
<Button </Button>
disabled={!allowDeleteBlock}
red
wide
on:click={deleteAutomationBlock}>
Delete Step
</Button>
{:else if $automationStore.selectedAutomation}
<Button
green
wide
data-cy="save-automation-setup"
on:click={saveAutomation}>
Save Automation
</Button>
<Button red wide on:click={() => confirmDeleteDialog.show()}>
Delete Automation
</Button>
{/if}
</div>
</section>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete the automation '${name}'?`}
okText="Delete Automation"
onOk={deleteAutomation} />
<Modal bind:this={webhookModal} width="30%"> <Modal bind:this={webhookModal} width="30%">
<CreateWebookModal /> <CreateWebookModal />
</Modal> </Modal>
<style> <style>
section { .title {
display: flex;
flex-direction: column;
height: 100%;
justify-content: flex-start;
align-items: stretch;
}
header {
font-size: 18px;
font-weight: 600;
font-family: inter, sans-serif;
display: flex; display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: var(--spacing-xl); gap: var(--spacing-xs);
color: var(--ink); }
.title h1 {
font-size: var(--font-size-m);
font-weight: 500;
margin: 0;
flex: 1 1 auto;
} }
header > span { .title i {
font-size: 20px;
color: var(--grey-5); color: var(--grey-5);
margin-right: var(--spacing-xl);
cursor: pointer;
} }
.selected { .title i.highlighted {
color: var(--ink); color: var(--ink);
} }
.title i.hoverable:hover {
cursor: pointer;
color: var(--blue);
}
.block-label { .block-label {
font-size: var(--font-size-xs);
font-weight: 500; font-weight: 500;
font-size: 14px;
color: var(--grey-7); color: var(--grey-7);
margin-bottom: var(--spacing-xl);
}
.content {
flex: 1 0 auto;
} }
.buttons { .footer {
display: grid; flex: 1 1 auto;
gap: var(--spacing-m); display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
} }
</style> </style>

2
packages/builder/src/components/automation/SetupPanel/ParamInputs/TableSelector.svelte → packages/builder/src/components/automation/SetupPanel/TableSelector.svelte

@ -6,7 +6,7 @@
</script> </script>
<div class="block-field"> <div class="block-field">
<Select bind:value secondary thin> <Select bind:value secondary extraThin>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each $backendUiStore.tables as table} {#each $backendUiStore.tables as table}
<option value={table._id}>{table.name}</option> <option value={table._id}>{table.name}</option>

1
packages/builder/src/components/automation/SetupPanel/index.js

@ -1 +0,0 @@
export { default as SetupPanel } from "./SetupPanel.svelte"

7
packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte

@ -3,12 +3,8 @@
import WebhookDisplay from "./WebhookDisplay.svelte" import WebhookDisplay from "./WebhookDisplay.svelte"
import { ModalContent } from "@budibase/bbui" import { ModalContent } from "@budibase/bbui"
import { onMount, onDestroy } from "svelte" import { onMount, onDestroy } from "svelte"
import { cloneDeep } from "lodash/fp"
import analytics from "analytics"
const POLL_RATE_MS = 2500 const POLL_RATE_MS = 2500
const DEFAULT_SCHEMA_OUTPUT = "Any input allowed"
let name
let interval let interval
let finished = false let finished = false
let schemaURL let schemaURL
@ -88,8 +84,7 @@
text-decoration: none; text-decoration: none;
} }
p { p {
margin-top: 0; margin: 0;
padding-top: 0;
text-align: justify; text-align: justify;
} }
.finished-text { .finished-text {

2
packages/builder/src/components/automation/Shared/WebhookDisplay.svelte

@ -1,7 +1,7 @@
<script> <script>
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { Input } from "@budibase/bbui" import { Input } from "@budibase/bbui"
import { store } from "../../../builderStore" import { store } from "builderStore"
export let value export let value
export let production = false export let production = false

3
packages/builder/src/components/automation/index.js

@ -1,3 +0,0 @@
export { default as AutomationBuilder } from "./AutomationBuilder/AutomationBuilder.svelte"
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte"
export { default as AutomationPanel } from "./AutomationPanel/AutomationPanel.svelte"

1
packages/builder/src/components/backend/DataTable/DataTable.svelte

@ -12,7 +12,6 @@
$: title = $backendUiStore.selectedTable.name $: title = $backendUiStore.selectedTable.name
$: schema = $backendUiStore.selectedTable.schema $: schema = $backendUiStore.selectedTable.schema
$: tableId = $backendUiStore.selectedTable._id
$: tableView = { $: tableView = {
schema, schema,
name: $backendUiStore.selectedView.name, name: $backendUiStore.selectedView.name,

1
packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte

@ -1,7 +1,6 @@
<script> <script>
import api from "builderStore/api" import api from "builderStore/api"
import Table from "./Table.svelte" import Table from "./Table.svelte"
import { onMount } from "svelte"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
export let tableId export let tableId

96
packages/builder/src/components/backend/DataTable/Table.svelte

@ -1,6 +1,5 @@
<script> <script>
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
import { Button } from "@budibase/bbui"
import { goto, params } from "@sveltech/routify" import { goto, params } from "@sveltech/routify"
import AgGrid from "@budibase/svelte-ag-grid" import AgGrid from "@budibase/svelte-ag-grid"
@ -8,11 +7,7 @@
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte" import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
import { import { getRenderer, editRowRenderer } from "./cells/cellRenderers"
getRenderer,
editRowRenderer,
deleteRowRenderer,
} from "./cells/cellRenderers"
import TableLoadingOverlay from "./TableLoadingOverlay" import TableLoadingOverlay from "./TableLoadingOverlay"
import TableHeader from "./TableHeader" import TableHeader from "./TableHeader"
import "@budibase/svelte-ag-grid/dist/index.css" import "@budibase/svelte-ag-grid/dist/index.css"
@ -58,8 +53,8 @@
resizable: false, resizable: false,
suppressMovable: true, suppressMovable: true,
suppressMenu: true, suppressMenu: true,
minWidth: 100, minWidth: 84,
width: 100, width: 84,
cellRenderer: editRowRenderer, cellRenderer: editRowRenderer,
}, },
] ]
@ -100,7 +95,7 @@
} }
const deleteRows = async () => { const deleteRows = async () => {
const response = await api.post(`/api/${tableId}/rows`, { await api.post(`/api/${tableId}/rows`, {
rows: selectedRows, rows: selectedRows,
type: "delete", type: "delete",
}) })
@ -110,16 +105,23 @@
} }
</script> </script>
<section> <div>
<div class="table-controls"> <div class="table-title">
<h2 class="title"><span>{title}</span></h2> <h1>{title}</h1>
<div class="popovers"> {#if loading}
<slot /> <div transition:fade>
{#if selectedRows.length > 0} <Spinner size="10" />
<DeleteRowsButton {selectedRows} {deleteRows} /> </div>
{/if} {/if}
</div>
</div> </div>
<div class="popovers">
<slot />
{#if selectedRows.length > 0}
<DeleteRowsButton {selectedRows} {deleteRows} />
{/if}
</div>
</div>
<div class="grid-wrapper">
<AgGrid <AgGrid
{theme} {theme}
{options} {options}
@ -127,35 +129,57 @@
{columnDefs} {columnDefs}
{loading} {loading}
on:select={({ detail }) => (selectedRows = detail)} /> on:select={({ detail }) => (selectedRows = detail)} />
</section> </div>
<style> <style>
.title { .table-title {
font-size: 24px; height: 24px;
font-weight: 600;
text-rendering: optimizeLegibility;
margin-top: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
} }
.table-title h1 {
.title > span { font-size: var(--font-size-m);
margin-right: var(--spacing-xs); font-weight: 500;
margin: 0;
} }
.table-title > div {
.table-controls { margin-left: var(--spacing-xs);
width: 100%;
} }
.popovers { .popovers {
display: flex; display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-l);
}
.popovers :global(button) {
font-weight: 500;
margin-top: var(--spacing-l);
}
.popovers :global(button svg) {
margin-right: var(--spacing-xs);
}
.grid-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.grid-wrapper :global(> *) {
height: auto;
flex: 1 1 auto;
} }
:global(.popovers > div) { :global(.ag-theme-alpine) {
margin-right: var(--spacing-m); --ag-border-color: var(--grey-4);
margin-bottom: var(--spacing-xl); --ag-header-background-color: var(--grey-1);
--ag-odd-row-background-color: var(--grey-1);
--ag-row-border-color: var(--grey-3);
} }
:global(.ag-menu) { :global(.ag-menu) {
@ -168,11 +192,15 @@
} }
:global(.ag-header-cell-text) { :global(.ag-header-cell-text) {
font-family: Inter; font-family: var(--font-sans);
font-weight: 600; font-weight: 600;
color: var(--ink); color: var(--ink);
} }
tbody tr:hover {
background: var(--grey-1);
}
:global(.ag-filter) { :global(.ag-filter) {
padding: var(--spacing-s); padding: var(--spacing-s);
outline: none; outline: none;

24
packages/builder/src/components/backend/DataTable/buttons/DeleteRowsButton.svelte

@ -1,6 +1,6 @@
<script> <script>
import { TextButton, Icon, Modal, ModalContent } from "@budibase/bbui" import { TextButton, Icon } from "@budibase/bbui"
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
export let selectedRows export let selectedRows
export let deleteRows export let deleteRows
@ -21,14 +21,12 @@
row(s) row(s)
</TextButton> </TextButton>
</div> </div>
<Modal bind:this={modal}> <ConfirmDialog
<ModalContent bind:this={modal}
red okText="Delete"
confirmText="Delete" onOk={confirmDeletion}
onConfirm={confirmDeletion} title="Confirm Deletion">
title="Confirm Row Deletion"> Are you sure you want to delete
Are you sure you want to delete {selectedRows.length}
{selectedRows.length} row{selectedRows.length > 1 ? 's' : ''}?
row{selectedRows.length > 1 ? 's' : ''}? </ConfirmDialog>
</ModalContent>
</Modal>

2
packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte

@ -170,7 +170,7 @@
okText="Delete Column" okText="Delete Column"
onOk={deleteColumn} onOk={deleteColumn}
onCancel={hideDeleteDialog} onCancel={hideDeleteDialog}
title="Confirm Delete" /> title="Confirm Deletion" />
<style> <style>
.actions { .actions {

5
packages/builder/src/components/backend/DataTable/modals/DeleteRow.svelte

@ -1,14 +1,11 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
import * as api from "../api" import * as api from "../api"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
export let row export let row
let anchor
let dropdown
let confirmDeleteDialog let confirmDeleteDialog
function showDelete() { function showDelete() {
@ -28,7 +25,7 @@
body={`Are you sure you wish to delete this row? Your data will be deleted and this action cannot be undone.`} body={`Are you sure you wish to delete this row? Your data will be deleted and this action cannot be undone.`}
okText="Delete Row" okText="Delete Row"
onOk={deleteRow} onOk={deleteRow}
title="Confirm Delete" /> title="Confirm Deletion" />
<style> <style>
.ri-delete-bin-line:hover { .ri-delete-bin-line:hover {

8
packages/builder/src/components/backend/DataTable/modals/EditRow.svelte

@ -1,13 +1,9 @@
<script> <script>
import { backendUiStore } from "builderStore" import { Modal, Button } from "@budibase/bbui"
import { DropdownMenu, Icon, Modal, Button } from "@budibase/bbui"
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte" import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
export let row export let row
let anchor
let dropdown
let confirmDeleteDialog
let modal let modal
function showModal(e) { function showModal(e) {
@ -16,7 +12,7 @@
} }
</script> </script>
<Button translucent small on:click={showModal}>Edit</Button> <Button data-cy="edit-row" translucent small on:click={showModal}>Edit</Button>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateEditRowModal {row} /> <CreateEditRowModal {row} />
</Modal> </Modal>

3
packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte

@ -1,10 +1,9 @@
<script> <script>
import { Heading, Body, Button, Select, Label } from "@budibase/bbui" import { Select, Label } from "@budibase/bbui"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import api from "builderStore/api" import api from "builderStore/api"
const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000 const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 1 const FILE_SIZE_LIMIT = BYTES_IN_MB * 1

116
packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte

@ -5,9 +5,10 @@
import CreateTableModal from "./modals/CreateTableModal.svelte" import CreateTableModal from "./modals/CreateTableModal.svelte"
import EditTablePopover from "./popovers/EditTablePopover.svelte" import EditTablePopover from "./popovers/EditTablePopover.svelte"
import EditViewPopover from "./popovers/EditViewPopover.svelte" import EditViewPopover from "./popovers/EditViewPopover.svelte"
import { Heading } from "@budibase/bbui" import { Modal } from "@budibase/bbui"
import { Spacer } from "@budibase/bbui" import NavItem from "components/common/NavItem.svelte"
let modal
$: selectedView = $: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name $backendUiStore.selectedView && $backendUiStore.selectedView.name
@ -20,67 +21,68 @@
backendUiStore.actions.views.select(view) backendUiStore.actions.views.select(view)
$goto(`./view/${view.name}`) $goto(`./view/${view.name}`)
} }
function onClickView(table, viewName) {
if (selectedView === viewName) {
return
}
selectView({
name: viewName,
...table.views[viewName],
})
}
</script> </script>
<div class="items-root"> {#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id} <div class="title">
<div class="hierarchy"> <h1>Tables</h1>
<div class="components-list-container"> <i data-cy="new-table" on:click={modal.show} class="ri-add-circle-fill" />
<Heading small>Tables</Heading> </div>
<Spacer medium /> <div class="hierarchy-items-container">
<CreateTableModal /> {#each $backendUiStore.tables as table, idx}
<div class="hierarchy-items-container"> <NavItem
{#each $backendUiStore.tables as table} border={idx > 0}
<ListItem icon="ri-table-line"
selected={selectedView === `all_${table._id}`} text={table.name}
title={table.name} selected={selectedView === `all_${table._id}`}
icon="ri-table-fill" on:click={() => selectTable(table)}>
on:click={() => selectTable(table)}> <EditTablePopover {table} />
<EditTablePopover {table} /> </NavItem>
</ListItem> {#each Object.keys(table.views || {}) as viewName}
{#each Object.keys(table.views || {}) as viewName} <NavItem
<ListItem indentLevel={1}
indented icon="ri-eye-line"
selected={selectedView === viewName} text={viewName}
title={viewName} selected={selectedView === viewName}
icon="ri-eye-line" on:click={() => onClickView(table, viewName)}>
on:click={() => (selectedView === viewName ? {} : selectView({ <EditViewPopover
name: viewName, view={{ name: viewName, ...table.views[viewName] }} />
...table.views[viewName], </NavItem>
}))}> {/each}
<EditViewPopover {/each}
view={{ name: viewName, ...table.views[viewName] }} /> </div>
</ListItem> {/if}
{/each} <Modal bind:this={modal}>
{/each} <CreateTableModal />
</div> </Modal>
</div>
</div>
{/if}
</div>
<style> <style>
h5 { .title {
font-size: 18px;
font-weight: 600;
margin-top: 0;
margin-bottom: var(--spacing-xl);
}
.items-root {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
justify-content: flex-start; justify-content: space-between;
align-items: stretch; align-items: center;
} }
.title h1 {
.hierarchy { font-size: var(--font-size-m);
display: flex; font-weight: 500;
flex-direction: column; margin: 0;
} }
.title i {
.hierarchy-items-container { font-size: 20px;
margin-top: var(--spacing-xl); }
flex: 1 1 auto; .title i:hover {
cursor: pointer;
color: var(--blue);
} }
</style> </style>

46
packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte

@ -2,8 +2,7 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { backendUiStore, store } from "builderStore" import { backendUiStore, store } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { Button, Input, Label, ModalContent, Modal } from "@budibase/bbui" import { Input, Label, ModalContent } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import TableDataImport from "../TableDataImport.svelte" import TableDataImport from "../TableDataImport.svelte"
import analytics from "analytics" import analytics from "analytics"
import screenTemplates from "builderStore/store/screenTemplates" import screenTemplates from "builderStore/store/screenTemplates"
@ -22,12 +21,6 @@
let dataImport let dataImport
let error = "" let error = ""
function resetState() {
name = ""
dataImport = undefined
error = ""
}
function checkValid(evt) { function checkValid(evt) {
const tableName = evt.target.value const tableName = evt.target.value
if ($backendUiStore.models?.some(model => model.name === tableName)) { if ($backendUiStore.models?.some(model => model.name === tableName)) {
@ -84,23 +77,20 @@
} }
</script> </script>
<Button primary wide on:click={modal.show}>Create New Table</Button> <ModalContent
<Modal bind:this={modal} on:hide={resetState}> title="Create Table"
<ModalContent confirmText="Create"
title="Create Table" onConfirm={saveTable}
confirmText="Create" disabled={error || !name || (dataImport && !dataImport.valid)}>
onConfirm={saveTable} <Input
disabled={error || !name || (dataImport && !dataImport.valid)}> data-cy="table-name-input"
<Input thin
data-cy="table-name-input" label="Table Name"
thin on:input={checkValid}
label="Table Name" bind:value={name}
on:input={checkValid} {error} />
bind:value={name} <div>
{error} /> <Label grey extraSmall>Create Table from CSV (Optional)</Label>
<div> <TableDataImport bind:dataImport />
<Label grey extraSmall>Create Table from CSV (Optional)</Label> </div>
<TableDataImport bind:dataImport /> </ModalContent>
</div>
</ModalContent>
</Modal>

97
packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte

@ -1,11 +1,9 @@
<script> <script>
import { backendUiStore, store } from "builderStore" import { backendUiStore, store } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" import { DropdownMenu, Button, Input } from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import screenTemplates from "builderStore/store/screenTemplates" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
import api from "builderStore/api"
export let table export let table
@ -66,42 +64,46 @@
} }
</script> </script>
<div bind:this={anchor} class="icon" on:click={dropdown.show}> <div on:click|stopPropagation>
<i class="ri-more-line" /> <div bind:this={anchor} class="icon" on:click={dropdown.show}>
<i class="ri-more-line" />
</div>
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
{#if editing}
<div class="actions">
<h5>Edit Table</h5>
<Input
label="Table Name"
thin
bind:value={table.name}
on:input={checkValid}
{error} />
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
<Button primary disabled={error} on:click={save}>Save</Button>
</footer>
</div>
{:else}
<DropdownContainer>
<DropdownItem
icon="ri-edit-line"
data-cy="edit-table"
title="Edit"
on:click={showEditor} />
<DropdownItem
icon="ri-delete-bin-line"
title="Delete"
on:click={showModal}
data-cy="delete-table" />
</DropdownContainer>
{/if}
</DropdownMenu>
</div> </div>
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
{#if editing}
<div class="actions">
<h5>Edit Table</h5>
<Input
label="Table Name"
thin
bind:value={table.name}
on:input={checkValid}
{error} />
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
<Button primary disabled={error} on:click={save}>Save</Button>
</footer>
</div>
{:else}
<ul>
<li on:click={showEditor}>
<Icon name="edit" />
Edit
</li>
<li data-cy="delete-table" on:click={showModal}>
<Icon name="delete" />
Delete
</li>
</ul>
{/if}
</DropdownMenu>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
okText="Delete Table" okText="Delete Table"
onOk={deleteTable} onOk={deleteTable}
title="Confirm Delete"> title="Confirm Deletion">
Are you sure you wish to delete the table Are you sure you wish to delete the table
<i>{table.name}?</i> <i>{table.name}?</i>
The following will also be deleted: The following will also be deleted:
@ -155,29 +157,4 @@
justify-content: flex-end; justify-content: flex-end;
gap: var(--spacing-m); gap: var(--spacing-m);
} }
ul {
list-style: none;
margin: 0;
padding: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0px;
align-items: center;
cursor: pointer;
}
li:hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
</style> </style>

85
packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte

@ -2,9 +2,9 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" import { DropdownMenu, Button, Input } from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
export let view export let view
@ -46,38 +46,42 @@
} }
</script> </script>
<div bind:this={anchor} class="icon" on:click={dropdown.show}> <div on:click|stopPropagation>
<i class="ri-more-line" /> <div bind:this={anchor} class="icon" on:click={dropdown.show}>
<i class="ri-more-line" />
</div>
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
{#if editing}
<div class="actions">
<h5>Edit View</h5>
<Input label="View Name" thin bind:value={view.name} />
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
<Button primary on:click={save}>Save</Button>
</footer>
</div>
{:else}
<DropdownContainer>
<DropdownItem
icon="ri-edit-line"
data-cy="edit-view"
title="Edit"
on:click={showEditor} />
<DropdownItem
icon="ri-delete-bin-line"
title="Delete"
data-cy="delete-view"
on:click={showDelete} />
</DropdownContainer>
{/if}
</DropdownMenu>
</div> </div>
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
{#if editing}
<div class="actions">
<h5>Edit View</h5>
<Input label="View Name" thin bind:value={view.name} />
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
<Button primary on:click={save}>Save</Button>
</footer>
</div>
{:else}
<ul>
<li on:click={showEditor}>
<Icon name="edit" />
Edit
</li>
<li data-cy="delete-view" on:click={showDelete}>
<Icon name="delete" />
Delete
</li>
</ul>
{/if}
</DropdownMenu>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
body={`Are you sure you wish to delete the view '${view.name}'? Your data will be deleted and this action cannot be undone.`} body={`Are you sure you wish to delete the view '${view.name}'? Your data will be deleted and this action cannot be undone.`}
okText="Delete View" okText="Delete View"
onOk={deleteView} onOk={deleteView}
title="Confirm Delete" /> title="Confirm Deletion" />
<style> <style>
div.icon { div.icon {
@ -108,29 +112,4 @@
justify-content: flex-end; justify-content: flex-end;
gap: var(--spacing-m); gap: var(--spacing-m);
} }
ul {
list-style: none;
margin: 0;
padding: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0px;
align-items: center;
cursor: pointer;
}
li:hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
</style> </style>

70
packages/builder/src/components/common/ActionButton.svelte

@ -1,70 +0,0 @@
<script>
export let disabled = false
export let hidden = false
export let secondary = false
export let primary = true
export let cancel = false
export let alert = false
export let warning = false
</script>
<button
on:click
class="button"
class:hidden
class:secondary
class:primary
class:alert
class:cancel
class:warning
{disabled}>
<slot />
</button>
<style>
.primary {
color: #ffffff;
background: var(--blue);
border: solid 1px var(--blue);
}
.alert {
color: white;
background: #e26d69;
border: solid 1px #e26d69;
}
.cancel {
color: var(--secondary40);
background: none;
}
.secondary {
color: var(--ink);
border: solid 1px var(--grey-4);
background: white;
}
.button {
font-size: 14px;
font-weight: 500;
border-radius: 3px;
padding: 10px 20px;
height: 40px;
}
.button:hover {
cursor: pointer;
filter: saturate(90%);
}
.button:disabled {
color: rgba(22, 48, 87, 0.2);
cursor: default;
background: transparent;
}
.hidden {
visibility: hidden;
}
</style>

171
packages/builder/src/components/common/Button.svelte

@ -1,171 +0,0 @@
<script>
export let color = "primary"
export let className = ""
export let style = ""
export let groupPosition = ""
export let grouped = false
$: borderClass = grouped ? "" : "border-normal"
</script>
<button
class="{color}
{className}
{borderClass}
{grouped ? 'grouped' : ''}"
{style}
on:click>
<slot />
</button>
<style>
.border-normal {
border-radius: var(--borderradiusall);
}
.border-left {
border-radius: var(--borderradius) 0 0 var(--borderradius);
}
.border-right {
border-radius: 0 var(--borderradius) var(--borderradius) 0;
}
.border-middle {
border-radius: 0;
}
button {
border-style: solid;
padding: 7.5px 15px;
cursor: pointer;
margin: 5px;
border-radius: 5px;
}
/* ---- PRIMARY ----*/
.primary {
background-color: var(--blue);
border-color: var(--blue);
color: var(--white);
}
.primary:hover {
background-color: var(--blue);
border-color: var(--blue);
}
.primary:active {
background-color: var(--primarydark);
border-color: var(--primarydark);
}
.primary-outline {
background-color: var(--white);
border-color: var(--blue);
color: var(--blue);
}
.primary-outline:hover {
background-color: var(--primary10);
}
.primary-outline:pressed {
background-color: var(--primary25);
}
/* ---- secondary ----*/
.secondary {
background-color: var(--ink);
border-color: var(--ink);
color: var(--white);
}
.secondary:hover {
background-color: var(--secondary75);
border-color: var(--secondary75);
}
.secondary:pressed {
background-color: var(--secondarydark);
border-color: var(--secondarydark);
}
.secondary-outline {
background-color: var(--white);
border-color: var(--ink);
color: var(--ink);
}
.secondary-outline:hover {
background-color: var(--secondary10);
}
.secondary-outline:pressed {
background-color: var(--secondary25);
}
/* ---- success ----*/
.success {
background-color: var(--success100);
border-color: var(--success100);
color: var(--white);
}
.success:hover {
background-color: var(--success75);
border-color: var(--success75);
}
.success:pressed {
background-color: var(--successdark);
border-color: var(--successdark);
}
.success-outline {
background-color: var(--white);
border-color: var(--success100);
color: var(--success100);
}
.success-outline:hover {
background-color: var(--success10);
}
.success-outline:pressed {
background-color: var(--success25);
}
/* ---- deletion ----*/
.deletion {
background-color: var(--red);
border-color: var(--red);
color: var(--white);
}
.deletion:hover {
background-color: var(--red-light);
border-color: var(--red);
color: var(--red);
}
.deletion:pressed {
background-color: var(--red-dark);
border-color: var(--red-dark);
color: var(--white);
}
.deletion-outline {
background-color: var(--white);
border-color: var(--red);
color: var(--red);
}
.deletion-outline:hover {
background-color: var(--red-light);
color: var(--red);
}
.deletion-outline:pressed {
background-color: var(--red-dark);
color: var(--white);
}
</style>

15
packages/builder/src/components/common/ButtonGroup.svelte

@ -1,15 +0,0 @@
<script>
export let style = ""
</script>
<div class="root" {style}>
<slot />
</div>
<style>
.root {
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>

13
packages/builder/src/components/common/Dropdowns/DropdownContainer.svelte

@ -0,0 +1,13 @@
<div class="dropdown-container" on:click>
<slot />
</div>
<style>
.dropdown-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
padding: var(--spacing-s) 0;
}
</style>

70
packages/builder/src/components/common/Dropdowns/DropdownItem.svelte

@ -0,0 +1,70 @@
<script>
export let icon
export let title
export let subtitle
export let disabled
</script>
<div
class="dropdown-item"
class:disabled
on:click
class:big={subtitle != null}
{...$$restProps}>
{#if icon}<i class={icon} />{/if}
<div class="content">
<div class="title">{title}</div>
{#if subtitle != null}
<div class="subtitle">{subtitle}</div>
{/if}
</div>
</div>
<style>
.dropdown-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
padding: var(--spacing-xs) var(--spacing-l);
color: var(--ink);
}
.dropdown-item.disabled,
.dropdown-item.disabled .subtitle {
pointer-events: none;
color: var(--grey-5);
}
.dropdown-item.big {
padding: var(--spacing-s) var(--spacing-l);
}
.dropdown-item:not(.disabled):hover {
background-color: var(--grey-2);
cursor: pointer;
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.title,
.subtitle {
font-size: var(--font-size-xs);
}
.title {
font-weight: 400;
}
.subtitle {
color: var(--grey-7);
font-weight: 400;
}
i {
font-size: 16px;
}
</style>

2
packages/builder/src/components/common/Dropdowns/index.js

@ -0,0 +1,2 @@
export { default as DropdownContainer } from "./DropdownContainer.svelte"
export { default as DropdownItem } from "./DropdownItem.svelte"

6
packages/builder/src/components/common/Dropzone.svelte

@ -1,11 +1,13 @@
<script> <script>
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { Heading, Body, Button, Dropzone } from "@budibase/bbui" import { Dropzone } from "@budibase/bbui"
import api from "builderStore/api" import api from "builderStore/api"
export let files = [] export let files = []
function handleFileTooLarge() { const BYTES_IN_MB = 1000000
function handleFileTooLarge(fileSizeLimit) {
notifier.danger( notifier.danger(
`Files cannot exceed ${fileSizeLimit / `Files cannot exceed ${fileSizeLimit /
BYTES_IN_MB}MB. Please try again with smaller files.` BYTES_IN_MB}MB. Please try again with smaller files.`

109
packages/builder/src/components/common/NavItem.svelte

@ -0,0 +1,109 @@
<script>
export let icon
export let withArrow = false
export let withActions = true
export let indentLevel = 0
export let text
export let border = true
export let selected = false
export let opened = false
export let draggable = false
</script>
<div
class="nav-item"
class:border
class:selected
style={`padding-left: ${indentLevel * 18}px`}
{draggable}
on:dragend
on:dragstart
on:dragover
on:drop
on:click
ondragover="return false"
ondragenter="return false">
<div class="content">
{#if withArrow}
<div class:opened class="icon arrow">
<i class="ri-arrow-right-s-line" />
</div>
{/if}
{#if icon}
<div class="icon"><i class={icon} /></div>
{/if}
<div class="text">{text}</div>
{#if withActions}
<div class="actions">
<slot />
</div>
{/if}
</div>
</div>
<style>
.nav-item {
border-radius: var(--border-radius-m);
cursor: pointer;
color: var(--grey-7);
}
.nav-item.border {
border-top: 1px solid var(--grey-1);
}
.nav-item.selected {
background-color: var(--grey-2);
color: var(--ink);
}
.nav-item:hover {
background-color: var(--grey-1);
}
.nav-item:hover .actions {
display: flex;
}
.content {
padding: 0 var(--spacing-m);
height: 32px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xs);
}
.icon {
font-size: 16px;
flex: 0 0 20px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.icon.arrow {
margin: 0 -2px 0 -6px;
font-size: 12px;
}
.icon.arrow.opened {
transform: rotate(90deg);
}
.icon + .icon {
margin-left: -4px;
}
.text {
flex: 1 1 auto;
font-weight: 500;
font-size: var(--font-size-xs);
}
.actions {
display: none;
width: 20px;
height: 20px;
cursor: pointer;
position: relative;
flex-direction: row;
justify-content: center;
align-items: center;
}
</style>

36
packages/builder/src/components/common/NumberBox.svelte

@ -1,36 +0,0 @@
<script>
export let value
export let label
const inputChanged = ev => {
try {
value = Number(ev.target.value)
} catch (_) {
value = null
}
}
let numberText = value === null || value === undefined ? "" : value.toString()
</script>
<div class="numberbox">
<label>{label}</label>
<input
class="budibase__input"
type="number"
{value}
on:change={inputChanged} />
</div>
<style>
.numberbox {
display: grid;
align-items: center;
}
label {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
</style>

76
packages/builder/src/components/common/Switcher.svelte

@ -1,76 +0,0 @@
<script>
export let tabs = []
export const selectTab = tabName => {
selected = tabName
selectedIndex = tabs.indexOf(selected)
}
let selected = tabs.length > 0 && tabs[0]
let selectedIndex = 0
const isSelected = tab => selected === tab
</script>
<div class="root">
<div class="switcher">
{#each tabs as tab}
<button class:selected={selected === tab} on:click={() => selectTab(tab)}>
{tab}
</button>
{/each}
</div>
<div class="panel">
{#if selectedIndex === 0}
<slot name="0" />
{:else if selectedIndex === 1}
<slot name="1" />
{:else if selectedIndex === 2}
<slot name="2" />
{:else if selectedIndex === 3}
<slot name="3" />
{/if}
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px 20px;
border-left: solid 1px var(--grey-2);
box-sizing: border-box;
}
.switcher {
display: flex;
margin: 0px 20px 20px 0px;
box-sizing: border-box;
}
.switcher > button {
display: inline-block;
border: none;
margin: 0;
padding: 0;
cursor: pointer;
font-size: 18px;
font-weight: 600;
color: var(--grey-5);
margin-right: 20px;
background: none;
outline: none;
font-family: Inter;
}
.switcher > .selected {
color: var(--ink);
}
.panel {
min-height: 0;
height: 100%;
overflow-y: auto;
}
</style>

3
packages/builder/src/components/common/core.js

@ -1,3 +0,0 @@
import { flow } from "lodash/fp"
export const pipe = (arg, funcs) => flow(funcs)(arg)

13
packages/builder/src/components/common/eventHandlers.js

@ -1,13 +0,0 @@
import { eventHandlers } from "../../../../client/src/state/eventHandlers"
export { EVENT_TYPE_MEMBER_NAME } from "../../../../client/src/state/eventHandlers"
export const allHandlers = () => {
const handlersObj = eventHandlers()
const handlers = Object.keys(handlersObj).map(name => ({
name,
...handlersObj[name],
}))
return handlers
}

24
packages/builder/src/components/settings/Link.svelte

@ -1,32 +1,36 @@
<script> <script>
import SettingsModal from "./SettingsModal.svelte" import SettingsModal from "./SettingsModal.svelte"
import { SettingsIcon } from "components/common/Icons/"
import { Modal } from "@budibase/bbui" import { Modal } from "@budibase/bbui"
let modal let modal
</script> </script>
<span class="topnavitemright settings" on:click={modal.show}> <div class="topnavitemright settings" on:click={modal.show}>
<SettingsIcon /> <i class="ri-settings-3-line" />
</span> </div>
<Modal bind:this={modal} width="600px"> <Modal bind:this={modal} width="600px">
<SettingsModal /> <SettingsModal />
</Modal> </Modal>
<style> <style>
span:first-letter { i {
text-transform: capitalize; font-size: 18px;
color: var(--grey-7);
} }
.topnavitemright { .topnavitemright {
cursor: pointer; cursor: pointer;
color: var(--grey-7); color: var(--grey-7);
margin: 0 20px 0 0; margin: 0 12px 0 0;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
height: 100%;
display: flex; display: flex;
flex: 1; flex-direction: row;
justify-content: center;
align-items: center; align-items: center;
box-sizing: border-box; height: 24px;
width: 24px;
}
.topnavitemright:hover i {
color: var(--ink);
} }
</style> </style>

13
packages/builder/src/components/settings/TabTitle.svelte

@ -1,13 +0,0 @@
<h3>
<slot />
</h3>
<style>
h3 {
font-size: 18px;
font-weight: bold;
color: var(--ink);
margin-top: 40px;
margin-bottom: 20px;
}
</style>

3
packages/builder/src/components/start/CreateAppModal.svelte

@ -7,9 +7,8 @@
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import { API, Info, User } from "./Steps" import { API, Info, User } from "./Steps"
import Indicator from "./Indicator.svelte" import Indicator from "./Indicator.svelte"
import { Input, TextArea, Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
import { post } from "builderStore/api" import { post } from "builderStore/api"
import analytics from "analytics" import analytics from "analytics"

2
packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte

@ -2,7 +2,7 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { map, join } from "lodash/fp" import { map, join } from "lodash/fp"
import iframeTemplate from "./iframeTemplate" import iframeTemplate from "./iframeTemplate"
import { pipe } from "components/common/core" import { pipe } from "../../../helpers"
let iframe let iframe
let styles = "" let styles = ""

2
packages/builder/src/components/userInterface/BindableInput.svelte

@ -43,7 +43,7 @@
width: 24px; width: 24px;
background: var(--grey-4); background: var(--grey-4);
right: var(--spacing-s); right: var(--spacing-s);
bottom: 9px; bottom: 5px;
} }
button:hover { button:hover {
background: var(--grey-5); background: var(--grey-5);

8
packages/builder/src/components/userInterface/CategoryTab.svelte

@ -18,9 +18,13 @@
<style> <style>
.tabs { .tabs {
display: flex; display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
list-style: none; list-style: none;
font-size: 18px; font-size: var(--font-size-m);
font-weight: 600; font-weight: 500;
height: 24px;
} }
li { li {

149
packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte

@ -3,10 +3,10 @@
import { store } from "builderStore" import { store } from "builderStore"
import { getComponentDefinition } from "builderStore/storeUtils" import { getComponentDefinition } from "builderStore/storeUtils"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last, cloneDeep } from "lodash/fp" import { last } from "lodash/fp"
import { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils" import { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils"
import { uuid } from "builderStore/uuid"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
export let component export let component
@ -98,110 +98,65 @@
} }
</script> </script>
<div bind:this={anchor} on:click|stopPropagation={() => {}}> <div bind:this={anchor} on:click|stopPropagation>
<div class="icon" on:click={dropdown.show}><i class="ri-more-line" /></div> <div class="icon" on:click={dropdown.show}><i class="ri-more-line" /></div>
<DropdownMenu bind:this={dropdown} width="170px" {anchor} align="left">
<DropdownContainer on:click={hideDropdown}>
<DropdownItem
icon="ri-delete-bin-line"
title="Delete"
on:click={() => confirmDeleteDialog.show()} />
<DropdownItem
icon="ri-arrow-up-line"
title="Move up"
on:click={moveUpComponent} />
<DropdownItem
icon="ri-arrow-down-line"
title="Move down"
on:click={moveDownComponent} />
<DropdownItem
icon="ri-repeat-one-line"
title="Duplicate"
on:click={copyComponent} />
<DropdownItem
icon="ri-scissors-cut-line"
title="Cut"
on:click={() => storeComponentForCopy(true)} />
<DropdownItem
icon="ri-file-copy-line"
title="Copy"
on:click={() => storeComponentForCopy(false)} />
<hr class="hr-style" />
<DropdownItem
icon="ri-insert-row-top"
title="Paste above"
disabled={noPaste}
on:click={() => pasteComponent('above')} />
<DropdownItem
icon="ri-insert-row-bottom"
title="Paste below"
disabled={noPaste}
on:click={() => pasteComponent('below')} />
<DropdownItem
icon="ri-insert-column-right"
title="Paste inside"
disabled={noPaste || noChildrenAllowed}
on:click={() => pasteComponent('inside')} />
</DropdownContainer>
</DropdownMenu>
</div> </div>
<DropdownMenu
bind:this={dropdown}
on:click={hideDropdown}
width="170px"
{anchor}
align="left">
<ul>
<li class="item" on:click={() => confirmDeleteDialog.show()}>
<i class="ri-delete-bin-2-line" />
Delete
</li>
<li class="item" on:click={moveUpComponent}>
<i class="ri-arrow-up-line" />
Move up
</li>
<li class="item" on:click={moveDownComponent}>
<i class="ri-arrow-down-line" />
Move down
</li>
<li class="item" on:click={copyComponent}>
<i class="ri-repeat-one-line" />
Duplicate
</li>
<li class="item" on:click={() => storeComponentForCopy(true)}>
<i class="ri-scissors-cut-line" />
Cut
</li>
<li class="item" on:click={() => storeComponentForCopy(false)}>
<i class="ri-file-copy-line" />
Copy
</li>
<hr class="hr-style" />
<li
class="item"
class:disabled={noPaste}
on:click={() => pasteComponent('above')}>
<i class="ri-insert-row-top" />
Paste above
</li>
<li
class="item"
class:disabled={noPaste}
on:click={() => pasteComponent('below')}>
<i class="ri-insert-row-bottom" />
Paste below
</li>
<li
class="item"
class:disabled={noPaste || noChildrenAllowed}
on:click={() => pasteComponent('inside')}>
<i class="ri-insert-column-right" />
Paste inside
</li>
</ul>
</DropdownMenu>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
title="Confirm Delete" title="Confirm Deletion"
body={`Are you sure you wish to delete this '${lastPartOfName(component)}' component?`} body={`Are you sure you wish to delete this '${lastPartOfName(component)}' component?`}
okText="Delete Component" okText="Delete Component"
onOk={deleteComponent} /> onOk={deleteComponent} />
<style> <style>
ul { hr {
list-style: none;
padding: 0;
margin: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0;
align-items: center;
cursor: pointer;
}
li:not(.disabled):hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
li i {
margin-right: 8px;
font-size: var(--font-size-s);
}
li.disabled {
color: var(--grey-4);
cursor: default;
}
.icon i {
font-size: 16px;
}
.hr-style {
margin: 8px 0; margin: 8px 0;
color: var(--grey-4); background-color: var(--grey-4);
height: 1px;
border: none;
} }
</style> </style>

56
packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte

@ -63,41 +63,31 @@
} }
</script> </script>
<div class="root"> <CategoryTab
<CategoryTab onClick={category => (selectedCategory = category)}
onClick={category => (selectedCategory = category)} {categories}
{categories} {selectedCategory} />
{selectedCategory} />
{#if displayName}
{#if displayName} <div class="instance-name">{componentInstance._instanceName}</div>
<div class="instance-name">{componentInstance._instanceName}</div> {/if}
<div class="component-props-container">
{#if selectedCategory.value === 'design'}
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
{:else if selectedCategory.value === 'settings'}
<SettingsView
{componentInstance}
{componentDefinition}
{panelDefinition}
displayNameField={displayName}
onChange={store.setComponentProp}
onScreenPropChange={store.setPageOrScreenProp}
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
{/if} {/if}
<div class="component-props-container">
{#if selectedCategory.value === 'design'}
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
{:else if selectedCategory.value === 'settings'}
<SettingsView
{componentInstance}
{componentDefinition}
{panelDefinition}
displayNameField={displayName}
onChange={store.setComponentProp}
onScreenPropChange={store.setPageOrScreenProp}
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
{/if}
</div>
</div> </div>
<style> <style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
}
.title > div:nth-child(1) { .title > div:nth-child(1) {
grid-column-start: name; grid-column-start: name;
color: var(--ink); color: var(--ink);
@ -108,7 +98,6 @@
} }
.component-props-container { .component-props-container {
margin-top: 16px;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
@ -118,8 +107,7 @@
} }
.instance-name { .instance-name {
margin-top: 20px; font-size: var(--font-size-xs);
font-size: 14px;
font-weight: 500; font-weight: 500;
color: var(--grey-7); color: var(--grey-7);
} }

106
packages/builder/src/components/userInterface/ComponentSelectionList.svelte

@ -2,47 +2,97 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
import Tab from "./ItemTab/Tab.svelte" const categories = components.categories
let selectedIndex
let anchors = []
let popover
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex]
export let toggleTab const close = () => {
popover.hide()
}
const categories = components.categories const onCategoryChosen = (category, idx) => {
let selectedCategory = categories[0] if (category.isCategory) {
selectedIndex = idx
popover.show()
} else {
onComponentChosen(category)
}
}
const onComponentChosen = component => { const onComponentChosen = component => {
store.addChildComponent(component._component, component.presetProps) store.addChildComponent(component._component, component.presetProps)
toggleTab("Navigate")
// Get ID path
const path = store.getPathToComponent($store.currentComponentInfo) const path = store.getPathToComponent($store.currentComponentInfo)
// Go to correct URL
$goto(`./:page/:screen/${path}`) $goto(`./:page/:screen/${path}`)
close()
} }
</script> </script>
<div class="root"> <div class="container">
<CategoryTab {#each categories as category, idx}
onClick={category => (selectedCategory = category)} <div
{selectedCategory} bind:this={anchors[idx]}
{categories} /> class="category"
on:click={() => onCategoryChosen(category, idx)}
<div class="panel"> class:active={idx === selectedIndex}>
<Tab {#if category.icon}<i class={category.icon} />{/if}
list={selectedCategory} <span>{category.name}</span>
on:selectItem={e => onComponentChosen(e.detail)} {#if category.isCategory}<i class="ri-arrow-down-s-line arrow" />{/if}
{toggleTab} /> </div>
</div> {/each}
</div> </div>
<DropdownMenu
on:close={() => (selectedIndex = null)}
bind:this={popover}
{anchor}
align="left">
<DropdownContainer>
{#each categories[selectedIndex].children as item}
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)}
<DropdownItem
icon={item.icon}
title={item.name}
on:click={() => onComponentChosen(item)} />
{/if}
{/each}
</DropdownContainer>
</DropdownMenu>
<style> <style>
.panel { .container {
margin-top: 20px; display: flex;
display: grid; flex-direction: row;
grid-template-columns: 1fr 1fr; justify-content: flex-start;
grid-gap: 20px; align-items: center;
z-index: 1;
min-height: 24px;
flex-wrap: wrap;
gap: var(--spacing-l);
}
.category {
color: var(--grey-7);
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--font-size-xs);
}
.category span {
font-weight: 500;
user-select: none;
}
.category.active,
.category:hover {
color: var(--ink);
}
.category i:not(:last-child) {
font-size: 16px;
} }
</style> </style>

92
packages/builder/src/components/userInterface/ComponentsHierarchy.svelte

@ -1,13 +1,12 @@
<script> <script>
import { params, goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp" import { trimCharsStart, trimChars } from "lodash/fp"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import { pipe } from "../../helpers"
import { pipe } from "components/common/core"
import { store } from "builderStore" import { store } from "builderStore"
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte" import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import NavItem from "components/common/NavItem.svelte"
export let screens = [] export let screens = []
@ -24,8 +23,6 @@
let confirmDeleteDialog let confirmDeleteDialog
let componentToDelete = "" let componentToDelete = ""
const joinPath = join("/")
const normalizedName = name => const normalizedName = name =>
pipe(name, [ pipe(name, [
trimCharsStart("./"), trimCharsStart("./"),
@ -42,26 +39,15 @@
<div class="root"> <div class="root">
{#each sortedScreens as screen} {#each sortedScreens as screen}
<div <NavItem
class="budibase__nav-item screen-header-row" icon="ri-artboard-2-line"
class:selected={$store.currentComponentInfo._id === screen.props._id} text={screen.props._instanceName}
on:click|stopPropagation={() => changeScreen(screen)}> withArrow={screen.props._children.length}
<span selected={$store.currentComponentInfo._id === screen.props._id}
class="icon" opened={$store.currentPreviewItem.name === screen.props._id}
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}> on:click={() => changeScreen(screen)}>
{#if screen.props._children.length} <ScreenDropdownMenu {screen} />
<ArrowDownIcon /> </NavItem>
{/if}
</span>
<i class="ri-artboard-2-fill icon" />
<span class="title">{screen.props._instanceName}</span>
<div class="dropdown-menu">
<ScreenDropdownMenu {screen} />
</div>
</div>
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children} {#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
<ComponentsHierarchyChildren <ComponentsHierarchyChildren
@ -71,55 +57,3 @@
{/if} {/if}
{/each} {/each}
</div> </div>
<style>
.root {
font-weight: 400;
color: var(--ink);
}
.screen-header-row {
display: flex;
flex-direction: row;
}
.title {
margin-left: 14px;
font-size: 14px;
font-weight: 400;
flex: 1;
}
.icon {
display: inline-block;
transition: 0.2s;
font-size: 24px;
width: 18px;
color: var(--grey-7);
}
.icon:nth-of-type(2) {
width: 14px;
margin: 0 0 0 5px;
}
.rotate :global(svg) {
transform: rotate(-90deg);
}
.dropdown-menu {
display: none;
color: var(--ink);
padding: 0 5px;
width: 24px;
height: 24px;
border-style: none;
background: rgba(0, 0, 0, 0);
cursor: pointer;
position: relative;
}
.budibase__nav-item:hover .dropdown-menu {
display: block;
}
</style>

83
packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte

@ -2,14 +2,9 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "../../helpers"
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte" import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
import { import NavItem from "components/common/NavItem.svelte"
XCircleIcon,
ChevronUpIcon,
ChevronDownIcon,
CopyIcon,
} from "../common/Icons"
import { getComponentDefinition } from "builderStore/storeUtils" import { getComponentDefinition } from "builderStore/storeUtils"
export let components = [] export let components = []
@ -40,7 +35,6 @@
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1) const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
const get_name = s => (!s ? "" : last(s.split("/"))) const get_name = s => (!s ? "" : last(s.split("/")))
const get_capitalised_name = name => pipe(name, [get_name, capitalise]) const get_capitalised_name = name => pipe(name, [get_name, capitalise])
const isScreenslot = name => name === "##builtin/screenslot" const isScreenslot = name => name === "##builtin/screenslot"
@ -134,29 +128,22 @@
on:drop={drop} on:drop={drop}
ondragover="return false" ondragover="return false"
ondragenter="return false" ondragenter="return false"
class="budibase__nav-item item drop-item" class="drop-item"
style="margin-left: {level * 20 + 40}px" /> style="margin-left: {(level + 1) * 18}px" />
{/if} {/if}
<div <NavItem
class="budibase__nav-item item" draggable
class:selected={currentComponent === component}
style="padding-left: {level * 20 + 40}px"
draggable={true}
on:dragend={dragend} on:dragend={dragend}
on:dragstart={dragstart(component)} on:dragstart={dragstart(component)}
on:dragover={dragover(component, index)} on:dragover={dragover(component, index)}
on:drop={drop} on:drop={drop}
ondragover="return false" text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
ondragenter="return false"> withArrow
<div class="nav-item"> indentLevel={level + 1}
<i class="icon ri-arrow-right-circle-line" /> selected={currentComponent === component}>
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName} <ComponentDropdownMenu {component} />
</div> </NavItem>
<div class="actions">
<ComponentDropdownMenu {component} />
</div>
</div>
{#if component._children} {#if component._children}
<svelte:self <svelte:self
@ -172,8 +159,8 @@
on:drop={drop} on:drop={drop}
ondragover="return false" ondragover="return false"
ondragenter="return false" ondragenter="return false"
class="budibase__nav-item item drop-item" class="drop-item"
style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 2 : 0)) * 20 + 40}px" /> style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 3 : 1)) * 18}px" />
{/if} {/if}
</li> </li>
{/each} {/each}
@ -186,47 +173,9 @@
margin: 0; margin: 0;
} }
.item {
display: grid;
grid-template-columns: 1fr auto auto auto;
padding: 0 var(--spacing-m);
margin: 0;
border-radius: var(--border-radius-m);
height: 36px;
align-items: center;
}
.drop-item { .drop-item {
border-radius: var(--border-radius-m);
height: 32px;
background: var(--blue-light); background: var(--blue-light);
height: 36px;
}
.actions {
display: none;
color: var(--ink);
border-style: none;
background: rgba(0, 0, 0, 0);
cursor: pointer;
position: relative;
}
.item:hover {
background: var(--grey-1);
cursor: pointer;
}
.item:hover .actions {
display: block;
}
.nav-item {
display: flex;
align-items: center;
font-size: 14px;
color: var(--ink);
}
.icon {
color: var(--grey-7);
margin-right: 8px;
} }
</style> </style>

13
packages/builder/src/components/userInterface/DesignView.svelte

@ -48,7 +48,7 @@
{/each} {/each}
{:else} {:else}
<div class="no-design"> <div class="no-design">
This component does not have any design properties. This component doesn't have any design properties.
</div> </div>
{/if} {/if}
</div> </div>
@ -61,10 +61,10 @@
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
gap: var(--spacing-l);
} }
.design-view-state-categories { .design-view-state-categories {
flex: 0 0 50px;
} }
.positioned-wrapper { .positioned-wrapper {
@ -79,10 +79,15 @@
min-height: 0; min-height: 0;
margin: 0 -20px; margin: 0 -20px;
padding: 0 20px; padding: 0 20px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
} }
.no-design { .no-design {
font-size: var(--font-size-s); font-size: var(--font-size-xs);
color: var(--grey-6); color: var(--grey-5);
} }
</style> </style>

8
packages/builder/src/components/userInterface/DetailScreenSelect.svelte

@ -29,7 +29,13 @@
</script> </script>
<div> <div>
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value> <DataList
editable
secondary
extraThin
on:blur={handleBlur}
on:change
bind:value>
<option value="" /> <option value="" />
{#each urls as url} {#each urls as url}
<option value={url.url}>{url.name}</option> <option value={url.url}>{url.name}</option>

11
packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte

@ -1,14 +1,7 @@
<script> <script>
import { store } from "builderStore" import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui"
import {
TextButton,
Button,
Body,
DropdownMenu,
ModalContent,
} from "@budibase/bbui"
import { AddIcon, ArrowDownIcon } from "components/common/Icons/" import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers" import { EVENT_TYPE_MEMBER_NAME } from "../../../../../client/src/state/eventHandlers"
import actionTypes from "./actions" import actionTypes from "./actions"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"

5
packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte

@ -1,11 +1,6 @@
<script> <script>
import { Input, DataList, Select } from "@budibase/bbui" import { Input, DataList, Select } from "@budibase/bbui"
import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe } from "components/common/core"
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { ArrowDownIcon } from "components/common/Icons/"
import { createEventDispatcher } from "svelte"
export let parameter export let parameter

41
packages/builder/src/components/userInterface/Feedback/FeedbackNavLink.svelte

@ -1,5 +1,4 @@
<script> <script>
import { FeedbackIcon } from "components/common/Icons/"
import { Popover } from "@budibase/bbui" import { Popover } from "@budibase/bbui"
import { store } from "builderStore" import { store } from "builderStore"
@ -16,41 +15,37 @@
}, FIVE_MINUTES) }, FIVE_MINUTES)
</script> </script>
<span <div class="container" bind:this={iconContainer} on:click={popover.show}>
class="container" <i class="ri-feedback-line" class:highlight={$store.highlightFeedbackIcon} />
bind:this={iconContainer} </div>
on:click={popover.show}
class:highlight={$store.highlightFeedbackIcon}>
<FeedbackIcon />
</span>
<Popover bind:this={popover} anchor={iconContainer} align="right"> <Popover bind:this={popover} anchor={iconContainer} align="right">
<FeedbackIframe on:finished={popover.hide} /> <FeedbackIframe on:finished={popover.hide} />
</Popover> </Popover>
<style> <style>
i {
font-size: 18px;
color: var(--grey-7);
}
i.highlight {
color: var(--blue);
filter: drop-shadow(0 0 20px var(--blue));
}
.container { .container {
cursor: pointer; cursor: pointer;
color: var(--grey-7); color: var(--grey-7);
margin: 0 20px 0 0; margin: 0 12px 0 0;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
height: 100%;
display: flex; display: flex;
flex: 1; flex-direction: row;
justify-content: center;
align-items: center; align-items: center;
box-sizing: border-box; height: 24px;
width: 24px;
} }
.container:hover i {
.container:hover {
color: var(--ink); color: var(--ink);
font-weight: 500;
}
.highlight {
color: var(--blue);
}
.highlight > :global(svg) {
filter: drop-shadow(0 0 20px var(--blue));
} }
</style> </style>

13
packages/builder/src/components/userInterface/FlatButton.svelte

@ -22,23 +22,22 @@
<style> <style>
.flatbutton { .flatbutton {
cursor: pointer; cursor: pointer;
max-height: 36px; height: 32px;
padding: 8px 2px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
background: #ffffff; background: white;
color: var(--grey-7); color: var(--grey-7);
border-radius: 5px; border-radius: var(--border-radius-m);
font-size: 14px; font-size: var(--font-size-xs);
font-weight: 400; font-weight: 500;
transition: all 0.3s; transition: all 0.3s;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
.selected { .selected {
background: var(--grey-3); background: var(--grey-2);
color: var(--ink); color: var(--ink);
} }

35
packages/builder/src/components/userInterface/FrontendNavigatePane.svelte

@ -4,22 +4,41 @@
import PageLayout from "components/userInterface/PageLayout.svelte" import PageLayout from "components/userInterface/PageLayout.svelte"
import PagesList from "components/userInterface/PagesList.svelte" import PagesList from "components/userInterface/PagesList.svelte"
import NewScreenModal from "components/userInterface/NewScreenModal.svelte" import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
import { Button, Spacer, Modal } from "@budibase/bbui" import { Modal } from "@budibase/bbui"
let modal let modal
</script> </script>
<div class="title">
<h1>Screens</h1>
<i on:click={modal.show} data-cy="new-screen" class="ri-add-circle-fill" />
</div>
<PagesList /> <PagesList />
<Spacer medium />
<Button primary wide on:click={modal.show}>Create New Screen</Button>
<Spacer medium />
<PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="nav-items-container"> <div class="nav-items-container">
<PageLayout layout={$store.pages[$store.currentPageName]} />
<ComponentsHierarchy screens={$store.screens} /> <ComponentsHierarchy screens={$store.screens} />
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<NewScreenModal /> <NewScreenModal />
</Modal> </Modal>
<style>
.title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.title h1 {
font-size: var(--font-size-m);
font-weight: 500;
margin: 0;
}
.title i {
font-size: 20px;
}
.title i:hover {
cursor: pointer;
color: var(--blue);
}
</style>

582
packages/builder/src/components/userInterface/IconSelect/IconSelect.svelte

@ -1,291 +1,291 @@
<script> <script>
import { DropdownMenu, Button, Input } from "@budibase/bbui" import { DropdownMenu, Button, Input } from "@budibase/bbui"
import { createEventDispatcher, tick } from "svelte" import { createEventDispatcher, tick } from "svelte"
import icons from "./icons.js" import icons from "./icons.js"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value = "" export let value = ""
export let maxIconsPerPage = 30 export let maxIconsPerPage = 30
let searchTerm = "" let searchTerm = ""
let selectedLetter = "A" let selectedLetter = "A"
let currentPage = 1 let currentPage = 1
let filteredIcons = findIconByTerm(selectedLetter) let filteredIcons = findIconByTerm(selectedLetter)
$: dispatch("change", value) $: dispatch("change", value)
const alphabet = [ const alphabet = [
"A", "A",
"B", "B",
"C", "C",
"D", "D",
"E", "E",
"F", "F",
"G", "G",
"H", "H",
"I", "I",
"J", "J",
"K", "K",
"L", "L",
"M", "M",
"N", "N",
"O", "O",
"P", "P",
"Q", "Q",
"R", "R",
"S", "S",
"T", "T",
"U", "U",
"V", "V",
"W", "W",
"X", "X",
"Y", "Y",
"Z", "Z",
] ]
let buttonAnchor, dropdown let buttonAnchor, dropdown
let loading = false let loading = false
function findIconByTerm(term) { function findIconByTerm(term) {
const r = new RegExp(`\^${term}`, "i") const r = new RegExp(`\^${term}`, "i")
return icons.filter(i => r.test(i.label)) return icons.filter(i => r.test(i.label))
} }
async function switchLetter(letter) { async function switchLetter(letter) {
currentPage = 1 currentPage = 1
searchTerm = "" searchTerm = ""
loading = true loading = true
selectedLetter = letter selectedLetter = letter
filteredIcons = findIconByTerm(letter) filteredIcons = findIconByTerm(letter)
await tick() //svg icons do not update without tick await tick() //svg icons do not update without tick
loading = false loading = false
} }
async function findIconOnPage() { async function findIconOnPage() {
loading = true loading = true
const iconIdx = filteredIcons.findIndex(i => i.value === value) const iconIdx = filteredIcons.findIndex(i => i.value === value)
if (iconIdx !== -1) { if (iconIdx !== -1) {
currentPage = Math.ceil(iconIdx / maxIconsPerPage) currentPage = Math.ceil(iconIdx / maxIconsPerPage)
} }
await tick() //svg icons do not update without tick await tick() //svg icons do not update without tick
loading = false loading = false
} }
async function setSelectedUI() { async function setSelectedUI() {
if (value) { if (value) {
const letter = displayValue.substring(0, 1) const letter = displayValue.substring(0, 1)
await switchLetter(letter) await switchLetter(letter)
await findIconOnPage() await findIconOnPage()
} }
} }
async function pageClick(next) { async function pageClick(next) {
loading = true loading = true
if (next && currentPage < totalPages) { if (next && currentPage < totalPages) {
currentPage++ currentPage++
} else if (!next && currentPage > 1) { } else if (!next && currentPage > 1) {
currentPage-- currentPage--
} }
await tick() //svg icons do not update without tick await tick() //svg icons do not update without tick
loading = false loading = false
} }
async function searchForIcon(e) { async function searchForIcon(e) {
currentPage = 1 currentPage = 1
loading = true loading = true
filteredIcons = findIconByTerm(searchTerm) filteredIcons = findIconByTerm(searchTerm)
await tick() //svg icons do not update without tick await tick() //svg icons do not update without tick
loading = false loading = false
} }
$: displayValue = value ? value.substring(7) : "Pick Icon" $: displayValue = value ? value.substring(7) : "Pick Icon"
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage) $: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
$: pageEndIdx = maxIconsPerPage * currentPage $: pageEndIdx = maxIconsPerPage * currentPage
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx) $: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx)
$: pagerText = `Page ${currentPage} of ${totalPages}` $: pagerText = `Page ${currentPage} of ${totalPages}`
</script> </script>
<div bind:this={buttonAnchor}> <div bind:this={buttonAnchor}>
<Button secondary on:click={dropdown.show}>{displayValue}</Button> <Button secondary small on:click={dropdown.show}>{displayValue}</Button>
</div> </div>
<DropdownMenu <DropdownMenu
bind:this={dropdown} bind:this={dropdown}
on:open={setSelectedUI} on:open={setSelectedUI}
anchor={buttonAnchor}> anchor={buttonAnchor}>
<div class="container"> <div class="container">
<div class="search-area"> <div class="search-area">
<div class="alphabet-area"> <div class="alphabet-area">
{#each alphabet as letter, idx} {#each alphabet as letter, idx}
<span <span
class="letter" class="letter"
class:letter-selected={letter === selectedLetter} class:letter-selected={letter === selectedLetter}
on:click={() => switchLetter(letter)}> on:click={() => switchLetter(letter)}>
{letter} {letter}
</span> </span>
{#if idx !== alphabet.length - 1}<span>-</span>{/if} {#if idx !== alphabet.length - 1}<span>-</span>{/if}
{/each} {/each}
</div> </div>
<div class="search-input"> <div class="search-input">
<div class="input-wrapper"> <div class="input-wrapper">
<Input bind:value={searchTerm} thin placeholder="Search Icon" /> <Input bind:value={searchTerm} thin placeholder="Search Icon" />
</div> </div>
<Button secondary on:click={searchForIcon}>Search</Button> <Button secondary on:click={searchForIcon}>Search</Button>
</div> </div>
<div class="page-area"> <div class="page-area">
<div class="pager"> <div class="pager">
<span on:click={() => pageClick(false)}> <span on:click={() => pageClick(false)}>
<i class="page-btn fas fa-chevron-left" /> <i class="page-btn fas fa-chevron-left" />
</span> </span>
<span>{pagerText}</span> <span>{pagerText}</span>
<span on:click={() => pageClick(true)}> <span on:click={() => pageClick(true)}>
<i class="page-btn fas fa-chevron-right" /> <i class="page-btn fas fa-chevron-right" />
</span> </span>
</div> </div>
</div> </div>
</div> </div>
{#if pagedIcons.length > 0} {#if pagedIcons.length > 0}
<div class="icon-area"> <div class="icon-area">
{#if !loading} {#if !loading}
{#each pagedIcons as icon} {#each pagedIcons as icon}
<div <div
class="icon-container" class="icon-container"
class:selected={value === icon.value} class:selected={value === icon.value}
on:click={() => (value = icon.value)}> on:click={() => (value = icon.value)}>
<div class="icon-preview"> <div class="icon-preview">
<i class={`${icon.value} fa-3x`} /> <i class={`${icon.value} fa-3x`} />
</div> </div>
<div class="icon-label">{icon.label}</div> <div class="icon-label">{icon.label}</div>
</div> </div>
{/each} {/each}
{/if} {/if}
</div> </div>
{:else} {:else}
<div class="no-icons"> <div class="no-icons">
<h5> <h5>
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`} {`There is no icons for this ${searchTerm ? 'search' : 'page'}`}
</h5> </h5>
</div> </div>
{/if} {/if}
</div> </div>
</DropdownMenu> </DropdownMenu>
<style> <style>
.container { .container {
width: 610px; width: 610px;
height: 350px; height: 350px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 10px 0px 10px 15px; padding: 10px 0px 10px 15px;
overflow-x: hidden; overflow-x: hidden;
} }
.search-area { .search-area {
flex: 0 0 80px; flex: 0 0 80px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.icon-area { .icon-area {
flex: 1; flex: 1;
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
grid-gap: 5px; grid-gap: 5px;
justify-content: flex-start; justify-content: flex-start;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding-right: 10px; padding-right: 10px;
} }
.no-icons { .no-icons {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.alphabet-area { .alphabet-area {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
padding-bottom: 10px; padding-bottom: 10px;
padding-right: 15px; padding-right: 15px;
justify-content: space-around; justify-content: space-around;
} }
.loading-container { .loading-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.search-input { .search-input {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
width: 100%; width: 100%;
padding-right: 15px; padding-right: 15px;
} }
.input-wrapper { .input-wrapper {
width: 510px; width: 510px;
margin-right: 5px; margin-right: 5px;
} }
.page-area { .page-area {
padding: 10px; padding: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.letter { .letter {
color: var(--blue); color: var(--blue);
} }
.letter:hover { .letter:hover {
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
.letter-selected { .letter-selected {
text-decoration: underline; text-decoration: underline;
} }
.icon-container { .icon-container {
height: 100px; height: 100px;
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
border: var(--border-dark); border: var(--border-dark);
} }
.icon-container:hover { .icon-container:hover {
cursor: pointer; cursor: pointer;
background: var(--grey-2); background: var(--grey-2);
} }
.selected { .selected {
background: var(--grey-3); background: var(--grey-3);
} }
.icon-preview { .icon-preview {
flex: 1; flex: 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.icon-label { .icon-label {
flex: 0 0 20px; flex: 0 0 20px;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
} }
.page-btn { .page-btn {
color: var(--blue); color: var(--blue);
} }
.page-btn:hover { .page-btn:hover {
cursor: pointer; cursor: pointer;
} }
</style> </style>

50
packages/builder/src/components/userInterface/ItemTab/Item.svelte

@ -1,50 +0,0 @@
<script>
export let item
</script>
<div data-cy={item.name} class="item-item" on:click>
<div class="item-icon"><i class={item.icon} /></div>
<div class="item-text">
<div class="item-name">{item.name}</div>
</div>
</div>
<style>
.item-item {
display: flex;
flex-direction: column;
cursor: pointer;
padding: 12px 16px 16px 16px;
height: 80px;
justify-content: center;
align-items: center;
background-color: var(--grey-1);
border-radius: 5px;
}
.item-item:hover {
background: var(--grey-2);
transition: all 0.3s;
}
.item-icon {
border-radius: 3px;
display: flex;
}
.item-text {
display: flex;
flex-direction: column;
}
.item-name {
font-size: 14px;
font-weight: 400;
text-align: center;
}
i {
font-size: 24px;
color: var(--grey-7);
}
</style>

52
packages/builder/src/components/userInterface/ItemTab/Tab.svelte

@ -1,52 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
import { store } from "builderStore"
import Item from "./Item.svelte"
const dispatch = createEventDispatcher()
export let list
let category = list
const handleClick = item => {
if (item.children && item.children.length > 0) {
list = item
} else {
dispatch("selectItem", item)
}
}
const goBack = () => {
list = category
}
</script>
{#if !list.isCategory}
<button class="back-button" on:click={goBack}>Back</button>
{/if}
{#each list.children as item}
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)}
<Item {item} on:click={() => handleClick(item)} />
{/if}
{/each}
<style>
.back-button {
grid-column: 1 / span 2;
font-size: 14px;
text-align: center;
height: 36px;
border-radius: 5px;
border: solid 1px var(--grey-3);
background: white;
cursor: pointer;
font-weight: 500;
font-family: Inter;
transition: all 0.3ms;
}
.back-button:hover {
background: var(--grey-1);
}
</style>

19
packages/builder/src/components/userInterface/OptionSelect.svelte

@ -13,6 +13,7 @@
let select let select
let selectMenu let selectMenu
let icon let icon
let width = 0
let selectAnchor = null let selectAnchor = null
let dimensions = { top: 0, bottom: 0, left: 0 } let dimensions = { top: 0, bottom: 0, left: 0 }
@ -91,6 +92,7 @@
"transform-origin": `center ${positionSide}`, "transform-origin": `center ${positionSide}`,
[positionSide]: `${dimensions[positionSide]}px`, [positionSide]: `${dimensions[positionSide]}px`,
left: `${dimensions.left.toFixed(0)}px`, left: `${dimensions.left.toFixed(0)}px`,
width: `${width}px`,
}) })
$: isOptionsObject = options.every(o => typeof o === "object") $: isOptionsObject = options.every(o => typeof o === "object")
@ -108,6 +110,7 @@
</script> </script>
<div <div
bind:clientWidth={width}
tabindex="0" tabindex="0"
bind:this={select} bind:this={select}
class="bb-select-container" class="bb-select-container"
@ -164,19 +167,17 @@
.bb-select-container { .bb-select-container {
outline: none; outline: none;
height: 36px;
cursor: pointer; cursor: pointer;
font-size: 14px;
overflow: hidden; overflow: hidden;
flex: 1 1 auto;
} }
.bb-select-anchor { .bb-select-anchor {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
padding: 0px 12px; padding: var(--spacing-s) var(--spacing-m);
height: 36px;
background-color: var(--grey-2); background-color: var(--grey-2);
border-radius: 5px; border-radius: var(--border-radius-m);
align-items: center; align-items: center;
white-space: nowrap; white-space: nowrap;
} }
@ -184,8 +185,11 @@
.bb-select-anchor > span { .bb-select-anchor > span {
color: var(--ink); color: var(--ink);
font-weight: 400; font-weight: 400;
width: 140px;
overflow-x: hidden; overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--font-size-xs);
flex: 1 1 auto;
} }
.bb-select-anchor > i { .bb-select-anchor > i {
@ -208,7 +212,6 @@
box-sizing: border-box; box-sizing: border-box;
flex-direction: column; flex-direction: column;
opacity: 0; opacity: 0;
width: 160px;
z-index: 2; z-index: 2;
color: var(--ink); color: var(--ink);
font-weight: 400; font-weight: 400;
@ -237,7 +240,7 @@
padding: 5px 0px; padding: 5px 0px;
cursor: pointer; cursor: pointer;
padding-left: 10px; padding-left: 10px;
font-size: var(--font-size-s); font-size: var(--font-size-xs);
} }
li:hover { li:hover {

65
packages/builder/src/components/userInterface/PageLayout.svelte

@ -1,21 +1,9 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
// import { tick } from "svelte"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import NavItem from "components/common/NavItem.svelte"
import { import { last } from "lodash/fp"
last,
sortBy,
map,
trimCharsStart,
trimChars,
join,
compose,
} from "lodash/fp"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { pipe } from "components/common/core"
import { store } from "builderStore" import { store } from "builderStore"
import { ArrowDownIcon, GridIcon } from "components/common/Icons/"
import { writable } from "svelte/store" import { writable } from "svelte/store"
export let layout export let layout
@ -24,13 +12,10 @@
let componentToDelete = "" let componentToDelete = ""
const dragDropStore = writable({}) const dragDropStore = writable({})
const joinPath = join("/")
const lastPartOfName = c => const lastPartOfName = c =>
c && last(c.name ? c.name.split("/") : c._component.split("/")) c && last(c.name ? c.name.split("/") : c._component.split("/"))
const isComponentSelected = (current, comp) => current === comp
$: _layout = { $: _layout = {
component: layout, component: layout,
title: lastPartOfName(layout), title: lastPartOfName(layout),
@ -42,18 +27,14 @@
} }
</script> </script>
<div <NavItem
class="budibase__nav-item root" border={false}
class:selected={$store.currentComponentInfo._id === _layout.component.props._id} icon="ri-layout-3-line"
on:click|stopPropagation={setCurrentScreenToLayout}> text="Master Screen"
<span withArrow
class="icon" selected={$store.currentComponentInfo._id === _layout.component.props._id}
class:rotate={$store.currentPreviewItem.name !== _layout.title}> opened={$store.currentPreviewItem.name === _layout.title}
<ArrowDownIcon /> on:click={setCurrentScreenToLayout} />
</span>
<i class="ri-layout-3-fill icon-big" />
<span class="title">Master Screen</span>
</div>
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children} {#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
<ComponentsHierarchyChildren <ComponentsHierarchyChildren
@ -62,29 +43,3 @@
currentComponent={$store.currentComponentInfo} currentComponent={$store.currentComponentInfo}
{dragDropStore} /> {dragDropStore} />
{/if} {/if}
<style>
.title {
margin-left: 10px;
font-size: 14px;
font-weight: 400;
color: var(--ink);
}
.icon {
width: 24px;
display: inline-block;
transition: 0.2s;
width: 20px;
color: var(--grey-7);
}
.icon-big {
font-size: 20px;
color: var(--grey-7);
}
.rotate :global(svg) {
transform: rotate(-90deg);
}
</style>

13
packages/builder/src/components/userInterface/PagesList.svelte

@ -43,24 +43,23 @@
button { button {
cursor: pointer; cursor: pointer;
padding: 0px 16px; padding: 0 var(--spacing-m);
height: 36px; height: 32px;
text-align: center; text-align: center;
background: #ffffff; background: #ffffff;
color: var(--grey-7); color: var(--grey-7);
border-radius: 5px; border-radius: 5px;
font-family: inter; font-size: var(--font-size-xs);
font-size: 14px; font-weight: 500;
font-weight: 400;
transition: all 0.3s; transition: all 0.3s;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
border: none !important; border: none !important;
transition: 0.2s;
outline: none; outline: none;
font-family: var(--font-sans);
} }
.active { .active {
background: var(--grey-3); background: var(--grey-2);
color: var(--ink); color: var(--ink);
} }
</style> </style>

6
packages/builder/src/components/userInterface/PropertyControl.svelte

@ -119,16 +119,14 @@
position: relative; position: relative;
display: flex; display: flex;
flex-flow: row; flex-flow: row;
margin: 8px 0;
align-items: center; align-items: center;
} }
.label { .label {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
flex: 0 0 100px; flex: 0 0 80px;
text-align: left; text-align: left;
color: var(--ink); color: var(--ink);
margin-right: auto; margin-right: auto;
@ -149,7 +147,7 @@
height: 90%; height: 90%;
width: 2rem; width: 2rem;
background: var(--grey-2); background: var(--grey-2);
right: 10px; right: 4px;
--spacing-s: 0; --spacing-s: 0;
border-left: 0.5px solid var(--grey-3); border-left: 0.5px solid var(--grey-3);
outline-color: var(--blue); outline-color: var(--blue);

32
packages/builder/src/components/userInterface/PropertyGroup.svelte

@ -13,14 +13,26 @@
$: style = componentInstance["_styles"][styleCategory] || {} $: style = componentInstance["_styles"][styleCategory] || {}
</script> </script>
<DetailSummary {name} on:open show={open}> <DetailSummary {name} on:open show={open} thin>
{#each properties as props} <div>
<PropertyControl {#each properties as props}
label={props.label} <PropertyControl
control={props.control} label={props.label}
key={props.key} control={props.control}
value={style[props.key]} key={props.key}
onChange={(key, value) => onStyleChanged(styleCategory, key, value)} value={style[props.key]}
props={{ ...excludeProps(props, ['control', 'label']) }} /> onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
{/each} props={{ ...excludeProps(props, ['control', 'label']) }} />
{/each}
</div>
</DetailSummary> </DetailSummary>
<style>
div {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-s);
}
</style>

2
packages/builder/src/components/userInterface/PropertyPanelControls/Input.svelte

@ -8,4 +8,4 @@
export let name, value, placeholder, type export let name, value, placeholder, type
</script> </script>
<Input {name} {value} {placeholder} {type} thin on:change /> <Input {name} {value} {placeholder} {type} extraThin on:change />

75
packages/builder/src/components/userInterface/ScreenDropdownMenu.svelte

@ -2,9 +2,8 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import api from "builderStore/api"
import Portal from "svelte-portal"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
export let screen export let screen
@ -12,10 +11,6 @@
let dropdown let dropdown
let anchor let anchor
const hideDropdown = () => {
dropdown.hide()
}
const deleteScreen = () => { const deleteScreen = () => {
store.deleteScreens(screen, $store.currentPageName) store.deleteScreens(screen, $store.currentPageName)
// update the page if required // update the page if required
@ -29,79 +24,27 @@
} }
</script> </script>
<div <div bind:this={anchor} on:click|stopPropagation>
bind:this={anchor}
class="root boundary"
on:click|stopPropagation={() => {}}>
<div class="icon" on:click={() => dropdown.show()}> <div class="icon" on:click={() => dropdown.show()}>
<i class="ri-more-line" /> <i class="ri-more-line" />
</div> </div>
<DropdownMenu bind:this={dropdown} {anchor} align="left"> <DropdownMenu bind:this={dropdown} {anchor} align="left">
<ul on:click={hideDropdown}> <DropdownContainer>
<li on:click={() => confirmDeleteDialog.show()}> <DropdownItem
<i class="ri-delete-bin-2-line" /> icon="ri-delete-bin-line"
Delete title="Delete"
</li> on:click={() => confirmDeleteDialog.show()} />
</ul> </DropdownContainer>
</DropdownMenu> </DropdownMenu>
</div> </div>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
title="Confirm Delete" title="Confirm Deletion"
body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`} body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`}
okText="Delete Screen" okText="Delete Screen"
onOk={deleteScreen} /> onOk={deleteScreen} />
<style> <style>
.root {
overflow: hidden;
z-index: 9;
}
.root button {
border-style: none;
border-radius: 2px;
padding: 0;
background: transparent;
cursor: pointer;
color: var(--ink);
outline: none;
}
ul {
z-index: 100000;
overflow: visible;
margin: var(--spacing-s) 0;
border-radius: var(--border-radius-s);
padding: 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0;
align-items: center;
cursor: pointer;
}
li:not(.disabled):hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
li i {
margin-right: 8px;
font-size: var(--font-size-s);
}
li.disabled {
color: var(--grey-4);
cursor: default;
}
.icon i { .icon i {
font-size: 16px; font-size: 16px;
} }

182
packages/builder/src/components/userInterface/ScreenSelect.svelte

@ -1,88 +1,94 @@
<script> <script>
import { DataList } from "@budibase/bbui" import { DataList } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import fetchBindableProperties from "builderStore/fetchBindableProperties" import fetchBindableProperties from "builderStore/fetchBindableProperties"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value = "" export let value = ""
$: urls = getUrls() $: urls = getUrls()
const handleBlur = () => dispatch("change", value) const handleBlur = () => dispatch("change", value)
// this will get urls of all screens, but only // this will get urls of all screens, but only
// choose detail screens that are usable in the current context // choose detail screens that are usable in the current context
// and substitute the :id param for the actual {{ ._id }} binding // and substitute the :id param for the actual {{ ._id }} binding
const getUrls = () => { const getUrls = () => {
const urls = [ const urls = [
...$store.screens ...$store.screens
.filter(screen => !screen.props._component.endsWith("/rowdetail")) .filter(screen => !screen.props._component.endsWith("/rowdetail"))
.map(screen => ({ .map(screen => ({
name: screen.props._instanceName, name: screen.props._instanceName,
url: screen.route, url: screen.route,
sort: screen.props._component, sort: screen.props._component,
})), })),
] ]
const bindableProperties = fetchBindableProperties({ const bindableProperties = fetchBindableProperties({
componentInstanceId: $store.currentComponentInfo._id, componentInstanceId: $store.currentComponentInfo._id,
components: $store.components, components: $store.components,
screen: $store.currentPreviewItem, screen: $store.currentPreviewItem,
tables: $backendUiStore.tables, tables: $backendUiStore.tables,
}) })
const detailScreens = $store.screens.filter(screen => const detailScreens = $store.screens.filter(screen =>
screen.props._component.endsWith("/rowdetail") screen.props._component.endsWith("/rowdetail")
) )
for (let detailScreen of detailScreens) { for (let detailScreen of detailScreens) {
const idBinding = bindableProperties.find(p => { const idBinding = bindableProperties.find(p => {
if ( if (
p.type === "context" && p.type === "context" &&
p.runtimeBinding.endsWith("._id") && p.runtimeBinding.endsWith("._id") &&
p.table p.table
) { ) {
const tableId = const tableId =
typeof p.table === "string" ? p.table : p.table.tableId typeof p.table === "string" ? p.table : p.table.tableId
return tableId === detailScreen.props.table return tableId === detailScreen.props.table
} }
return false return false
}) })
if (idBinding) { if (idBinding) {
urls.push({ urls.push({
name: detailScreen.props._instanceName, name: detailScreen.props._instanceName,
url: detailScreen.route.replace( url: detailScreen.route.replace(
":id", ":id",
`{{ ${idBinding.runtimeBinding} }}` `{{ ${idBinding.runtimeBinding} }}`
), ),
sort: detailScreen.props._component, sort: detailScreen.props._component,
}) })
} }
} }
return urls return urls
} }
</script> </script>
<div> <div>
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value> <DataList
<option value="" /> editable
{#each urls as url} secondary
<option value={url.url}>{url.name}</option> extraThin
{/each} on:blur={handleBlur}
</DataList> on:change
</div> bind:value>
<option value="" />
<style> {#each urls as url}
div { <option value={url.url}>{url.name}</option>
flex: 1 1 auto; {/each}
display: flex; </DataList>
flex-direction: row; </div>
}
div :global(> div) { <style>
flex: 1 1 auto; div {
} flex: 1 1 auto;
</style> display: flex;
flex-direction: row;
}
div :global(> div) {
flex: 1 1 auto;
}
</style>

94
packages/builder/src/components/userInterface/SettingsView.svelte

@ -85,53 +85,65 @@
} }
</script> </script>
{#if screenOrPageInstance} <div class="settings-view-container">
{#each screenOrPageDefinition as def} {#if screenOrPageInstance}
<PropertyControl {#each screenOrPageDefinition as def}
bindable={false} <PropertyControl
control={def.control} bindable={false}
label={def.label} control={def.control}
key={def.key} label={def.label}
value={screenOrPageInstance[def.key]} key={def.key}
onChange={onScreenPropChange} value={screenOrPageInstance[def.key]}
props={{ ...excludeProps(def, ['control', 'label']) }} /> onChange={onScreenPropChange}
{/each} props={{ ...excludeProps(def, ['control', 'label']) }} />
{/if} {/each}
{#if displayNameField}
<PropertyControl
control={Input}
label="Name"
key="_instanceName"
value={componentInstance._instanceName}
onChange={onInstanceNameChange} />
{#if duplicateName}
<span class="duplicate-name">Name must be unique</span>
{/if} {/if}
{/if}
{#if panelDefinition && panelDefinition.length > 0} {#if displayNameField}
{#each panelDefinition as definition} <PropertyControl
{#if canRenderControl(definition.key, definition.dependsOn)} control={Input}
<PropertyControl label="Name"
control={definition.control} key="_instanceName"
label={definition.label} value={componentInstance._instanceName}
key={definition.key} onChange={onInstanceNameChange} />
value={componentInstance[definition.key] || componentInstance[definition.key]?.defaultValue} {#if duplicateName}
{componentInstance} <span class="duplicate-name">Name must be unique</span>
{onChange}
props={{ ...excludeProps(definition, ['control', 'label']) }} />
{/if} {/if}
{/each} {/if}
{:else}
<div>This component does not have any settings.</div> {#if panelDefinition && panelDefinition.length > 0}
{/if} {#each panelDefinition as definition}
{#if canRenderControl(definition.key, definition.dependsOn)}
<PropertyControl
control={definition.control}
label={definition.label}
key={definition.key}
value={componentInstance[definition.key] || componentInstance[definition.key]?.defaultValue}
{componentInstance}
{onChange}
props={{ ...excludeProps(definition, ['control', 'label']) }} />
{/if}
{/each}
{:else}
<div class="empty">
This component doesn't have any additional settings.
</div>
{/if}
</div>
<style> <style>
div { .settings-view-container {
font-size: var(--font-size-s); display: flex;
flex-direction: column;
width: 100%;
height: 100%;
gap: var(--spacing-s);
}
.empty {
font-size: var(--font-size-xs);
margin-top: var(--spacing-m); margin-top: var(--spacing-m);
color: var(--grey-6); color: var(--grey-5);
} }
.duplicate-name { .duplicate-name {

2
packages/builder/src/components/userInterface/TableSelect.svelte

@ -6,7 +6,7 @@
</script> </script>
<div> <div>
<Select thin secondary wide on:change {value}> <Select extraThin secondary wide on:change {value}>
<option value="">Choose a table</option> <option value="">Choose a table</option>
{#each $backendUiStore.tables as table} {#each $backendUiStore.tables as table}
<option value={table._id}>{table.name}</option> <option value={table._id}>{table.name}</option>

326
packages/builder/src/components/userInterface/TableViewSelect.svelte

@ -1,163 +1,163 @@
<script> <script>
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui" import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import fetchBindableProperties from "../../builderStore/fetchBindableProperties" import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let anchorRight, dropdownRight let anchorRight, dropdownRight
export let value = {} export let value = {}
function handleSelected(selected) { function handleSelected(selected) {
dispatch("change", selected) dispatch("change", selected)
dropdownRight.hide() dropdownRight.hide()
} }
$: tables = $backendUiStore.tables.map(m => ({ $: tables = $backendUiStore.tables.map(m => ({
label: m.name, label: m.name,
name: `all_${m._id}`, name: `all_${m._id}`,
tableId: m._id, tableId: m._id,
type: "table", type: "table",
})) }))
$: views = $backendUiStore.tables.reduce((acc, cur) => { $: views = $backendUiStore.tables.reduce((acc, cur) => {
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({ let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
label: key, label: key,
name: key, name: key,
...value, ...value,
type: "view", type: "view",
})) }))
return [...acc, ...viewsArr] return [...acc, ...viewsArr]
}, []) }, [])
$: bindableProperties = fetchBindableProperties({ $: bindableProperties = fetchBindableProperties({
componentInstanceId: $store.currentComponentInfo._id, componentInstanceId: $store.currentComponentInfo._id,
components: $store.components, components: $store.components,
screen: $store.currentPreviewItem, screen: $store.currentPreviewItem,
tables: $backendUiStore.tables, tables: $backendUiStore.tables,
}) })
$: links = bindableProperties $: links = bindableProperties
.filter(x => x.fieldSchema.type === "link") .filter(x => x.fieldSchema?.type === "link")
.map(property => ({ .map(property => ({
label: property.readableBinding, label: property.readableBinding,
fieldName: property.fieldSchema.name, fieldName: property.fieldSchema.name,
name: `all_${property.fieldSchema.tableId}`, name: `all_${property.fieldSchema.tableId}`,
tableId: property.fieldSchema.tableId, tableId: property.fieldSchema.tableId,
type: "link", type: "link",
})) }))
</script> </script>
<div <div
class="dropdownbutton" class="dropdownbutton"
bind:this={anchorRight} bind:this={anchorRight}
on:click={dropdownRight.show}> on:click={dropdownRight.show}>
<span>{value.label ? value.label : 'Table / View'}</span> <span>{value.label ? value.label : 'Table / View'}</span>
<Icon name="arrowdown" /> <Icon name="arrowdown" />
</div> </div>
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}> <DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
<div class="dropdown"> <div class="dropdown">
<div class="title"> <div class="title">
<Heading extraSmall>Tables</Heading> <Heading extraSmall>Tables</Heading>
</div> </div>
<ul> <ul>
{#each tables as table} {#each tables as table}
<li <li
class:selected={value === table} class:selected={value === table}
on:click={() => handleSelected(table)}> on:click={() => handleSelected(table)}>
{table.label} {table.label}
</li> </li>
{/each} {/each}
</ul> </ul>
<hr /> <hr />
<div class="title"> <div class="title">
<Heading extraSmall>Views</Heading> <Heading extraSmall>Views</Heading>
</div> </div>
<ul> <ul>
{#each views as view} {#each views as view}
<li <li
class:selected={value === view} class:selected={value === view}
on:click={() => handleSelected(view)}> on:click={() => handleSelected(view)}>
{view.label} {view.label}
</li> </li>
{/each} {/each}
</ul> </ul>
<hr /> <hr />
<div class="title"> <div class="title">
<Heading extraSmall>Relationships</Heading> <Heading extraSmall>Relationships</Heading>
</div> </div>
<ul> <ul>
{#each links as link} {#each links as link}
<li <li
class:selected={value === link} class:selected={value === link}
on:click={() => handleSelected(link)}> on:click={() => handleSelected(link)}>
{link.label} {link.label}
</li> </li>
{/each} {/each}
</ul> </ul>
</div> </div>
</DropdownMenu> </DropdownMenu>
<style> <style>
.dropdownbutton { .dropdownbutton {
background-color: var(--grey-2); background-color: var(--grey-2);
border: var(--border-transparent); border: var(--border-transparent);
padding: var(--spacing-m); padding: var(--spacing-s) var(--spacing-m);
border-radius: var(--border-radius-m); border-radius: var(--border-radius-m);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
flex: 1 1 auto; flex: 1 1 auto;
} }
.dropdownbutton:hover { .dropdownbutton:hover {
cursor: pointer; cursor: pointer;
background-color: var(--grey-3); background-color: var(--grey-3);
} }
.dropdownbutton span { .dropdownbutton span {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
flex: 1 1 auto; flex: 1 1 auto;
text-align: left; text-align: left;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
} }
.dropdownbutton :global(svg) { .dropdownbutton :global(svg) {
margin: -4px 0; margin: -4px 0;
} }
.dropdown { .dropdown {
padding: var(--spacing-m) 0; padding: var(--spacing-m) 0;
z-index: 99999999; z-index: 99999999;
} }
.title { .title {
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m); padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
} }
hr { hr {
margin: var(--spacing-m) 0 var(--spacing-xl) 0; margin: var(--spacing-m) 0 var(--spacing-xl) 0;
} }
ul { ul {
list-style: none; list-style: none;
padding-left: 0px; padding-left: 0px;
margin: 0px; margin: 0px;
} }
li { li {
cursor: pointer; cursor: pointer;
margin: 0px; margin: 0px;
padding: var(--spacing-s) var(--spacing-m); padding: var(--spacing-s) var(--spacing-m);
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
} }
.selected { .selected {
background-color: var(--grey-4); background-color: var(--grey-4);
} }
li:hover { li:hover {
background-color: var(--grey-4); background-color: var(--grey-4);
} }
</style> </style>

193
packages/builder/src/components/userInterface/UserInterfaceRoot.svelte

@ -1,193 +0,0 @@
<script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import PageLayout from "./PageLayout.svelte"
import PagesList from "./PagesList.svelte"
import { store } from "builderStore"
import NewScreenModal from "./NewScreenModal.svelte"
import CurrentItemPreview from "./AppPreview/CurrentItemPreview.svelte"
import SettingsView from "./SettingsView.svelte"
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp"
import { AddIcon } from "components/common/Icons"
import { Modal } from "@budibase/bbui"
let newScreenPicker
let confirmDeleteDialog
let componentToDelete = ""
let settingsView
let modal
const settings = () => {
settingsView.show()
}
const lastPartOfName = c => (c ? last(c.split("/")) : "")
</script>
<div class="root">
<div class="ui-nav">
<div class="pages-list-container">
<div class="nav-header">
<span class="navigator-title">Navigator</span>
<span class="components-nav-page">Pages</span>
</div>
<div class="nav-items-container">
<PagesList />
</div>
</div>
<PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="components-list-container">
<div class="nav-group-header">
<span class="components-nav-header" style="margin-top: 0;">
Screens
</span>
<div>
<button on:click={modal.show}>
<AddIcon />
</button>
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy screens={$store.screens} />
</div>
</div>
</div>
<div class="preview-pane">
<CurrentItemPreview />
</div>
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
<div class="components-pane">
<ComponentsPaneSwitcher />
</div>
{/if}
</div>
<Modal bind:this={modal}>
<NewScreenModal />
</Modal>
<SettingsView bind:this={settingsView} />
<style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
width: 20px;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.root {
display: grid;
grid-template-columns: 300px 1fr 300px;
height: 100%;
width: 100%;
background: var(--grey-1);
}
.ui-nav {
grid-column: 1;
background-color: var(--white);
height: calc(100vh - 49px);
padding: 0;
display: flex;
flex-direction: column;
}
.preview-pane {
grid-column: 2;
margin: 40px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
}
.components-pane {
grid-column: 3;
background-color: var(--white);
height: calc(100vh - 49px);
}
.components-nav-page {
font-size: 13px;
color: var(--ink);
text-transform: uppercase;
padding-left: 20px;
margin-top: 20px;
font-weight: 600;
opacity: 0.4;
}
.components-nav-header {
font-size: 13px;
color: var(--ink);
text-transform: uppercase;
margin-top: 20px;
font-weight: 600;
opacity: 0.4;
}
.nav-header {
display: flex;
flex-direction: column;
margin-top: 20px;
}
.nav-items-container {
padding: 1rem 0rem 0rem 0rem;
}
.nav-group-header {
display: flex;
padding: 0px 20px 0px 20px;
font-size: 0.9rem;
font-weight: bold;
justify-content: space-between;
align-items: center;
}
.nav-group-header > div:nth-child(1) {
padding: 0rem 0.5rem 0rem 0rem;
vertical-align: bottom;
grid-column-start: icon;
margin-right: 5px;
}
.nav-group-header > span:nth-child(3) {
margin-left: 5px;
vertical-align: bottom;
grid-column-start: title;
margin-top: auto;
}
.nav-group-header > div:nth-child(3) {
vertical-align: bottom;
grid-column-start: button;
cursor: pointer;
color: var(--blue);
}
.nav-group-header > div:nth-child(3):hover {
color: var(--blue);
}
.navigator-title {
font-size: 14px;
color: var(--ink);
font-weight: 600;
text-transform: uppercase;
padding: 0 20px 20px 20px;
line-height: 1rem !important;
}
.components-list-container {
padding: 10px 0px 0 0;
}
</style>

2
packages/builder/src/components/userInterface/pagesParsing/searchComponents.js

@ -1,5 +1,5 @@
import { isUndefined, filter, some, includes } from "lodash/fp" import { isUndefined, filter, some, includes } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "../../../helpers"
const normalString = s => (s || "").trim().toLowerCase() const normalString = s => (s || "").trim().toLowerCase()

2
packages/builder/src/components/userInterface/pagesParsing/splitRootComponentName.js

@ -1,5 +1,5 @@
import { split, last } from "lodash/fp" import { split, last } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "../../../helpers"
export const splitName = fullname => { export const splitName = fullname => {
const componentName = pipe(fullname, [split("/"), last]) const componentName = pipe(fullname, [split("/"), last])

1833
packages/builder/src/components/userInterface/temporaryPanelStructure.js

File diff suppressed because it is too large

6
packages/builder/src/helpers.js

@ -1,5 +1,5 @@
import { last } from "lodash/fp" import { last, flow } from "lodash/fp"
import { pipe } from "components/common/core"
export const buildStyle = styles => { export const buildStyle = styles => {
let str = "" let str = ""
for (let s in styles) { for (let s in styles) {
@ -15,6 +15,8 @@ export const convertCamel = str => {
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
} }
export const pipe = (arg, funcs) => flow(funcs)(arg)
export const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1) export const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
export const get_name = s => (!s ? "" : last(s.split("/"))) export const get_name = s => (!s ? "" : last(s.split("/")))

2
packages/builder/src/index.html

@ -15,7 +15,7 @@
</head> </head>
<body id="app"> <body id="app">
<script src='/_builder/bundle.js'></script> <script src='/_builder/bundle.js'></script>
</body> </body>

52
packages/builder/src/pages/[application]/_reset.svelte

@ -67,23 +67,27 @@
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<FeedbackNavLink /> <FeedbackNavLink />
<div class="topnavitemright">
<a target="_blank" href="https://docs.budibase.com">
<i class="ri-question-line" />
</a>
</div>
<div class="topnavitemright">
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions">
<i class="ri-discuss-line" />
</a>
</div>
<SettingsLink /> <SettingsLink />
<span <Button
class:active={false} secondary
class="topnavitemright"
on:click={() => { on:click={() => {
document.cookie = 'budibase:token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' document.cookie = 'budibase:token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'
window.open(`/${application}`) window.open(`/${application}`)
}}> }}>
<PreviewIcon /> Preview
</span> </Button>
<span class="topnavitemright">
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions">
<i class="ri-question-fill help" />
</a>
</span>
</div> </div>
</div> </div>
<div class="beta"> <div class="beta">
@ -122,7 +126,7 @@
flex: 0 0 auto; flex: 0 0 auto;
height: 60px; height: 60px;
background: #fff; background: #fff;
padding: 0px 20px 0 20px; padding: 0 20px;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
justify-content: space-between; justify-content: space-between;
@ -168,20 +172,19 @@
font-weight: 500; font-weight: 500;
} }
.topnavitemright { .topnavitemright a {
cursor: pointer; cursor: pointer;
color: var(--grey-7); color: var(--grey-7);
margin: 0 20px 0 0; margin: 0 12px 0 0;
font-weight: 500;
font-size: 1rem;
height: 100%;
display: flex; display: flex;
flex: 1; flex-direction: row;
justify-content: center;
align-items: center; align-items: center;
box-sizing: border-box; height: 24px;
width: 24px;
} }
.topnavitemright:hover { .topnavitemright a:hover {
color: var(--ink); color: var(--ink);
font-weight: 500; font-weight: 500;
} }
@ -207,10 +210,13 @@
text-transform: capitalize; text-transform: capitalize;
} }
.help { i {
font-size: 24px; font-size: 18px;
color: var(--grey-7); color: var(--grey-7);
} }
i:hover {
color: var(--ink);
}
.beta { .beta {
position: absolute; position: absolute;

32
packages/builder/src/pages/[application]/automate/_layout.svelte

@ -1,6 +1,7 @@
<script> <script>
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { AutomationPanel, SetupPanel } from "components/automation" import AutomationPanel from "components/automation/AutomationPanel/AutomationPanel.svelte"
import SetupPanel from "components/automation/SetupPanel/SetupPanel.svelte"
</script> </script>
<!-- routify:options index=3 --> <!-- routify:options index=3 -->
@ -12,28 +13,39 @@
<slot /> <slot />
</div> </div>
{#if $automationStore.selectedAutomation} {#if $automationStore.selectedAutomation}
<div class="nav"> <div class="nav setup">
<SetupPanel /> <SetupPanel />
</div> </div>
{/if} {/if}
</div> </div>
<style> <style>
.content {
position: relative;
}
.root { .root {
height: calc(100% - 60px); height: calc(100% - 60px);
display: grid; display: grid;
grid-template-columns: 300px minmax(510px, 1fr) 300px; grid-template-columns: 260px minmax(510px, 1fr) 260px;
background: var(--grey-1); background: var(--grey-2);
line-height: 1;
} }
.nav { .nav {
overflow-y: auto; overflow-y: auto;
background: var(--white); background: var(--white);
padding: var(--spacing-xl) var(--spacing-xl); padding: var(--spacing-l) var(--spacing-xl);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
}
.content {
position: relative;
padding: var(--spacing-l) 40px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
overflow: hidden;
} }
</style> </style>

2
packages/builder/src/pages/[application]/automate/index.svelte

@ -1,5 +1,5 @@
<script> <script>
import { AutomationBuilder } from "components/automation" import AutomationBuilder from "components/automation/AutomationBuilder/AutomationBuilder.svelte"
</script> </script>
<AutomationBuilder /> <AutomationBuilder />

21
packages/builder/src/pages/[application]/data/_layout.svelte

@ -1,6 +1,4 @@
<script> <script>
import { store, backendUiStore } from "builderStore"
import * as api from "components/backend/DataTable/api"
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte" import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
</script> </script>
@ -18,18 +16,27 @@
.root { .root {
height: calc(100vh - 60px); height: calc(100vh - 60px);
display: grid; display: grid;
grid-template-columns: 300px minmax(0, 1fr); grid-template-columns: 260px minmax(0, 1fr);
background: var(--grey-1); background: var(--grey-2);
line-height: 1;
} }
.content { .content {
flex: 1 1 auto; flex: 1 1 auto;
padding: var(--spacing-xl) 40px; padding: var(--spacing-l) 40px;
overflow-y: auto; overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
} }
.nav { .nav {
overflow-y: auto; overflow-y: auto;
background: var(--white); background: var(--white);
padding: var(--spacing-xl); padding: var(--spacing-l) var(--spacing-xl);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
} }
</style> </style>

5
packages/builder/src/pages/[application]/data/table/[selectedTable]/index.svelte

@ -11,7 +11,8 @@
<style> <style>
i { i {
font-size: var(--font-size-xl); font-size: var(--font-size-m);
color: var(--grey-4); color: var(--grey-5);
margin-top: 2px;
} }
</style> </style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save