Merge branch 'master' of github.com:Budibase/budibase into feature/remove-multitenancy
This commit is contained in:
commit
ccc8f3c13e
|
@ -12,8 +12,6 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna
|
|||
|
||||
- **packages/client** - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it.
|
||||
|
||||
- **packages/cli** - The budibase CLI. This is the main module that gets downloaded from NPM and is responsible for creating and managing apps, as well as running the budibase server.
|
||||
|
||||
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||
|
||||
|
||||
|
@ -95,14 +93,9 @@ then `cd ` into your local copy.
|
|||
|
||||
### 4. Initialising Budibase and Creating a Budibase App
|
||||
|
||||
`yarn initialise` will initialise your budibase installation. A Budibase apps folder will have been created in `~/.budibase`. You can also just start up the budibase electron app and it should initialise budibase for you.
|
||||
Starting up the budibase electron app should initialise budibase for you. A Budibase apps folder will have been created in `~/.budibase`.
|
||||
|
||||
This is a blank apps folder, so you will need to create yourself an app.
|
||||
|
||||
```
|
||||
cd packages/server
|
||||
yarn run budi new your-app-name
|
||||
```
|
||||
This is a blank apps folder, so you will need to create yourself an app, you can do this by clicking "Create New App" from the budibase builder.
|
||||
|
||||
This will create a new budibase application in the `~/.budibase/<your-app-uuid>` directory, and NPM install the component libraries for that application. Let's start building your app with the budibase builder!
|
||||
|
||||
|
@ -116,16 +109,6 @@ To run the budibase server and builder in dev mode (i.e. with live reloading):
|
|||
|
||||
This will enable watch mode for both the builder app, server, client library and any component libraries.
|
||||
|
||||
### Running Commands from /server Directory
|
||||
|
||||
Notice that when inside `packages/server`, you can use any Budibase CLI command via yarn:
|
||||
|
||||
e.g. `yarn budi new mikes_app` == `budi new mikes_app`
|
||||
|
||||
This will use the CLI directly from `packages/cli`, rather than your globally installed `budi`
|
||||
|
||||
|
||||
|
||||
## Data Storage
|
||||
|
||||
When you are running locally, budibase stores data on disk using [PouchDB](https://pouchdb.com/), as well as some JSON on local files. After setting up budibase, you can find all of this data in the `~/.budibase` directory.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.2.6",
|
||||
"version": "0.3.0",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -14,35 +14,26 @@ context("Create a automation", () => {
|
|||
cy.createTestTableWithData()
|
||||
|
||||
cy.contains("automate").click()
|
||||
cy.contains("Create New Automation").click()
|
||||
cy.get("[data-cy=new-automation]").click()
|
||||
cy.get(".modal").within(() => {
|
||||
cy.get("input").type("Add Row")
|
||||
cy.get(".buttons")
|
||||
.contains("Create")
|
||||
.click()
|
||||
cy.get(".buttons").contains("Create").click()
|
||||
})
|
||||
|
||||
// Add trigger
|
||||
cy.get("[data-cy=add-automation-component]").click()
|
||||
cy.get("[data-cy=ROW_SAVED]").click()
|
||||
cy.get("[data-cy=automation-block-setup]").within(() => {
|
||||
cy.get("select")
|
||||
.first()
|
||||
.select("dog")
|
||||
cy.contains("Trigger").click()
|
||||
cy.contains("Row Saved").click()
|
||||
cy.get(".setup").within(() => {
|
||||
cy.get("select").first().select("dog")
|
||||
})
|
||||
|
||||
// Create action
|
||||
cy.get("[data-cy=CREATE_ROW]").click()
|
||||
cy.get("[data-cy=automation-block-setup]").within(() => {
|
||||
cy.get("select")
|
||||
.first()
|
||||
.select("dog")
|
||||
cy.get("input")
|
||||
.first()
|
||||
.type("goodboy")
|
||||
cy.get("input")
|
||||
.eq(1)
|
||||
.type("11")
|
||||
cy.contains("Action").click()
|
||||
cy.contains("Create Row").click()
|
||||
cy.get(".setup").within(() => {
|
||||
cy.get("select").first().select("dog")
|
||||
cy.get("input").first().type("goodboy")
|
||||
cy.get("input").eq(1).type("11")
|
||||
})
|
||||
|
||||
// Save
|
||||
|
@ -50,7 +41,7 @@ context("Create a automation", () => {
|
|||
|
||||
// Activate Automation
|
||||
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", () => {
|
||||
|
|
|
@ -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(() => {
|
||||
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')
|
||||
})
|
||||
// https://on.cypress.io/interacting-with-elements
|
||||
it("should add a container", () => {
|
||||
cy.navigateToFrontend()
|
||||
cy.get(".switcher > :nth-child(2)").click()
|
||||
cy.contains("Container").click()
|
||||
})
|
||||
it("should add a headline", () => {
|
||||
cy.addHeadlineComponent("An Amazing headline!")
|
||||
|
||||
// https://on.cypress.io/interacting-with-elements
|
||||
it('should add a container', () => {
|
||||
cy.contains('frontend').click()
|
||||
cy.get('.switcher > :nth-child(2)').click()
|
||||
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()
|
||||
|
||||
cy.contains('Container').click()
|
||||
})
|
||||
it('should add a headline', () => {
|
||||
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')
|
||||
})
|
||||
getIframeBody()
|
||||
.contains("An Amazing headline!")
|
||||
.should("have.css", "font-size", "60px")
|
||||
})
|
||||
})
|
||||
|
||||
const getIframeDocument = () => {
|
||||
return cy
|
||||
.get('iframe')
|
||||
// Cypress yields jQuery element, which has the real
|
||||
// DOM element under property "0".
|
||||
// From the real DOM iframe element we can get
|
||||
// the "document" element, it is stored in "contentDocument" property
|
||||
// Cypress "its" command can access deep properties using dot notation
|
||||
// https://on.cypress.io/its
|
||||
.its('0.contentDocument').should('exist')
|
||||
return (
|
||||
cy
|
||||
.get("iframe")
|
||||
// Cypress yields jQuery element, which has the real
|
||||
// DOM element under property "0".
|
||||
// From the real DOM iframe element we can get
|
||||
// the "document" element, it is stored in "contentDocument" property
|
||||
// Cypress "its" command can access deep properties using dot notation
|
||||
// https://on.cypress.io/its
|
||||
.its("0.contentDocument")
|
||||
.should("exist")
|
||||
)
|
||||
}
|
||||
|
||||
const getIframeBody = () => {
|
||||
// get the document
|
||||
return getIframeDocument()
|
||||
// automatically retries until body is loaded
|
||||
.its('body').should('not.be.undefined')
|
||||
// wraps "body" DOM element to allow
|
||||
// chaining more Cypress commands, like ".find(...)"
|
||||
.then(cy.wrap)
|
||||
}
|
||||
// get the document
|
||||
return (
|
||||
getIframeDocument()
|
||||
// automatically retries until body is loaded
|
||||
.its("body")
|
||||
.should("not.be.undefined")
|
||||
// wraps "body" DOM element to allow
|
||||
// chaining more Cypress commands, like ".find(...)"
|
||||
.then(cy.wrap)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ context("Create a Table", () => {
|
|||
cy.createTable("dog")
|
||||
|
||||
// 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", () => {
|
||||
|
@ -26,9 +26,7 @@ context("Create a Table", () => {
|
|||
.trigger("mouseover")
|
||||
.find(".ri-pencil-line")
|
||||
.click({ force: true })
|
||||
cy.get(".actions input")
|
||||
.first()
|
||||
.type("updated")
|
||||
cy.get(".actions input").first().type("updated")
|
||||
// Unset table display column
|
||||
cy.contains("display column").click()
|
||||
cy.contains("Save Column").click()
|
||||
|
@ -36,7 +34,8 @@ context("Create a Table", () => {
|
|||
})
|
||||
|
||||
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.contains("Save").click()
|
||||
cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
|
||||
|
@ -50,10 +49,10 @@ context("Create a Table", () => {
|
|||
})
|
||||
|
||||
it("deletes a column", () => {
|
||||
cy
|
||||
.contains("header", "name")
|
||||
.trigger("mouseover")
|
||||
.find(".ri-pencil-line").click({ force: true })
|
||||
cy.contains("header", "name")
|
||||
.trigger("mouseover")
|
||||
.find(".ri-pencil-line")
|
||||
.click({ force: true })
|
||||
cy.contains("Delete").click()
|
||||
cy.wait(50)
|
||||
cy.get(".buttons").contains("Delete").click()
|
||||
|
@ -61,9 +60,7 @@ context("Create a Table", () => {
|
|||
})
|
||||
|
||||
it("deletes a table", () => {
|
||||
cy.contains("div", "dog")
|
||||
.get(".ri-more-line")
|
||||
.click()
|
||||
cy.contains(".nav-item", "dog").get(".actions").invoke("show").click()
|
||||
cy.get("[data-cy=delete-table]").click()
|
||||
cy.contains("Delete Table").click()
|
||||
cy.contains("dog").should("not.exist")
|
||||
|
|
|
@ -22,10 +22,12 @@ context("Create a View", () => {
|
|||
cy.get("input").type("Test View")
|
||||
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 => {
|
||||
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"])
|
||||
})
|
||||
})
|
||||
|
@ -33,14 +35,8 @@ context("Create a View", () => {
|
|||
it("filters the view by age over 10", () => {
|
||||
cy.contains("Filter").click()
|
||||
cy.contains("Add Filter").click()
|
||||
cy.get(".menu-container")
|
||||
.find("select")
|
||||
.first()
|
||||
.select("age")
|
||||
cy.get(".menu-container")
|
||||
.find("select")
|
||||
.eq(1)
|
||||
.select("More Than")
|
||||
cy.get(".menu-container").find("select").first().select("age")
|
||||
cy.get(".menu-container").find("select").eq(1).select("More Than")
|
||||
cy.get(".menu-container").find("input").type(18)
|
||||
cy.contains("Save").click()
|
||||
cy.get("[role=rowgroup] .ag-row").get($values => {
|
||||
|
@ -53,20 +49,16 @@ context("Create a View", () => {
|
|||
cy.viewport("macbook-15")
|
||||
|
||||
cy.contains("Calculate").click()
|
||||
cy.get(".menu-container")
|
||||
.find("select")
|
||||
.eq(0)
|
||||
.select("Statistics")
|
||||
cy.get(".menu-container").find("select").eq(0).select("Statistics")
|
||||
cy.wait(50)
|
||||
cy.get(".menu-container")
|
||||
.find("select")
|
||||
.eq(1)
|
||||
.select("age")
|
||||
cy.get(".menu-container").find("select").eq(1).select("age")
|
||||
cy.contains("Save").click()
|
||||
cy.get(".ag-center-cols-viewport").scrollTo("100%")
|
||||
cy.get("[data-cy=table-header]").then($headers => {
|
||||
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([
|
||||
"field",
|
||||
"sum",
|
||||
|
@ -78,16 +70,10 @@ context("Create a View", () => {
|
|||
])
|
||||
})
|
||||
cy.get(".ag-cell").then($values => {
|
||||
const values = Array.from($values).map(header => header.textContent.trim())
|
||||
expect(values).to.deep.eq([
|
||||
"age",
|
||||
"155",
|
||||
"20",
|
||||
"49",
|
||||
"5",
|
||||
"5347",
|
||||
"31",
|
||||
])
|
||||
const values = Array.from($values).map(header =>
|
||||
header.textContent.trim()
|
||||
)
|
||||
expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -102,8 +88,7 @@ context("Create a View", () => {
|
|||
cy.contains("Students").should("be.visible")
|
||||
cy.contains("Teachers").should("be.visible")
|
||||
|
||||
cy
|
||||
.get(".ag-row[row-index=0]")
|
||||
cy.get(".ag-row[row-index=0]")
|
||||
.find(".ag-cell")
|
||||
.then($values => {
|
||||
const values = Array.from($values).map(value => value.textContent)
|
||||
|
@ -120,10 +105,11 @@ context("Create a View", () => {
|
|||
})
|
||||
|
||||
it("renames a view", () => {
|
||||
cy.contains("[data-cy=table-nav-item]", "Test View")
|
||||
.find(".ri-more-line")
|
||||
cy.contains(".nav-item", "Test View")
|
||||
.find(".actions")
|
||||
.invoke("show")
|
||||
.click()
|
||||
cy.contains("Edit").click()
|
||||
cy.get("[data-cy=edit-view]").click()
|
||||
cy.get(".menu-container").within(() => {
|
||||
cy.get("input").type(" Updated")
|
||||
cy.contains("Save").click()
|
||||
|
@ -132,11 +118,11 @@ context("Create a View", () => {
|
|||
})
|
||||
|
||||
it("deletes a view", () => {
|
||||
cy.contains("[data-cy=table-nav-item]", "Test View Updated").click()
|
||||
cy.contains("[data-cy=table-nav-item]", "Test View Updated")
|
||||
.find(".ri-more-line")
|
||||
cy.contains(".nav-item", "Test View Updated")
|
||||
.find(".actions")
|
||||
.invoke("show")
|
||||
.click()
|
||||
cy.contains("Delete").click()
|
||||
cy.get("[data-cy=delete-view]").click()
|
||||
cy.contains("Delete View").click()
|
||||
cy.contains("TestView Updated").should("not.be.visible")
|
||||
})
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
// 3. Runs the server using said folder
|
||||
|
||||
const rimraf = require("rimraf")
|
||||
const { join } = require("path")
|
||||
const run = require("../../cli/src/commands/run/runHandler")
|
||||
const { join, resolve } = require("path")
|
||||
// const run = require("../../cli/src/commands/run/runHandler")
|
||||
const initialiseBudibase = require("../../server/src/utilities/initialiseBudibase")
|
||||
|
||||
const homedir = join(require("os").homedir(), ".budibase")
|
||||
|
@ -16,9 +16,22 @@ process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
|
|||
process.env.NODE_ENV = "cypress"
|
||||
process.env.ENABLE_ANALYTICS = "false"
|
||||
|
||||
async function run(dir) {
|
||||
process.env.BUDIBASE_DIR = resolve(dir)
|
||||
require("dotenv").config({ path: resolve(dir, ".env") })
|
||||
|
||||
// dont make this a variable or top level require
|
||||
// it will cause environment module to be loaded prematurely
|
||||
const server = require("../../server/src/app")
|
||||
server.on("close", () => console.log("Server Closed"))
|
||||
}
|
||||
|
||||
initialiseBudibase({ dir: homedir, clientId: "cypress-test" })
|
||||
.then(() => {
|
||||
delete require.cache[require.resolve("../../server/src/environment")]
|
||||
run({ dir: homedir })
|
||||
const xPlatHomeDir = homedir.startsWith("~")
|
||||
? join(homedir(), homedir.substring(1))
|
||||
: homedir
|
||||
run(xPlatHomeDir)
|
||||
})
|
||||
.catch(e => console.error(e))
|
||||
|
|
|
@ -51,7 +51,7 @@ Cypress.Commands.add("createApp", name => {
|
|||
.click()
|
||||
.type("test")
|
||||
cy.contains("Submit").click()
|
||||
cy.contains("Create New Table", {
|
||||
cy.get("[data-cy=new-table]", {
|
||||
timeout: 20000,
|
||||
}).should("be.visible")
|
||||
})
|
||||
|
@ -65,7 +65,7 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
|||
|
||||
Cypress.Commands.add("createTable", tableName => {
|
||||
// Enter table name
|
||||
cy.contains("Create New Table").click()
|
||||
cy.get("[data-cy=new-table]").click()
|
||||
cy.get(".modal").within(() => {
|
||||
cy.get("input")
|
||||
.first()
|
||||
|
@ -79,9 +79,7 @@ Cypress.Commands.add("createTable", tableName => {
|
|||
|
||||
Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
|
||||
// Select Table
|
||||
cy.get(".root")
|
||||
.contains(tableName)
|
||||
.click()
|
||||
cy.contains(".nav-item", tableName).click()
|
||||
cy.contains("Create New Column").click()
|
||||
|
||||
// Configure column
|
||||
|
@ -155,7 +153,7 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
|||
})
|
||||
|
||||
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("input")
|
||||
.eq(0)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.2.6",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -64,7 +64,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.47.0",
|
||||
"@budibase/client": "^0.2.6",
|
||||
"@budibase/client": "^0.3.0",
|
||||
"@budibase/colorpicker": "^1.0.1",
|
||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||
|
|
|
@ -85,14 +85,6 @@
|
|||
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 {
|
||||
border: 1px solid var(--grey-4);
|
||||
background: #fff;
|
||||
|
|
|
@ -106,11 +106,15 @@ const contextToBindables = (tables, walkResult) => context => {
|
|||
}
|
||||
}
|
||||
|
||||
const stringType = { type: "string" }
|
||||
return (
|
||||
Object.entries(schema)
|
||||
.map(newBindable)
|
||||
// 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]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ import { walkProps } from "./storeUtils"
|
|||
import { get_capitalised_name } from "../helpers"
|
||||
|
||||
export default function(component, state) {
|
||||
const capitalised = get_capitalised_name(component)
|
||||
const capitalised = get_capitalised_name(
|
||||
component.name || component._component
|
||||
)
|
||||
|
||||
const matchingComponents = []
|
||||
|
||||
|
|
|
@ -371,7 +371,7 @@ const addChildComponent = store => (componentToAdd, presetProps = {}) => {
|
|||
const component = getComponentDefinition(state, componentToAdd)
|
||||
|
||||
const instanceId = get(backendUiStore).selectedDatabase._id
|
||||
const instanceName = getNewComponentName(componentToAdd, state)
|
||||
const instanceName = getNewComponentName(component, state)
|
||||
|
||||
const newComponent = createProps(
|
||||
component,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script>
|
||||
import { afterUpdate } from "svelte"
|
||||
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
|
||||
$: automationLive = automation?.live
|
||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
||||
$: automationCount = $automationStore.automations?.length ?? 0
|
||||
|
||||
function onSelect(block) {
|
||||
automationStore.update(state => {
|
||||
|
@ -14,80 +14,19 @@
|
|||
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>
|
||||
|
||||
<section>
|
||||
{#if automation}
|
||||
<BlockList />
|
||||
<Flowchart {automation} {onSelect} />
|
||||
</section>
|
||||
<footer>
|
||||
{#if automation}
|
||||
<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>
|
||||
{:else if automationCount === 0}
|
||||
<i>Create your first automation to get started</i>
|
||||
{:else}<i>Select an automation to edit</i>{/if}
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
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);
|
||||
i {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--grey-5);
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
|
@ -3,7 +3,6 @@
|
|||
import Arrow from "./Arrow.svelte"
|
||||
import { flip } from "svelte/animate"
|
||||
import { fade, fly } from "svelte/transition"
|
||||
import { automationStore } from "builderStore"
|
||||
|
||||
export let automation
|
||||
export let onSelect
|
||||
|
@ -18,16 +17,9 @@
|
|||
blocks = blocks.concat(automation.definition.steps || [])
|
||||
}
|
||||
}
|
||||
$: automationCount = $automationStore.automations?.length ?? 0
|
||||
</script>
|
||||
|
||||
{#if automationCount === 0}
|
||||
<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}
|
||||
{#if !blocks.length}<i>Add a trigger to your automation to get started</i>{/if}
|
||||
<section class="canvas">
|
||||
{#each blocks as block, idx (block.id)}
|
||||
<div
|
||||
|
@ -44,19 +36,18 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
i {
|
||||
font-size: var(--font-size-xl);
|
||||
color: var(--grey-4);
|
||||
padding: var(--spacing-xl) 40px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
section {
|
||||
position: absolute;
|
||||
padding: 40px;
|
||||
margin: 0 -40px calc(-1 * var(--spacing-l)) -40px;
|
||||
padding: var(--spacing-l) 40px 0 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 {
|
||||
|
@ -65,4 +56,9 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--grey-5);
|
||||
}
|
||||
</style>
|
|
@ -10,6 +10,11 @@
|
|||
$: steps =
|
||||
$automationStore.selectedAutomation?.automation?.definition?.steps ?? []
|
||||
$: blockIdx = steps.findIndex(step => step.id === block.id)
|
||||
$: allowDeleteTrigger = !steps.length
|
||||
|
||||
function deleteStep() {
|
||||
automationStore.actions.deleteAutomationBlock(block)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -30,6 +35,9 @@
|
|||
<div class="label">
|
||||
{#if block.type === 'TRIGGER'}Trigger{:else}Step {blockIdx + 1}{/if}
|
||||
</div>
|
||||
{#if block.type !== 'TRIGGER' || allowDeleteTrigger}
|
||||
<i on:click|stopPropagation={deleteStep} class="delete ri-close-line" />
|
||||
{/if}
|
||||
</header>
|
||||
<hr />
|
||||
<p>
|
||||
|
@ -61,6 +69,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
header span {
|
||||
flex: 1 1 auto;
|
||||
|
@ -74,7 +83,13 @@
|
|||
}
|
||||
header i {
|
||||
font-size: 20px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
header i.delete {
|
||||
opacity: 0.5;
|
||||
}
|
||||
header i.delete:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ACTION {
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,50 +1,44 @@
|
|||
<script>
|
||||
import { automationStore } from "builderStore"
|
||||
import AutomationList from "./AutomationList/AutomationList.svelte"
|
||||
import BlockList from "./BlockList/BlockList.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { Spacer } from "@budibase/bbui"
|
||||
import AutomationList from "./AutomationList.svelte"
|
||||
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { automationStore, backendUiStore } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
|
||||
let selectedTab = "AUTOMATIONS"
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<Heading black small>
|
||||
<span
|
||||
data-cy="automation-list"
|
||||
class="hoverable automation-header"
|
||||
class:selected={selectedTab === 'AUTOMATIONS'}
|
||||
on:click={() => (selectedTab = 'AUTOMATIONS')}>
|
||||
Automations
|
||||
</span>
|
||||
{#if $automationStore.selectedAutomation}
|
||||
<span
|
||||
data-cy="add-automation-component"
|
||||
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}
|
||||
<div class="title">
|
||||
<h1>Automations</h1>
|
||||
<i
|
||||
on:click={modal.show}
|
||||
data-cy="new-automation"
|
||||
class="ri-add-circle-fill" />
|
||||
</div>
|
||||
<AutomationList />
|
||||
<Modal bind:this={modal}>
|
||||
<CreateAutomationModal />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
header {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
background: none;
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.automation-header {
|
||||
margin-right: var(--spacing-xl);
|
||||
.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);
|
||||
}
|
||||
|
||||
span:not(.selected) {
|
||||
|
|
|
@ -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>
|
|
@ -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,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>
|
|
@ -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"
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import TableSelector from "./ParamInputs/TableSelector.svelte"
|
||||
import RowSelector from "./ParamInputs/RowSelector.svelte"
|
||||
import { Button, Input, TextArea, Select, Label } from "@budibase/bbui"
|
||||
import TableSelector from "./TableSelector.svelte"
|
||||
import RowSelector from "./RowSelector.svelte"
|
||||
import { Button, Input, Select, Label } from "@budibase/bbui"
|
||||
import { automationStore } from "builderStore"
|
||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||
import BindableInput from "../../userInterface/BindableInput.svelte"
|
||||
import BindableInput from "components/userInterface/BindableInput.svelte"
|
||||
|
||||
export let block
|
||||
export let webhookModal
|
||||
|
@ -47,43 +47,41 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="container" data-cy="automation-block-setup">
|
||||
<div class="block-label">{block.name}</div>
|
||||
{#each inputs as [key, value]}
|
||||
<div class="bb-margin-xl block-field">
|
||||
<Label extraSmall grey>{value.title}</Label>
|
||||
{#if value.type === 'string' && value.enum}
|
||||
<Select bind:value={block.inputs[key]} thin secondary>
|
||||
<option value="">Choose an option</option>
|
||||
{#each value.enum as option, idx}
|
||||
<option value={option}>
|
||||
{value.pretty ? value.pretty[idx] : option}
|
||||
</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else if value.customType === 'password'}
|
||||
<Input type="password" thin bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'table'}
|
||||
<TableSelector bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'row'}
|
||||
<RowSelector bind:value={block.inputs[key]} {bindings} />
|
||||
{:else if value.customType === 'webhookUrl'}
|
||||
<WebhookDisplay value={block.inputs[key]} />
|
||||
{:else if value.type === 'string' || value.type === 'number'}
|
||||
<BindableInput
|
||||
type="string"
|
||||
thin
|
||||
bind:value={block.inputs[key]}
|
||||
{bindings} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if stepId === 'WEBHOOK'}
|
||||
<Button wide secondary on:click={() => webhookModal.show()}>
|
||||
Setup webhook
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="block-label">{block.name}</div>
|
||||
{#each inputs as [key, value]}
|
||||
<div class="block-field">
|
||||
<Label extraSmall grey>{value.title}</Label>
|
||||
{#if value.type === 'string' && value.enum}
|
||||
<Select bind:value={block.inputs[key]} extraThin secondary>
|
||||
<option value="">Choose an option</option>
|
||||
{#each value.enum as option, idx}
|
||||
<option value={option}>
|
||||
{value.pretty ? value.pretty[idx] : option}
|
||||
</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else if value.customType === 'password'}
|
||||
<Input type="password" extraThin bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'table'}
|
||||
<TableSelector bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'row'}
|
||||
<RowSelector bind:value={block.inputs[key]} {bindings} />
|
||||
{:else if value.customType === 'webhookUrl'}
|
||||
<WebhookDisplay value={block.inputs[key]} />
|
||||
{:else if value.type === 'string' || value.type === 'number'}
|
||||
<BindableInput
|
||||
type="string"
|
||||
extraThin
|
||||
bind:value={block.inputs[key]}
|
||||
{bindings} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if stepId === 'WEBHOOK'}
|
||||
<Button wide secondary on:click={() => webhookModal.show()}>
|
||||
Set Up Webhook
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.block-field {
|
||||
|
@ -92,7 +90,7 @@
|
|||
|
||||
.block-label {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-7);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { Input, Select, Label } from "@budibase/bbui"
|
||||
import BindableInput from "../../../userInterface/BindableInput.svelte"
|
||||
import BindableInput from "../../userInterface/BindableInput.svelte"
|
||||
|
||||
export let value
|
||||
export let bindings
|
||||
|
@ -19,7 +19,7 @@
|
|||
</script>
|
||||
|
||||
<div class="block-field">
|
||||
<Select bind:value={value.tableId} thin secondary>
|
||||
<Select bind:value={value.tableId} extraThin secondary>
|
||||
<option value="">Choose an option</option>
|
||||
{#each $backendUiStore.tables as table}
|
||||
<option value={table._id}>{table.name}</option>
|
||||
|
@ -31,7 +31,7 @@
|
|||
<div class="schema-fields">
|
||||
{#each schemaFields as [field, 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>
|
||||
{#each schema.constraints.inclusion as option}
|
||||
<option value={option}>{option}</option>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</Select>
|
||||
{:else if schema.type === 'string' || schema.type === 'number'}
|
||||
<BindableInput
|
||||
thin
|
||||
extraThin
|
||||
bind:value={value[field]}
|
||||
label={field}
|
||||
type="string"
|
|
@ -2,25 +2,27 @@
|
|||
import { backendUiStore, automationStore } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
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 ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
||||
let selectedTab = "SETUP"
|
||||
let confirmDeleteDialog
|
||||
let webhookModal
|
||||
|
||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
||||
$: automation = $automationStore.selectedAutomation?.automation
|
||||
$: allowDeleteBlock =
|
||||
$automationStore.selectedBlock?.type !== "TRIGGER" ||
|
||||
!automation?.definition?.steps?.length
|
||||
$: name = automation?.name ?? ""
|
||||
$: automationLive = automation?.live
|
||||
|
||||
function deleteAutomationBlock() {
|
||||
automationStore.actions.deleteAutomationBlock(
|
||||
$automationStore.selectedBlock
|
||||
)
|
||||
function setAutomationLive(live) {
|
||||
if (automation.live === live) {
|
||||
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() {
|
||||
|
@ -41,115 +43,78 @@
|
|||
})
|
||||
notifier.success(`Automation ${automation.name} saved.`)
|
||||
}
|
||||
|
||||
async function deleteAutomation() {
|
||||
await automationStore.actions.delete({
|
||||
instanceId,
|
||||
automation,
|
||||
})
|
||||
notifier.success("Automation deleted.")
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<span
|
||||
class="hoverable"
|
||||
class:selected={selectedTab === 'SETUP'}
|
||||
on:click={() => (selectedTab = 'SETUP')}>
|
||||
Setup
|
||||
</span>
|
||||
</header>
|
||||
<div class="content">
|
||||
{#if $automationStore.selectedBlock}
|
||||
<AutomationBlockSetup
|
||||
bind:block={$automationStore.selectedBlock}
|
||||
{webhookModal} />
|
||||
{:else if $automationStore.selectedAutomation}
|
||||
<div class="block-label">Automation <b>{automation.name}</b></div>
|
||||
<Button secondary wide on:click={testAutomation}>Test Automation</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{#if $automationStore.selectedBlock}
|
||||
<Button
|
||||
green
|
||||
wide
|
||||
data-cy="save-automation-setup"
|
||||
on:click={saveAutomation}>
|
||||
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} />
|
||||
<div class="title">
|
||||
<h1>Setup</h1>
|
||||
<i
|
||||
class:highlighted={automationLive}
|
||||
class:hoverable={automationLive}
|
||||
on:click={() => setAutomationLive(false)}
|
||||
class="ri-stop-circle-fill" />
|
||||
<i
|
||||
class:highlighted={!automationLive}
|
||||
class:hoverable={!automationLive}
|
||||
data-cy="activate-automation"
|
||||
on:click={() => setAutomationLive(true)}
|
||||
class="ri-play-circle-fill" />
|
||||
</div>
|
||||
{#if $automationStore.selectedBlock}
|
||||
<AutomationBlockSetup
|
||||
bind:block={$automationStore.selectedBlock}
|
||||
{webhookModal} />
|
||||
{:else if $automationStore.selectedAutomation}
|
||||
<div class="block-label">{automation.name}</div>
|
||||
<Button secondary wide on:click={testAutomation}>Test Automation</Button>
|
||||
{/if}
|
||||
<Button
|
||||
secondary
|
||||
wide
|
||||
data-cy="save-automation-setup"
|
||||
on:click={saveAutomation}>
|
||||
Save Automation
|
||||
</Button>
|
||||
<Modal bind:this={webhookModal} width="30%">
|
||||
<CreateWebookModal />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
section {
|
||||
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;
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
color: var(--ink);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
header > span {
|
||||
.title h1 {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.title i {
|
||||
font-size: 20px;
|
||||
color: var(--grey-5);
|
||||
margin-right: var(--spacing-xl);
|
||||
cursor: pointer;
|
||||
}
|
||||
.selected {
|
||||
.title i.highlighted {
|
||||
color: var(--ink);
|
||||
}
|
||||
.title i.hoverable:hover {
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.block-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--grey-7);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
.footer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</script>
|
||||
|
||||
<div class="block-field">
|
||||
<Select bind:value secondary thin>
|
||||
<Select bind:value secondary extraThin>
|
||||
<option value="">Choose an option</option>
|
||||
{#each $backendUiStore.tables as table}
|
||||
<option value={table._id}>{table.name}</option>
|
|
@ -1 +0,0 @@
|
|||
export { default as SetupPanel } from "./SetupPanel.svelte"
|
|
@ -3,12 +3,8 @@
|
|||
import WebhookDisplay from "./WebhookDisplay.svelte"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import analytics from "analytics"
|
||||
|
||||
const POLL_RATE_MS = 2500
|
||||
const DEFAULT_SCHEMA_OUTPUT = "Any input allowed"
|
||||
let name
|
||||
let interval
|
||||
let finished = false
|
||||
let schemaURL
|
||||
|
@ -88,8 +84,7 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
margin: 0;
|
||||
text-align: justify;
|
||||
}
|
||||
.finished-text {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { Input } from "@budibase/bbui"
|
||||
import { store } from "../../../builderStore"
|
||||
import { store } from "builderStore"
|
||||
|
||||
export let value
|
||||
export let production = false
|
||||
|
|
|
@ -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"
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
$: title = $backendUiStore.selectedTable.name
|
||||
$: schema = $backendUiStore.selectedTable.schema
|
||||
$: tableId = $backendUiStore.selectedTable._id
|
||||
$: tableView = {
|
||||
schema,
|
||||
name: $backendUiStore.selectedView.name,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import api from "builderStore/api"
|
||||
import Table from "./Table.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import { backendUiStore } from "builderStore"
|
||||
|
||||
export let tableId
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { fade } from "svelte/transition"
|
||||
import { Button } from "@budibase/bbui"
|
||||
import { goto, params } from "@sveltech/routify"
|
||||
import AgGrid from "@budibase/svelte-ag-grid"
|
||||
|
||||
|
@ -8,11 +7,7 @@
|
|||
import { notifier } from "builderStore/store/notifications"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
|
||||
import {
|
||||
getRenderer,
|
||||
editRowRenderer,
|
||||
deleteRowRenderer,
|
||||
} from "./cells/cellRenderers"
|
||||
import { getRenderer, editRowRenderer } from "./cells/cellRenderers"
|
||||
import TableLoadingOverlay from "./TableLoadingOverlay"
|
||||
import TableHeader from "./TableHeader"
|
||||
import "@budibase/svelte-ag-grid/dist/index.css"
|
||||
|
@ -58,8 +53,8 @@
|
|||
resizable: false,
|
||||
suppressMovable: true,
|
||||
suppressMenu: true,
|
||||
minWidth: 100,
|
||||
width: 100,
|
||||
minWidth: 84,
|
||||
width: 84,
|
||||
cellRenderer: editRowRenderer,
|
||||
},
|
||||
]
|
||||
|
@ -100,7 +95,7 @@
|
|||
}
|
||||
|
||||
const deleteRows = async () => {
|
||||
const response = await api.post(`/api/${tableId}/rows`, {
|
||||
await api.post(`/api/${tableId}/rows`, {
|
||||
rows: selectedRows,
|
||||
type: "delete",
|
||||
})
|
||||
|
@ -110,16 +105,23 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="table-controls">
|
||||
<h2 class="title"><span>{title}</span></h2>
|
||||
<div class="popovers">
|
||||
<slot />
|
||||
{#if selectedRows.length > 0}
|
||||
<DeleteRowsButton {selectedRows} {deleteRows} />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div class="table-title">
|
||||
<h1>{title}</h1>
|
||||
{#if loading}
|
||||
<div transition:fade>
|
||||
<Spinner size="10" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="popovers">
|
||||
<slot />
|
||||
{#if selectedRows.length > 0}
|
||||
<DeleteRowsButton {selectedRows} {deleteRows} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-wrapper">
|
||||
<AgGrid
|
||||
{theme}
|
||||
{options}
|
||||
|
@ -127,35 +129,57 @@
|
|||
{columnDefs}
|
||||
{loading}
|
||||
on:select={({ detail }) => (selectedRows = detail)} />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
text-rendering: optimizeLegibility;
|
||||
margin-top: 0;
|
||||
.table-title {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title > span {
|
||||
margin-right: var(--spacing-xs);
|
||||
.table-title h1 {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table-controls {
|
||||
width: 100%;
|
||||
.table-title > div {
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.popovers {
|
||||
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);
|
||||
}
|
||||
|
||||
:global(.popovers > div) {
|
||||
margin-right: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
.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(.ag-theme-alpine) {
|
||||
--ag-border-color: var(--grey-4);
|
||||
--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) {
|
||||
|
@ -168,11 +192,15 @@
|
|||
}
|
||||
|
||||
:global(.ag-header-cell-text) {
|
||||
font-family: Inter;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: var(--grey-1);
|
||||
}
|
||||
|
||||
:global(.ag-filter) {
|
||||
padding: var(--spacing-s);
|
||||
outline: none;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { TextButton, Icon, Modal, ModalContent } from "@budibase/bbui"
|
||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
||||
import { TextButton, Icon } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
||||
export let selectedRows
|
||||
export let deleteRows
|
||||
|
@ -21,14 +21,12 @@
|
|||
row(s)
|
||||
</TextButton>
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<ModalContent
|
||||
red
|
||||
confirmText="Delete"
|
||||
onConfirm={confirmDeletion}
|
||||
title="Confirm Row Deletion">
|
||||
Are you sure you want to delete
|
||||
{selectedRows.length}
|
||||
row{selectedRows.length > 1 ? 's' : ''}?
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ConfirmDialog
|
||||
bind:this={modal}
|
||||
okText="Delete"
|
||||
onOk={confirmDeletion}
|
||||
title="Confirm Deletion">
|
||||
Are you sure you want to delete
|
||||
{selectedRows.length}
|
||||
row{selectedRows.length > 1 ? 's' : ''}?
|
||||
</ConfirmDialog>
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
okText="Delete Column"
|
||||
onOk={deleteColumn}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Delete" />
|
||||
title="Confirm Deletion" />
|
||||
|
||||
<style>
|
||||
.actions {
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
|
||||
import * as api from "../api"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
||||
export let row
|
||||
|
||||
let anchor
|
||||
let dropdown
|
||||
let confirmDeleteDialog
|
||||
|
||||
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.`}
|
||||
okText="Delete Row"
|
||||
onOk={deleteRow}
|
||||
title="Confirm Delete" />
|
||||
title="Confirm Deletion" />
|
||||
|
||||
<style>
|
||||
.ri-delete-bin-line:hover {
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { DropdownMenu, Icon, Modal, Button } from "@budibase/bbui"
|
||||
import { Modal, Button } from "@budibase/bbui"
|
||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
||||
|
||||
export let row
|
||||
|
||||
let anchor
|
||||
let dropdown
|
||||
let confirmDeleteDialog
|
||||
let modal
|
||||
|
||||
function showModal(e) {
|
||||
|
@ -16,7 +12,7 @@
|
|||
}
|
||||
</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}>
|
||||
<CreateEditRowModal {row} />
|
||||
</Modal>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<script>
|
||||
import { Heading, Body, Button, Select, Label } from "@budibase/bbui"
|
||||
import { Select, Label } from "@budibase/bbui"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import api from "builderStore/api"
|
||||
|
||||
const BYTES_IN_KB = 1000
|
||||
const BYTES_IN_MB = 1000000
|
||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 1
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { Spacer } from "@budibase/bbui"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
|
||||
let modal
|
||||
$: selectedView =
|
||||
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
||||
|
||||
|
@ -20,67 +21,68 @@
|
|||
backendUiStore.actions.views.select(view)
|
||||
$goto(`./view/${view.name}`)
|
||||
}
|
||||
|
||||
function onClickView(table, viewName) {
|
||||
if (selectedView === viewName) {
|
||||
return
|
||||
}
|
||||
selectView({
|
||||
name: viewName,
|
||||
...table.views[viewName],
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="items-root">
|
||||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||
<div class="hierarchy">
|
||||
<div class="components-list-container">
|
||||
<Heading small>Tables</Heading>
|
||||
<Spacer medium />
|
||||
<CreateTableModal />
|
||||
<div class="hierarchy-items-container">
|
||||
{#each $backendUiStore.tables as table}
|
||||
<ListItem
|
||||
selected={selectedView === `all_${table._id}`}
|
||||
title={table.name}
|
||||
icon="ri-table-fill"
|
||||
on:click={() => selectTable(table)}>
|
||||
<EditTablePopover {table} />
|
||||
</ListItem>
|
||||
{#each Object.keys(table.views || {}) as viewName}
|
||||
<ListItem
|
||||
indented
|
||||
selected={selectedView === viewName}
|
||||
title={viewName}
|
||||
icon="ri-eye-line"
|
||||
on:click={() => (selectedView === viewName ? {} : selectView({
|
||||
name: viewName,
|
||||
...table.views[viewName],
|
||||
}))}>
|
||||
<EditViewPopover
|
||||
view={{ name: viewName, ...table.views[viewName] }} />
|
||||
</ListItem>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||
<div class="title">
|
||||
<h1>Tables</h1>
|
||||
<i data-cy="new-table" on:click={modal.show} class="ri-add-circle-fill" />
|
||||
</div>
|
||||
<div class="hierarchy-items-container">
|
||||
{#each $backendUiStore.tables as table, idx}
|
||||
<NavItem
|
||||
border={idx > 0}
|
||||
icon="ri-table-line"
|
||||
text={table.name}
|
||||
selected={selectedView === `all_${table._id}`}
|
||||
on:click={() => selectTable(table)}>
|
||||
<EditTablePopover {table} />
|
||||
</NavItem>
|
||||
{#each Object.keys(table.views || {}) as viewName}
|
||||
<NavItem
|
||||
indentLevel={1}
|
||||
icon="ri-eye-line"
|
||||
text={viewName}
|
||||
selected={selectedView === viewName}
|
||||
on:click={() => onClickView(table, viewName)}>
|
||||
<EditViewPopover
|
||||
view={{ name: viewName, ...table.views[viewName] }} />
|
||||
</NavItem>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<Modal bind:this={modal}>
|
||||
<CreateTableModal />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
h5 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.items-root {
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hierarchy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.title h1 {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hierarchy-items-container {
|
||||
margin-top: var(--spacing-xl);
|
||||
flex: 1 1 auto;
|
||||
.title i {
|
||||
font-size: 20px;
|
||||
}
|
||||
.title i:hover {
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { Button, Input, Label, ModalContent, Modal } from "@budibase/bbui"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import { Input, Label, ModalContent } from "@budibase/bbui"
|
||||
import TableDataImport from "../TableDataImport.svelte"
|
||||
import analytics from "analytics"
|
||||
import screenTemplates from "builderStore/store/screenTemplates"
|
||||
|
@ -22,12 +21,6 @@
|
|||
let dataImport
|
||||
let error = ""
|
||||
|
||||
function resetState() {
|
||||
name = ""
|
||||
dataImport = undefined
|
||||
error = ""
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
const tableName = evt.target.value
|
||||
if ($backendUiStore.models?.some(model => model.name === tableName)) {
|
||||
|
@ -84,23 +77,20 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Button primary wide on:click={modal.show}>Create New Table</Button>
|
||||
<Modal bind:this={modal} on:hide={resetState}>
|
||||
<ModalContent
|
||||
title="Create Table"
|
||||
confirmText="Create"
|
||||
onConfirm={saveTable}
|
||||
disabled={error || !name || (dataImport && !dataImport.valid)}>
|
||||
<Input
|
||||
data-cy="table-name-input"
|
||||
thin
|
||||
label="Table Name"
|
||||
on:input={checkValid}
|
||||
bind:value={name}
|
||||
{error} />
|
||||
<div>
|
||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||
<TableDataImport bind:dataImport />
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ModalContent
|
||||
title="Create Table"
|
||||
confirmText="Create"
|
||||
onConfirm={saveTable}
|
||||
disabled={error || !name || (dataImport && !dataImport.valid)}>
|
||||
<Input
|
||||
data-cy="table-name-input"
|
||||
thin
|
||||
label="Table Name"
|
||||
on:input={checkValid}
|
||||
bind:value={name}
|
||||
{error} />
|
||||
<div>
|
||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||
<TableDataImport bind:dataImport />
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<script>
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import screenTemplates from "builderStore/store/screenTemplates"
|
||||
import api from "builderStore/api"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
export let table
|
||||
|
||||
|
@ -66,42 +64,46 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
||||
<i class="ri-more-line" />
|
||||
<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}>
|
||||
{#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>
|
||||
<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
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Table"
|
||||
onOk={deleteTable}
|
||||
title="Confirm Delete">
|
||||
title="Confirm Deletion">
|
||||
Are you sure you wish to delete the table
|
||||
<i>{table.name}?</i>
|
||||
The following will also be deleted:
|
||||
|
@ -155,29 +157,4 @@
|
|||
justify-content: flex-end;
|
||||
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>
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
export let view
|
||||
|
||||
|
@ -46,38 +46,42 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
||||
<i class="ri-more-line" />
|
||||
<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}>
|
||||
{#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>
|
||||
<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
|
||||
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.`}
|
||||
okText="Delete View"
|
||||
onOk={deleteView}
|
||||
title="Confirm Delete" />
|
||||
title="Confirm Deletion" />
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
|
@ -108,29 +112,4 @@
|
|||
justify-content: flex-end;
|
||||
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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
export { default as DropdownContainer } from "./DropdownContainer.svelte"
|
||||
export { default as DropdownItem } from "./DropdownItem.svelte"
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { Heading, Body, Button, Dropzone } from "@budibase/bbui"
|
||||
import { Dropzone } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
|
||||
export let files = []
|
||||
|
||||
function handleFileTooLarge() {
|
||||
const BYTES_IN_MB = 1000000
|
||||
|
||||
function handleFileTooLarge(fileSizeLimit) {
|
||||
notifier.danger(
|
||||
`Files cannot exceed ${fileSizeLimit /
|
||||
BYTES_IN_MB}MB. Please try again with smaller files.`
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,3 +0,0 @@
|
|||
import { flow } from "lodash/fp"
|
||||
|
||||
export const pipe = (arg, funcs) => flow(funcs)(arg)
|
|
@ -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
|
||||
}
|
|
@ -1,32 +1,36 @@
|
|||
<script>
|
||||
import SettingsModal from "./SettingsModal.svelte"
|
||||
import { SettingsIcon } from "components/common/Icons/"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<span class="topnavitemright settings" on:click={modal.show}>
|
||||
<SettingsIcon />
|
||||
</span>
|
||||
<div class="topnavitemright settings" on:click={modal.show}>
|
||||
<i class="ri-settings-3-line" />
|
||||
</div>
|
||||
<Modal bind:this={modal} width="600px">
|
||||
<SettingsModal />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
span:first-letter {
|
||||
text-transform: capitalize;
|
||||
i {
|
||||
font-size: 18px;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
.topnavitemright {
|
||||
cursor: pointer;
|
||||
color: var(--grey-7);
|
||||
margin: 0 20px 0 0;
|
||||
margin: 0 12px 0 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.topnavitemright:hover i {
|
||||
color: var(--ink);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -7,9 +7,8 @@
|
|||
import Spinner from "components/common/Spinner.svelte"
|
||||
import { API, Info, User } from "./Steps"
|
||||
import Indicator from "./Indicator.svelte"
|
||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||
import { Button } from "@budibase/bbui"
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
|
||||
import { fade } from "svelte/transition"
|
||||
import { post } from "builderStore/api"
|
||||
import analytics from "analytics"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { store, backendUiStore } from "builderStore"
|
||||
import { map, join } from "lodash/fp"
|
||||
import iframeTemplate from "./iframeTemplate"
|
||||
import { pipe } from "components/common/core"
|
||||
import { pipe } from "../../../helpers"
|
||||
|
||||
let iframe
|
||||
let styles = ""
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
width: 24px;
|
||||
background: var(--grey-4);
|
||||
right: var(--spacing-s);
|
||||
bottom: 9px;
|
||||
bottom: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: var(--grey-5);
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
<style>
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: 500;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
li {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import { store } from "builderStore"
|
||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||
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 { uuid } from "builderStore/uuid"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
export let component
|
||||
|
||||
|
@ -98,110 +98,65 @@
|
|||
}
|
||||
</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>
|
||||
<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>
|
||||
<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
|
||||
bind:this={confirmDeleteDialog}
|
||||
title="Confirm Delete"
|
||||
title="Confirm Deletion"
|
||||
body={`Are you sure you wish to delete this '${lastPartOfName(component)}' component?`}
|
||||
okText="Delete Component"
|
||||
onOk={deleteComponent} />
|
||||
|
||||
<style>
|
||||
ul {
|
||||
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 {
|
||||
hr {
|
||||
margin: 8px 0;
|
||||
color: var(--grey-4);
|
||||
background-color: var(--grey-4);
|
||||
height: 1px;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -63,41 +63,31 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{categories}
|
||||
{selectedCategory} />
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{categories}
|
||||
{selectedCategory} />
|
||||
|
||||
{#if displayName}
|
||||
<div class="instance-name">{componentInstance._instanceName}</div>
|
||||
{#if displayName}
|
||||
<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}
|
||||
|
||||
<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>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title > div:nth-child(1) {
|
||||
grid-column-start: name;
|
||||
color: var(--ink);
|
||||
|
@ -108,7 +98,6 @@
|
|||
}
|
||||
|
||||
.component-props-container {
|
||||
margin-top: 16px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
|
@ -118,8 +107,7 @@
|
|||
}
|
||||
|
||||
.instance-name {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
|
|
|
@ -2,47 +2,97 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
import components from "./temporaryPanelStructure.js"
|
||||
import CategoryTab from "./CategoryTab.svelte"
|
||||
|
||||
import Tab from "./ItemTab/Tab.svelte"
|
||||
|
||||
export let toggleTab
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
const categories = components.categories
|
||||
let selectedCategory = categories[0]
|
||||
let selectedIndex
|
||||
let anchors = []
|
||||
let popover
|
||||
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex]
|
||||
|
||||
const close = () => {
|
||||
popover.hide()
|
||||
}
|
||||
|
||||
const onCategoryChosen = (category, idx) => {
|
||||
if (category.isCategory) {
|
||||
selectedIndex = idx
|
||||
popover.show()
|
||||
} else {
|
||||
onComponentChosen(category)
|
||||
}
|
||||
}
|
||||
|
||||
const onComponentChosen = component => {
|
||||
store.addChildComponent(component._component, component.presetProps)
|
||||
|
||||
toggleTab("Navigate")
|
||||
|
||||
// Get ID path
|
||||
const path = store.getPathToComponent($store.currentComponentInfo)
|
||||
|
||||
// Go to correct URL
|
||||
$goto(`./:page/:screen/${path}`)
|
||||
close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{selectedCategory}
|
||||
{categories} />
|
||||
|
||||
<div class="panel">
|
||||
<Tab
|
||||
list={selectedCategory}
|
||||
on:selectItem={e => onComponentChosen(e.detail)}
|
||||
{toggleTab} />
|
||||
</div>
|
||||
<div class="container">
|
||||
{#each categories as category, idx}
|
||||
<div
|
||||
bind:this={anchors[idx]}
|
||||
class="category"
|
||||
on:click={() => onCategoryChosen(category, idx)}
|
||||
class:active={idx === selectedIndex}>
|
||||
{#if category.icon}<i class={category.icon} />{/if}
|
||||
<span>{category.name}</span>
|
||||
{#if category.isCategory}<i class="ri-arrow-down-s-line arrow" />{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</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>
|
||||
.panel {
|
||||
margin-top: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 20px;
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
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>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<script>
|
||||
import { params, goto } from "@sveltech/routify"
|
||||
import { goto } from "@sveltech/routify"
|
||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
||||
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { pipe } from "components/common/core"
|
||||
import { trimCharsStart, trimChars } from "lodash/fp"
|
||||
import { pipe } from "../../helpers"
|
||||
import { store } from "builderStore"
|
||||
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
|
||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
|
||||
export let screens = []
|
||||
|
||||
|
@ -24,8 +23,6 @@
|
|||
let confirmDeleteDialog
|
||||
let componentToDelete = ""
|
||||
|
||||
const joinPath = join("/")
|
||||
|
||||
const normalizedName = name =>
|
||||
pipe(name, [
|
||||
trimCharsStart("./"),
|
||||
|
@ -42,26 +39,15 @@
|
|||
|
||||
<div class="root">
|
||||
{#each sortedScreens as screen}
|
||||
<div
|
||||
class="budibase__nav-item screen-header-row"
|
||||
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
||||
on:click|stopPropagation={() => changeScreen(screen)}>
|
||||
<span
|
||||
class="icon"
|
||||
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}>
|
||||
{#if screen.props._children.length}
|
||||
<ArrowDownIcon />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<i class="ri-artboard-2-fill icon" />
|
||||
|
||||
<span class="title">{screen.props._instanceName}</span>
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<ScreenDropdownMenu {screen} />
|
||||
</div>
|
||||
</div>
|
||||
<NavItem
|
||||
icon="ri-artboard-2-line"
|
||||
text={screen.props._instanceName}
|
||||
withArrow={screen.props._children.length}
|
||||
selected={$store.currentComponentInfo._id === screen.props._id}
|
||||
opened={$store.currentPreviewItem.name === screen.props._id}
|
||||
on:click={() => changeScreen(screen)}>
|
||||
<ScreenDropdownMenu {screen} />
|
||||
</NavItem>
|
||||
|
||||
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
|
||||
<ComponentsHierarchyChildren
|
||||
|
@ -71,55 +57,3 @@
|
|||
{/if}
|
||||
{/each}
|
||||
</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>
|
||||
|
|
|
@ -2,14 +2,9 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
import { last } from "lodash/fp"
|
||||
import { pipe } from "components/common/core"
|
||||
import { pipe } from "../../helpers"
|
||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||
import {
|
||||
XCircleIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
CopyIcon,
|
||||
} from "../common/Icons"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||
|
||||
export let components = []
|
||||
|
@ -40,7 +35,6 @@
|
|||
|
||||
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||
const get_name = s => (!s ? "" : last(s.split("/")))
|
||||
|
||||
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
||||
const isScreenslot = name => name === "##builtin/screenslot"
|
||||
|
||||
|
@ -134,29 +128,22 @@
|
|||
on:drop={drop}
|
||||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
class="budibase__nav-item item drop-item"
|
||||
style="margin-left: {level * 20 + 40}px" />
|
||||
class="drop-item"
|
||||
style="margin-left: {(level + 1) * 18}px" />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="budibase__nav-item item"
|
||||
class:selected={currentComponent === component}
|
||||
style="padding-left: {level * 20 + 40}px"
|
||||
draggable={true}
|
||||
<NavItem
|
||||
draggable
|
||||
on:dragend={dragend}
|
||||
on:dragstart={dragstart(component)}
|
||||
on:dragover={dragover(component, index)}
|
||||
on:drop={drop}
|
||||
ondragover="return false"
|
||||
ondragenter="return false">
|
||||
<div class="nav-item">
|
||||
<i class="icon ri-arrow-right-circle-line" />
|
||||
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<ComponentDropdownMenu {component} />
|
||||
</div>
|
||||
</div>
|
||||
text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||
withArrow
|
||||
indentLevel={level + 1}
|
||||
selected={currentComponent === component}>
|
||||
<ComponentDropdownMenu {component} />
|
||||
</NavItem>
|
||||
|
||||
{#if component._children}
|
||||
<svelte:self
|
||||
|
@ -172,8 +159,8 @@
|
|||
on:drop={drop}
|
||||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
class="budibase__nav-item item drop-item"
|
||||
style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 2 : 0)) * 20 + 40}px" />
|
||||
class="drop-item"
|
||||
style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 3 : 1)) * 18}px" />
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -186,47 +173,9 @@
|
|||
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 {
|
||||
border-radius: var(--border-radius-m);
|
||||
height: 32px;
|
||||
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>
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
{/each}
|
||||
{:else}
|
||||
<div class="no-design">
|
||||
This component does not have any design properties.
|
||||
This component doesn't have any design properties.
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -61,10 +61,10 @@
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
||||
.design-view-state-categories {
|
||||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
.positioned-wrapper {
|
||||
|
@ -79,10 +79,15 @@
|
|||
min-height: 0;
|
||||
margin: 0 -20px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.no-design {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--grey-6);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-5);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,13 @@
|
|||
</script>
|
||||
|
||||
<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="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
<script>
|
||||
import { store } from "builderStore"
|
||||
import {
|
||||
TextButton,
|
||||
Button,
|
||||
Body,
|
||||
DropdownMenu,
|
||||
ModalContent,
|
||||
} from "@budibase/bbui"
|
||||
import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui"
|
||||
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 { createEventDispatcher } from "svelte"
|
||||
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
<script>
|
||||
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 { ArrowDownIcon } from "components/common/Icons/"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let parameter
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { FeedbackIcon } from "components/common/Icons/"
|
||||
import { Popover } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
|
||||
|
@ -16,41 +15,37 @@
|
|||
}, FIVE_MINUTES)
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="container"
|
||||
bind:this={iconContainer}
|
||||
on:click={popover.show}
|
||||
class:highlight={$store.highlightFeedbackIcon}>
|
||||
<FeedbackIcon />
|
||||
</span>
|
||||
<div class="container" bind:this={iconContainer} on:click={popover.show}>
|
||||
<i class="ri-feedback-line" class:highlight={$store.highlightFeedbackIcon} />
|
||||
</div>
|
||||
<Popover bind:this={popover} anchor={iconContainer} align="right">
|
||||
<FeedbackIframe on:finished={popover.hide} />
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
i {
|
||||
font-size: 18px;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
i.highlight {
|
||||
color: var(--blue);
|
||||
filter: drop-shadow(0 0 20px var(--blue));
|
||||
}
|
||||
|
||||
.container {
|
||||
cursor: pointer;
|
||||
color: var(--grey-7);
|
||||
margin: 0 20px 0 0;
|
||||
margin: 0 12px 0 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.container:hover {
|
||||
.container:hover i {
|
||||
color: var(--ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.highlight > :global(svg) {
|
||||
filter: drop-shadow(0 0 20px var(--blue));
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,23 +22,22 @@
|
|||
<style>
|
||||
.flatbutton {
|
||||
cursor: pointer;
|
||||
max-height: 36px;
|
||||
padding: 8px 2px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
background: white;
|
||||
color: var(--grey-7);
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
border-radius: var(--border-radius-m);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
background: var(--grey-2);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,22 +4,41 @@
|
|||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
||||
import PagesList from "components/userInterface/PagesList.svelte"
|
||||
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
||||
import { Button, Spacer, Modal } from "@budibase/bbui"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
<h1>Screens</h1>
|
||||
<i on:click={modal.show} data-cy="new-screen" class="ri-add-circle-fill" />
|
||||
</div>
|
||||
<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">
|
||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||
<ComponentsHierarchy screens={$store.screens} />
|
||||
</div>
|
||||
|
||||
<Modal bind:this={modal}>
|
||||
<NewScreenModal />
|
||||
</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>
|
||||
|
|
|
@ -1,291 +1,291 @@
|
|||
<script>
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import { createEventDispatcher, tick } from "svelte"
|
||||
|
||||
import icons from "./icons.js"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let maxIconsPerPage = 30
|
||||
|
||||
let searchTerm = ""
|
||||
let selectedLetter = "A"
|
||||
|
||||
let currentPage = 1
|
||||
let filteredIcons = findIconByTerm(selectedLetter)
|
||||
|
||||
$: dispatch("change", value)
|
||||
|
||||
const alphabet = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
]
|
||||
let buttonAnchor, dropdown
|
||||
let loading = false
|
||||
|
||||
function findIconByTerm(term) {
|
||||
const r = new RegExp(`\^${term}`, "i")
|
||||
return icons.filter(i => r.test(i.label))
|
||||
}
|
||||
|
||||
async function switchLetter(letter) {
|
||||
currentPage = 1
|
||||
searchTerm = ""
|
||||
loading = true
|
||||
selectedLetter = letter
|
||||
filteredIcons = findIconByTerm(letter)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function findIconOnPage() {
|
||||
loading = true
|
||||
const iconIdx = filteredIcons.findIndex(i => i.value === value)
|
||||
if (iconIdx !== -1) {
|
||||
currentPage = Math.ceil(iconIdx / maxIconsPerPage)
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function setSelectedUI() {
|
||||
if (value) {
|
||||
const letter = displayValue.substring(0, 1)
|
||||
await switchLetter(letter)
|
||||
await findIconOnPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function pageClick(next) {
|
||||
loading = true
|
||||
if (next && currentPage < totalPages) {
|
||||
currentPage++
|
||||
} else if (!next && currentPage > 1) {
|
||||
currentPage--
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function searchForIcon(e) {
|
||||
currentPage = 1
|
||||
loading = true
|
||||
filteredIcons = findIconByTerm(searchTerm)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
$: displayValue = value ? value.substring(7) : "Pick Icon"
|
||||
|
||||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
|
||||
$: pageEndIdx = maxIconsPerPage * currentPage
|
||||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx)
|
||||
|
||||
$: pagerText = `Page ${currentPage} of ${totalPages}`
|
||||
</script>
|
||||
|
||||
<div bind:this={buttonAnchor}>
|
||||
<Button secondary on:click={dropdown.show}>{displayValue}</Button>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={dropdown}
|
||||
on:open={setSelectedUI}
|
||||
anchor={buttonAnchor}>
|
||||
<div class="container">
|
||||
<div class="search-area">
|
||||
<div class="alphabet-area">
|
||||
{#each alphabet as letter, idx}
|
||||
<span
|
||||
class="letter"
|
||||
class:letter-selected={letter === selectedLetter}
|
||||
on:click={() => switchLetter(letter)}>
|
||||
{letter}
|
||||
</span>
|
||||
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
<div class="input-wrapper">
|
||||
<Input bind:value={searchTerm} thin placeholder="Search Icon" />
|
||||
</div>
|
||||
<Button secondary on:click={searchForIcon}>Search</Button>
|
||||
</div>
|
||||
<div class="page-area">
|
||||
<div class="pager">
|
||||
<span on:click={() => pageClick(false)}>
|
||||
<i class="page-btn fas fa-chevron-left" />
|
||||
</span>
|
||||
<span>{pagerText}</span>
|
||||
<span on:click={() => pageClick(true)}>
|
||||
<i class="page-btn fas fa-chevron-right" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if pagedIcons.length > 0}
|
||||
<div class="icon-area">
|
||||
{#if !loading}
|
||||
{#each pagedIcons as icon}
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === icon.value}
|
||||
on:click={() => (value = icon.value)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`${icon.value} fa-3x`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon.label}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-icons">
|
||||
<h5>
|
||||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`}
|
||||
</h5>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 610px;
|
||||
height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0px 10px 15px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.icon-area {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.no-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alphabet-area {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 15px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.page-area {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.letter {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.letter:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.letter-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: var(--border-dark);
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
cursor: pointer;
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import { createEventDispatcher, tick } from "svelte"
|
||||
|
||||
import icons from "./icons.js"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let maxIconsPerPage = 30
|
||||
|
||||
let searchTerm = ""
|
||||
let selectedLetter = "A"
|
||||
|
||||
let currentPage = 1
|
||||
let filteredIcons = findIconByTerm(selectedLetter)
|
||||
|
||||
$: dispatch("change", value)
|
||||
|
||||
const alphabet = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
]
|
||||
let buttonAnchor, dropdown
|
||||
let loading = false
|
||||
|
||||
function findIconByTerm(term) {
|
||||
const r = new RegExp(`\^${term}`, "i")
|
||||
return icons.filter(i => r.test(i.label))
|
||||
}
|
||||
|
||||
async function switchLetter(letter) {
|
||||
currentPage = 1
|
||||
searchTerm = ""
|
||||
loading = true
|
||||
selectedLetter = letter
|
||||
filteredIcons = findIconByTerm(letter)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function findIconOnPage() {
|
||||
loading = true
|
||||
const iconIdx = filteredIcons.findIndex(i => i.value === value)
|
||||
if (iconIdx !== -1) {
|
||||
currentPage = Math.ceil(iconIdx / maxIconsPerPage)
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function setSelectedUI() {
|
||||
if (value) {
|
||||
const letter = displayValue.substring(0, 1)
|
||||
await switchLetter(letter)
|
||||
await findIconOnPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function pageClick(next) {
|
||||
loading = true
|
||||
if (next && currentPage < totalPages) {
|
||||
currentPage++
|
||||
} else if (!next && currentPage > 1) {
|
||||
currentPage--
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function searchForIcon(e) {
|
||||
currentPage = 1
|
||||
loading = true
|
||||
filteredIcons = findIconByTerm(searchTerm)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
$: displayValue = value ? value.substring(7) : "Pick Icon"
|
||||
|
||||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
|
||||
$: pageEndIdx = maxIconsPerPage * currentPage
|
||||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx)
|
||||
|
||||
$: pagerText = `Page ${currentPage} of ${totalPages}`
|
||||
</script>
|
||||
|
||||
<div bind:this={buttonAnchor}>
|
||||
<Button secondary small on:click={dropdown.show}>{displayValue}</Button>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={dropdown}
|
||||
on:open={setSelectedUI}
|
||||
anchor={buttonAnchor}>
|
||||
<div class="container">
|
||||
<div class="search-area">
|
||||
<div class="alphabet-area">
|
||||
{#each alphabet as letter, idx}
|
||||
<span
|
||||
class="letter"
|
||||
class:letter-selected={letter === selectedLetter}
|
||||
on:click={() => switchLetter(letter)}>
|
||||
{letter}
|
||||
</span>
|
||||
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
<div class="input-wrapper">
|
||||
<Input bind:value={searchTerm} thin placeholder="Search Icon" />
|
||||
</div>
|
||||
<Button secondary on:click={searchForIcon}>Search</Button>
|
||||
</div>
|
||||
<div class="page-area">
|
||||
<div class="pager">
|
||||
<span on:click={() => pageClick(false)}>
|
||||
<i class="page-btn fas fa-chevron-left" />
|
||||
</span>
|
||||
<span>{pagerText}</span>
|
||||
<span on:click={() => pageClick(true)}>
|
||||
<i class="page-btn fas fa-chevron-right" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if pagedIcons.length > 0}
|
||||
<div class="icon-area">
|
||||
{#if !loading}
|
||||
{#each pagedIcons as icon}
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === icon.value}
|
||||
on:click={() => (value = icon.value)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`${icon.value} fa-3x`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon.label}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-icons">
|
||||
<h5>
|
||||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`}
|
||||
</h5>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 610px;
|
||||
height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0px 10px 15px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.icon-area {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.no-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alphabet-area {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 15px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.page-area {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.letter {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.letter:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.letter-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: var(--border-dark);
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
cursor: pointer;
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -13,6 +13,7 @@
|
|||
let select
|
||||
let selectMenu
|
||||
let icon
|
||||
let width = 0
|
||||
|
||||
let selectAnchor = null
|
||||
let dimensions = { top: 0, bottom: 0, left: 0 }
|
||||
|
@ -91,6 +92,7 @@
|
|||
"transform-origin": `center ${positionSide}`,
|
||||
[positionSide]: `${dimensions[positionSide]}px`,
|
||||
left: `${dimensions.left.toFixed(0)}px`,
|
||||
width: `${width}px`,
|
||||
})
|
||||
|
||||
$: isOptionsObject = options.every(o => typeof o === "object")
|
||||
|
@ -108,6 +110,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
bind:clientWidth={width}
|
||||
tabindex="0"
|
||||
bind:this={select}
|
||||
class="bb-select-container"
|
||||
|
@ -164,19 +167,17 @@
|
|||
|
||||
.bb-select-container {
|
||||
outline: none;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.bb-select-anchor {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 0px 12px;
|
||||
height: 36px;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background-color: var(--grey-2);
|
||||
border-radius: 5px;
|
||||
border-radius: var(--border-radius-m);
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -184,8 +185,11 @@
|
|||
.bb-select-anchor > span {
|
||||
color: var(--ink);
|
||||
font-weight: 400;
|
||||
width: 140px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: var(--font-size-xs);
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.bb-select-anchor > i {
|
||||
|
@ -208,7 +212,6 @@
|
|||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
opacity: 0;
|
||||
width: 160px;
|
||||
z-index: 2;
|
||||
color: var(--ink);
|
||||
font-weight: 400;
|
||||
|
@ -237,7 +240,7 @@
|
|||
padding: 5px 0px;
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
font-size: var(--font-size-s);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
|
|
|
@ -1,21 +1,9 @@
|
|||
<script>
|
||||
import { goto } from "@sveltech/routify"
|
||||
// import { tick } from "svelte"
|
||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
||||
|
||||
import {
|
||||
last,
|
||||
sortBy,
|
||||
map,
|
||||
trimCharsStart,
|
||||
trimChars,
|
||||
join,
|
||||
compose,
|
||||
} from "lodash/fp"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { pipe } from "components/common/core"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { last } from "lodash/fp"
|
||||
import { store } from "builderStore"
|
||||
import { ArrowDownIcon, GridIcon } from "components/common/Icons/"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
export let layout
|
||||
|
@ -24,13 +12,10 @@
|
|||
let componentToDelete = ""
|
||||
|
||||
const dragDropStore = writable({})
|
||||
const joinPath = join("/")
|
||||
|
||||
const lastPartOfName = c =>
|
||||
c && last(c.name ? c.name.split("/") : c._component.split("/"))
|
||||
|
||||
const isComponentSelected = (current, comp) => current === comp
|
||||
|
||||
$: _layout = {
|
||||
component: layout,
|
||||
title: lastPartOfName(layout),
|
||||
|
@ -42,18 +27,14 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="budibase__nav-item root"
|
||||
class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
|
||||
on:click|stopPropagation={setCurrentScreenToLayout}>
|
||||
<span
|
||||
class="icon"
|
||||
class:rotate={$store.currentPreviewItem.name !== _layout.title}>
|
||||
<ArrowDownIcon />
|
||||
</span>
|
||||
<i class="ri-layout-3-fill icon-big" />
|
||||
<span class="title">Master Screen</span>
|
||||
</div>
|
||||
<NavItem
|
||||
border={false}
|
||||
icon="ri-layout-3-line"
|
||||
text="Master Screen"
|
||||
withArrow
|
||||
selected={$store.currentComponentInfo._id === _layout.component.props._id}
|
||||
opened={$store.currentPreviewItem.name === _layout.title}
|
||||
on:click={setCurrentScreenToLayout} />
|
||||
|
||||
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
|
||||
<ComponentsHierarchyChildren
|
||||
|
@ -62,29 +43,3 @@
|
|||
currentComponent={$store.currentComponentInfo}
|
||||
{dragDropStore} />
|
||||
{/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>
|
||||
|
|
|
@ -43,24 +43,23 @@
|
|||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 0px 16px;
|
||||
height: 36px;
|
||||
padding: 0 var(--spacing-m);
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
color: var(--grey-7);
|
||||
border-radius: 5px;
|
||||
font-family: inter;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-rendering: optimizeLegibility;
|
||||
border: none !important;
|
||||
transition: 0.2s;
|
||||
outline: none;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--grey-3);
|
||||
background: var(--grey-2);
|
||||
color: var(--ink);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -119,16 +119,14 @@
|
|||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 8px 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
flex: 0 0 100px;
|
||||
flex: 0 0 80px;
|
||||
text-align: left;
|
||||
color: var(--ink);
|
||||
margin-right: auto;
|
||||
|
@ -149,7 +147,7 @@
|
|||
height: 90%;
|
||||
width: 2rem;
|
||||
background: var(--grey-2);
|
||||
right: 10px;
|
||||
right: 4px;
|
||||
--spacing-s: 0;
|
||||
border-left: 0.5px solid var(--grey-3);
|
||||
outline-color: var(--blue);
|
||||
|
|
|
@ -13,14 +13,26 @@
|
|||
$: style = componentInstance["_styles"][styleCategory] || {}
|
||||
</script>
|
||||
|
||||
<DetailSummary {name} on:open show={open}>
|
||||
{#each properties as props}
|
||||
<PropertyControl
|
||||
label={props.label}
|
||||
control={props.control}
|
||||
key={props.key}
|
||||
value={style[props.key]}
|
||||
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
|
||||
props={{ ...excludeProps(props, ['control', 'label']) }} />
|
||||
{/each}
|
||||
<DetailSummary {name} on:open show={open} thin>
|
||||
<div>
|
||||
{#each properties as props}
|
||||
<PropertyControl
|
||||
label={props.label}
|
||||
control={props.control}
|
||||
key={props.key}
|
||||
value={style[props.key]}
|
||||
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
|
||||
props={{ ...excludeProps(props, ['control', 'label']) }} />
|
||||
{/each}
|
||||
</div>
|
||||
</DetailSummary>
|
||||
|
||||
<style>
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
export let name, value, placeholder, type
|
||||
</script>
|
||||
|
||||
<Input {name} {value} {placeholder} {type} thin on:change />
|
||||
<Input {name} {value} {placeholder} {type} extraThin on:change />
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import api from "builderStore/api"
|
||||
import Portal from "svelte-portal"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
export let screen
|
||||
|
||||
|
@ -12,10 +11,6 @@
|
|||
let dropdown
|
||||
let anchor
|
||||
|
||||
const hideDropdown = () => {
|
||||
dropdown.hide()
|
||||
}
|
||||
|
||||
const deleteScreen = () => {
|
||||
store.deleteScreens(screen, $store.currentPageName)
|
||||
// update the page if required
|
||||
|
@ -29,79 +24,27 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={anchor}
|
||||
class="root boundary"
|
||||
on:click|stopPropagation={() => {}}>
|
||||
<div bind:this={anchor} on:click|stopPropagation>
|
||||
<div class="icon" on:click={() => dropdown.show()}>
|
||||
<i class="ri-more-line" />
|
||||
</div>
|
||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
||||
<ul on:click={hideDropdown}>
|
||||
<li on:click={() => confirmDeleteDialog.show()}>
|
||||
<i class="ri-delete-bin-2-line" />
|
||||
Delete
|
||||
</li>
|
||||
</ul>
|
||||
<DropdownContainer>
|
||||
<DropdownItem
|
||||
icon="ri-delete-bin-line"
|
||||
title="Delete"
|
||||
on:click={() => confirmDeleteDialog.show()} />
|
||||
</DropdownContainer>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
title="Confirm Delete"
|
||||
title="Confirm Deletion"
|
||||
body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`}
|
||||
okText="Delete Screen"
|
||||
onOk={deleteScreen} />
|
||||
|
||||
<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 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
|
|
@ -1,88 +1,94 @@
|
|||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
||||
$: urls = getUrls()
|
||||
|
||||
const handleBlur = () => dispatch("change", value)
|
||||
|
||||
// this will get urls of all screens, but only
|
||||
// choose detail screens that are usable in the current context
|
||||
// and substitute the :id param for the actual {{ ._id }} binding
|
||||
const getUrls = () => {
|
||||
const urls = [
|
||||
...$store.screens
|
||||
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||
.map(screen => ({
|
||||
name: screen.props._instanceName,
|
||||
url: screen.route,
|
||||
sort: screen.props._component,
|
||||
})),
|
||||
]
|
||||
|
||||
const bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
const detailScreens = $store.screens.filter(screen =>
|
||||
screen.props._component.endsWith("/rowdetail")
|
||||
)
|
||||
|
||||
for (let detailScreen of detailScreens) {
|
||||
const idBinding = bindableProperties.find(p => {
|
||||
if (
|
||||
p.type === "context" &&
|
||||
p.runtimeBinding.endsWith("._id") &&
|
||||
p.table
|
||||
) {
|
||||
const tableId =
|
||||
typeof p.table === "string" ? p.table : p.table.tableId
|
||||
return tableId === detailScreen.props.table
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (idBinding) {
|
||||
urls.push({
|
||||
name: detailScreen.props._instanceName,
|
||||
url: detailScreen.route.replace(
|
||||
":id",
|
||||
`{{ ${idBinding.runtimeBinding} }}`
|
||||
),
|
||||
sort: detailScreen.props._component,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value>
|
||||
<option value="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div :global(> div) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
||||
$: urls = getUrls()
|
||||
|
||||
const handleBlur = () => dispatch("change", value)
|
||||
|
||||
// this will get urls of all screens, but only
|
||||
// choose detail screens that are usable in the current context
|
||||
// and substitute the :id param for the actual {{ ._id }} binding
|
||||
const getUrls = () => {
|
||||
const urls = [
|
||||
...$store.screens
|
||||
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||
.map(screen => ({
|
||||
name: screen.props._instanceName,
|
||||
url: screen.route,
|
||||
sort: screen.props._component,
|
||||
})),
|
||||
]
|
||||
|
||||
const bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
const detailScreens = $store.screens.filter(screen =>
|
||||
screen.props._component.endsWith("/rowdetail")
|
||||
)
|
||||
|
||||
for (let detailScreen of detailScreens) {
|
||||
const idBinding = bindableProperties.find(p => {
|
||||
if (
|
||||
p.type === "context" &&
|
||||
p.runtimeBinding.endsWith("._id") &&
|
||||
p.table
|
||||
) {
|
||||
const tableId =
|
||||
typeof p.table === "string" ? p.table : p.table.tableId
|
||||
return tableId === detailScreen.props.table
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (idBinding) {
|
||||
urls.push({
|
||||
name: detailScreen.props._instanceName,
|
||||
url: detailScreen.route.replace(
|
||||
":id",
|
||||
`{{ ${idBinding.runtimeBinding} }}`
|
||||
),
|
||||
sort: detailScreen.props._component,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<DataList
|
||||
editable
|
||||
secondary
|
||||
extraThin
|
||||
on:blur={handleBlur}
|
||||
on:change
|
||||
bind:value>
|
||||
<option value="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div :global(> div) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -85,53 +85,65 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if screenOrPageInstance}
|
||||
{#each screenOrPageDefinition as def}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
value={screenOrPageInstance[def.key]}
|
||||
onChange={onScreenPropChange}
|
||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#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 panelDefinition && panelDefinition.length > 0}
|
||||
{#each panelDefinition as definition}
|
||||
{#if canRenderControl(definition.key, definition.dependsOn)}
|
||||
<div class="settings-view-container">
|
||||
{#if screenOrPageInstance}
|
||||
{#each screenOrPageDefinition as def}
|
||||
<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']) }} />
|
||||
bindable={false}
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
value={screenOrPageInstance[def.key]}
|
||||
onChange={onScreenPropChange}
|
||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#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}
|
||||
{/each}
|
||||
{:else}
|
||||
<div>This component does not have any settings.</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if panelDefinition && panelDefinition.length > 0}
|
||||
{#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>
|
||||
div {
|
||||
font-size: var(--font-size-s);
|
||||
.settings-view-container {
|
||||
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);
|
||||
color: var(--grey-6);
|
||||
color: var(--grey-5);
|
||||
}
|
||||
|
||||
.duplicate-name {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</script>
|
||||
|
||||
<div>
|
||||
<Select thin secondary wide on:change {value}>
|
||||
<Select extraThin secondary wide on:change {value}>
|
||||
<option value="">Choose a table</option>
|
||||
{#each $backendUiStore.tables as table}
|
||||
<option value={table._id}>{table.name}</option>
|
||||
|
|
|
@ -1,163 +1,163 @@
|
|||
<script>
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
||||
export let value = {}
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
$: tables = $backendUiStore.tables.map(m => ({
|
||||
label: m.name,
|
||||
name: `all_${m._id}`,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
}))
|
||||
|
||||
$: views = $backendUiStore.tables.reduce((acc, cur) => {
|
||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
||||
label: key,
|
||||
name: key,
|
||||
...value,
|
||||
type: "view",
|
||||
}))
|
||||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema.type === "link")
|
||||
.map(property => ({
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
name: `all_${property.fieldSchema.tableId}`,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
type: "link",
|
||||
}))
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each tables as table}
|
||||
<li
|
||||
class:selected={value === table}
|
||||
on:click={() => handleSelected(table)}>
|
||||
{table.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li
|
||||
class:selected={value === view}
|
||||
on:click={() => handleSelected(view)}>
|
||||
{view.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li
|
||||
class:selected={value === link}
|
||||
on:click={() => handleSelected(link)}>
|
||||
{link.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.dropdownbutton {
|
||||
background-color: var(--grey-2);
|
||||
border: var(--border-transparent);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--border-radius-m);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dropdownbutton:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--grey-3);
|
||||
}
|
||||
.dropdownbutton span {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.dropdownbutton :global(svg) {
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
padding: var(--spacing-m) 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
.title {
|
||||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-m) 0 var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
||||
export let value = {}
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
$: tables = $backendUiStore.tables.map(m => ({
|
||||
label: m.name,
|
||||
name: `all_${m._id}`,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
}))
|
||||
|
||||
$: views = $backendUiStore.tables.reduce((acc, cur) => {
|
||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
||||
label: key,
|
||||
name: key,
|
||||
...value,
|
||||
type: "view",
|
||||
}))
|
||||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => ({
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
name: `all_${property.fieldSchema.tableId}`,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
type: "link",
|
||||
}))
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each tables as table}
|
||||
<li
|
||||
class:selected={value === table}
|
||||
on:click={() => handleSelected(table)}>
|
||||
{table.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li
|
||||
class:selected={value === view}
|
||||
on:click={() => handleSelected(view)}>
|
||||
{view.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li
|
||||
class:selected={value === link}
|
||||
on:click={() => handleSelected(link)}>
|
||||
{link.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.dropdownbutton {
|
||||
background-color: var(--grey-2);
|
||||
border: var(--border-transparent);
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
border-radius: var(--border-radius-m);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dropdownbutton:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--grey-3);
|
||||
}
|
||||
.dropdownbutton span {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.dropdownbutton :global(svg) {
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
padding: var(--spacing-m) 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
.title {
|
||||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-m) 0 var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -1,5 +1,5 @@
|
|||
import { isUndefined, filter, some, includes } from "lodash/fp"
|
||||
import { pipe } from "components/common/core"
|
||||
import { pipe } from "../../../helpers"
|
||||
|
||||
const normalString = s => (s || "").trim().toLowerCase()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { split, last } from "lodash/fp"
|
||||
import { pipe } from "components/common/core"
|
||||
import { pipe } from "../../../helpers"
|
||||
|
||||
export const splitName = fullname => {
|
||||
const componentName = pipe(fullname, [split("/"), last])
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
import { last } from "lodash/fp"
|
||||
import { pipe } from "components/common/core"
|
||||
import { last, flow } from "lodash/fp"
|
||||
|
||||
export const buildStyle = styles => {
|
||||
let str = ""
|
||||
for (let s in styles) {
|
||||
|
@ -15,6 +15,8 @@ export const convertCamel = str => {
|
|||
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 get_name = s => (!s ? "" : last(s.split("/")))
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</head>
|
||||
|
||||
<body id="app">
|
||||
|
||||
|
||||
<script src='/_builder/bundle.js'></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -67,23 +67,27 @@
|
|||
</div>
|
||||
<div class="toprightnav">
|
||||
<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 />
|
||||
<span
|
||||
class:active={false}
|
||||
class="topnavitemright"
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => {
|
||||
document.cookie = 'budibase:token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'
|
||||
window.open(`/${application}`)
|
||||
}}>
|
||||
<PreviewIcon />
|
||||
</span>
|
||||
<span class="topnavitemright">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/Budibase/budibase/discussions">
|
||||
<i class="ri-question-fill help" />
|
||||
</a>
|
||||
</span>
|
||||
Preview
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="beta">
|
||||
|
@ -122,7 +126,7 @@
|
|||
flex: 0 0 auto;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 0px 20px 0 20px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
|
@ -168,20 +172,19 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.topnavitemright {
|
||||
.topnavitemright a {
|
||||
cursor: pointer;
|
||||
color: var(--grey-7);
|
||||
margin: 0 20px 0 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
margin: 0 12px 0 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.topnavitemright:hover {
|
||||
.topnavitemright a:hover {
|
||||
color: var(--ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
@ -207,10 +210,13 @@
|
|||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 24px;
|
||||
i {
|
||||
font-size: 18px;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
i:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.beta {
|
||||
position: absolute;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue