merge from master - including fixes
This commit is contained in:
parent
a3bd28d99f
commit
027819bae0
|
@ -15,8 +15,12 @@ context("Create a automation", () => {
|
||||||
|
|
||||||
cy.contains("automate").click()
|
cy.contains("automate").click()
|
||||||
cy.contains("Create New Automation").click()
|
cy.contains("Create New Automation").click()
|
||||||
cy.get("input").type("Add Record")
|
cy.get(".modal").within(() => {
|
||||||
cy.contains("Save").click()
|
cy.get("input").type("Add Record")
|
||||||
|
cy.get(".buttons")
|
||||||
|
.contains("Create")
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
|
||||||
// Add trigger
|
// Add trigger
|
||||||
cy.get("[data-cy=add-automation-component]").click()
|
cy.get("[data-cy=add-automation-component]").click()
|
||||||
|
@ -46,7 +50,6 @@ context("Create a automation", () => {
|
||||||
|
|
||||||
// Activate Automation
|
// Activate Automation
|
||||||
cy.get("[data-cy=activate-automation]").click()
|
cy.get("[data-cy=activate-automation]").click()
|
||||||
cy.contains("Add Record").should("be.visible")
|
|
||||||
cy.get(".stop-button.highlighted").should("be.visible")
|
cy.get(".stop-button.highlighted").should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,70 +1,65 @@
|
||||||
context('Create a Table', () => {
|
context("Create a Table", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('localhost:4001/_builder')
|
cy.visit("localhost:4001/_builder")
|
||||||
cy.createApp('Table App', 'Table App Description')
|
cy.createApp("Table App", "Table App Description")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create a new Table', () => {
|
it("should create a new Table", () => {
|
||||||
cy.createTable('dog')
|
cy.createTable("dog")
|
||||||
|
|
||||||
// Check if Table exists
|
// Check if Table exists
|
||||||
cy.get('.title').should('contain.text', 'dog')
|
cy.get(".title span").should("have.text", "dog")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a new column to the table', () => {
|
it("adds a new column to the table", () => {
|
||||||
cy.addColumn('dog', 'name', 'Plain Text')
|
cy.addColumn("dog", "name", "Text")
|
||||||
|
cy.contains("name").should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
cy.contains('name').should("be.visible")
|
it("creates a record in the table", () => {
|
||||||
})
|
cy.addRecord(["Rover"])
|
||||||
|
cy.contains("Rover").should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
it('creates a record in the table', () => {
|
it("updates a column on the table", () => {
|
||||||
cy.addRecord(["Rover"])
|
cy.contains("name").click()
|
||||||
|
cy.get("[data-cy='edit-column-header']").click()
|
||||||
|
cy.get(".actions input")
|
||||||
|
.first()
|
||||||
|
.type("updated")
|
||||||
|
cy.get("select").select("Text")
|
||||||
|
cy.contains("Save Column").click()
|
||||||
|
cy.contains("nameupdated").should("have.text", "nameupdated")
|
||||||
|
})
|
||||||
|
|
||||||
cy.contains('Rover').should("be.visible")
|
it("edits a record", () => {
|
||||||
})
|
cy.get("tbody .ri-more-line").click()
|
||||||
|
cy.get("[data-cy=edit-row]").click()
|
||||||
|
cy.get(".modal input").type("Updated")
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
|
||||||
|
})
|
||||||
|
|
||||||
it('updates a column on the table', () => {
|
it("deletes a record", () => {
|
||||||
cy.contains("name").click()
|
cy.get("tbody .ri-more-line").click()
|
||||||
cy.get("[data-cy='edit-column-header']").click()
|
cy.get("[data-cy=delete-row]").click()
|
||||||
|
cy.contains("Delete Row").click()
|
||||||
|
cy.contains("RoverUpdated").should("not.exist")
|
||||||
|
})
|
||||||
|
|
||||||
cy.get("[placeholder=Name]").type("updated")
|
it("deletes a column", () => {
|
||||||
cy.get("select").select("Plain Text")
|
cy.contains("name").click()
|
||||||
|
cy.get("[data-cy='delete-column-header']").click()
|
||||||
cy.contains("Save Column").click()
|
cy.contains("Delete Column").click()
|
||||||
|
cy.contains("nameupdated").should("not.exist")
|
||||||
cy.contains('nameupdated').should('have.text', 'nameupdated ')
|
})
|
||||||
})
|
|
||||||
|
|
||||||
it('edits a record', () => {
|
|
||||||
cy.get("tbody .ri-more-line").click()
|
|
||||||
cy.get("[data-cy=edit-row]").click()
|
|
||||||
cy.get(".actions input").type("Updated")
|
|
||||||
cy.contains("Save").click()
|
|
||||||
|
|
||||||
cy.contains('RoverUpdated').should('have.text', 'RoverUpdated')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('deletes a record', () => {
|
|
||||||
cy.get("tbody .ri-more-line").click()
|
|
||||||
cy.get("[data-cy=delete-row]").click()
|
|
||||||
cy.get(".modal-actions").contains("Delete").click()
|
|
||||||
|
|
||||||
cy.contains('RoverUpdated').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('deletes a column', () => {
|
|
||||||
cy.contains("name").click()
|
|
||||||
cy.get("[data-cy='delete-column-header']").click()
|
|
||||||
|
|
||||||
cy.contains('nameupdated').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('deletes a table', () => {
|
|
||||||
cy.contains("div", "dog").get(".ri-more-line").click()
|
|
||||||
cy.get("[data-cy=delete-table]").click()
|
|
||||||
cy.get(".modal-actions").contains("Delete").click()
|
|
||||||
|
|
||||||
cy.contains('dog').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
it("deletes a table", () => {
|
||||||
|
cy.contains("div", "dog")
|
||||||
|
.get(".ri-more-line")
|
||||||
|
.click()
|
||||||
|
cy.get("[data-cy=delete-table]").click()
|
||||||
|
cy.contains("Delete Table").click()
|
||||||
|
cy.contains("dog").should("not.exist")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,91 +1,100 @@
|
||||||
|
context("Create a View", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit("localhost:4001/_builder")
|
||||||
|
cy.createApp("View App", "View App Description")
|
||||||
|
cy.createTable("data")
|
||||||
|
cy.addColumn("data", "group", "Text")
|
||||||
|
cy.addColumn("data", "age", "Number")
|
||||||
|
cy.addColumn("data", "rating", "Number")
|
||||||
|
|
||||||
context('Create a View', () => {
|
// 6 Records
|
||||||
before(() => {
|
cy.addRecord(["Students", 25, 1])
|
||||||
cy.visit('localhost:4001/_builder')
|
cy.addRecord(["Students", 20, 3])
|
||||||
cy.createApp('View App', 'View App Description')
|
cy.addRecord(["Students", 18, 6])
|
||||||
cy.createTable('data')
|
cy.addRecord(["Students", 25, 2])
|
||||||
cy.addColumn('data', 'group', 'Plain Text')
|
cy.addRecord(["Teachers", 49, 5])
|
||||||
cy.addColumn('data', 'age', 'Number')
|
cy.addRecord(["Teachers", 36, 3])
|
||||||
cy.addColumn('data', 'rating', 'Number')
|
})
|
||||||
|
|
||||||
// 6 Records
|
it("creates a view", () => {
|
||||||
cy.addRecord(["Students", 25, 1])
|
cy.contains("Create New View").click()
|
||||||
cy.addRecord(["Students", 20, 3])
|
cy.get(".menu-container").within(() => {
|
||||||
cy.addRecord(["Students", 18, 6])
|
cy.get("input").type("Test View")
|
||||||
cy.addRecord(["Students", 25, 2])
|
|
||||||
cy.addRecord(["Teachers", 49, 5])
|
|
||||||
cy.addRecord(["Teachers", 36, 3])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('creates a view', () => {
|
|
||||||
cy.contains("Create New View").click()
|
|
||||||
cy.get("[placeholder='View Name']").type("Test View")
|
|
||||||
cy.contains("Save View").click()
|
cy.contains("Save View").click()
|
||||||
cy.get(".title").contains("Test View")
|
|
||||||
cy.get("thead th").should(($headers) => {
|
|
||||||
expect($headers).to.have.length(3)
|
|
||||||
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
|
||||||
expect(headers.get()).to.deep.eq([
|
|
||||||
"group",
|
|
||||||
"age",
|
|
||||||
"rating"
|
|
||||||
])
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
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("input[placeholder='age']").type(18)
|
|
||||||
cy.contains("Save").click()
|
|
||||||
cy.get("tbody tr").should(($values) => {
|
|
||||||
expect($values).to.have.length(5)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates a stats calculation view based on age', () => {
|
|
||||||
cy.contains("Calculate").click()
|
|
||||||
// we may reinstate this - have commented this dropdown for now as there is only one option
|
|
||||||
//cy.get(".menu-container").find("select").first().select("Statistics")
|
|
||||||
cy.get(".menu-container").find("select").eq(0).select("age")
|
|
||||||
cy.contains("Save").click()
|
|
||||||
cy.get("thead th").should(($headers) => {
|
|
||||||
expect($headers).to.have.length(7)
|
|
||||||
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
|
||||||
expect(headers.get()).to.deep.eq([
|
|
||||||
"field",
|
|
||||||
"sum",
|
|
||||||
"min",
|
|
||||||
"max",
|
|
||||||
"count",
|
|
||||||
"sumsqr",
|
|
||||||
"avg",
|
|
||||||
])
|
|
||||||
})
|
|
||||||
cy.get("tbody td").should(($values) => {
|
|
||||||
const values = $values.map((i, value) => Cypress.$(value).text())
|
|
||||||
expect(values.get()).to.deep.eq([
|
|
||||||
"age",
|
|
||||||
"155",
|
|
||||||
"20",
|
|
||||||
"49",
|
|
||||||
"5",
|
|
||||||
"5347",
|
|
||||||
"31"
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
cy.get(".title").contains("Test View")
|
||||||
|
cy.get("thead th div").should($headers => {
|
||||||
|
expect($headers).to.have.length(3)
|
||||||
|
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
||||||
|
expect(headers.get()).to.deep.eq(["group", "age", "rating"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('groups the view by group', () => {
|
it("filters the view by age over 10", () => {
|
||||||
cy.contains("Group By").click()
|
cy.contains("Filter").click()
|
||||||
cy.get("select").select("group")
|
cy.contains("Add Filter").click()
|
||||||
cy.contains("Save").click()
|
cy.get(".menu-container")
|
||||||
cy.contains("Students").should("be.visible")
|
.find("select")
|
||||||
cy.contains("Teachers").should("be.visible")
|
.first()
|
||||||
|
.select("age")
|
||||||
|
cy.get(".menu-container")
|
||||||
|
.find("select")
|
||||||
|
.eq(1)
|
||||||
|
.select("More Than")
|
||||||
|
cy.get("input").type(18)
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.get("tbody tr").should($values => {
|
||||||
|
expect($values).to.have.length(5)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
cy.get("tbody tr").first().find("td").should(($values) => {
|
it("creates a stats calculation view based on age", () => {
|
||||||
|
cy.contains("Calculate").click()
|
||||||
|
// we may reinstate this - have commented this dropdown for now as there is only one option
|
||||||
|
//cy.get(".menu-container").find("select").first().select("Statistics")
|
||||||
|
cy.get(".menu-container")
|
||||||
|
.find("select")
|
||||||
|
.eq(0)
|
||||||
|
.select("age")
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.get("thead th div").should($headers => {
|
||||||
|
expect($headers).to.have.length(7)
|
||||||
|
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
||||||
|
expect(headers.get()).to.deep.eq([
|
||||||
|
"field",
|
||||||
|
"sum",
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
"count",
|
||||||
|
"sumsqr",
|
||||||
|
"avg",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
cy.get("tbody td").should($values => {
|
||||||
|
const values = $values.map((i, value) => Cypress.$(value).text())
|
||||||
|
expect(values.get()).to.deep.eq([
|
||||||
|
"age",
|
||||||
|
"155",
|
||||||
|
"20",
|
||||||
|
"49",
|
||||||
|
"5",
|
||||||
|
"5347",
|
||||||
|
"31",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("groups the view by group", () => {
|
||||||
|
cy.contains("Group By").click()
|
||||||
|
cy.get("select").select("group")
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.contains("Students").should("be.visible")
|
||||||
|
cy.contains("Teachers").should("be.visible")
|
||||||
|
|
||||||
|
cy.get("tbody tr")
|
||||||
|
.first()
|
||||||
|
.find("td")
|
||||||
|
.should($values => {
|
||||||
const values = $values.map((i, value) => Cypress.$(value).text())
|
const values = $values.map((i, value) => Cypress.$(value).text())
|
||||||
expect(values.get()).to.deep.eq([
|
expect(values.get()).to.deep.eq([
|
||||||
"Students",
|
"Students",
|
||||||
|
@ -94,24 +103,30 @@ context('Create a View', () => {
|
||||||
"25",
|
"25",
|
||||||
"3",
|
"3",
|
||||||
"1650",
|
"1650",
|
||||||
"23.333333333333332"
|
"23.333333333333332",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renames a view', () => {
|
it("renames a view", () => {
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View").find(".ri-more-line").click()
|
cy.contains("[data-cy=model-nav-item]", "Test View")
|
||||||
cy.contains("Edit").click()
|
.find(".ri-more-line")
|
||||||
cy.get("[placeholder='View Name']").type(" Updated")
|
.click()
|
||||||
|
cy.contains("Edit").click()
|
||||||
|
cy.get(".menu-container").within(() => {
|
||||||
|
cy.get("input").type(" Updated")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
cy.contains("Test View Updated").should("be.visible")
|
|
||||||
})
|
})
|
||||||
|
cy.contains("Test View Updated").should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
it('deletes a view', () => {
|
it("deletes a view", () => {
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View Updated").click()
|
cy.contains("[data-cy=model-nav-item]", "Test View Updated").click()
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View Updated").find(".ri-more-line").click()
|
cy.contains("[data-cy=model-nav-item]", "Test View Updated")
|
||||||
cy.contains("Delete").click()
|
.find(".ri-more-line")
|
||||||
cy.get(".content").contains("button", "Delete").click()
|
.click()
|
||||||
cy.contains("TestView Updated").should("not.be.visible")
|
cy.contains("Delete").click()
|
||||||
})
|
cy.contains("Delete View").click()
|
||||||
|
cy.contains("TestView Updated").should("not.be.visible")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -59,16 +59,21 @@ Cypress.Commands.add("createApp", name => {
|
||||||
|
|
||||||
Cypress.Commands.add("createTestTableWithData", () => {
|
Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
cy.createTable("dog")
|
cy.createTable("dog")
|
||||||
cy.addColumn("dog", "name", "Plain Text")
|
cy.addColumn("dog", "name", "Text")
|
||||||
cy.addColumn("dog", "age", "Number")
|
cy.addColumn("dog", "age", "Number")
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTable", tableName => {
|
Cypress.Commands.add("createTable", tableName => {
|
||||||
// Enter model name
|
// Enter model name
|
||||||
cy.contains("Create New Table").click()
|
cy.contains("Create New Table").click()
|
||||||
cy.get("[placeholder='Table Name']").type(tableName)
|
cy.get(".modal").within(() => {
|
||||||
|
cy.get("input")
|
||||||
cy.contains("Save").click()
|
.first()
|
||||||
|
.type(tableName)
|
||||||
|
cy.get(".buttons")
|
||||||
|
.contains("Create")
|
||||||
|
.click()
|
||||||
|
})
|
||||||
cy.contains(tableName).should("be.visible")
|
cy.contains(tableName).should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -77,25 +82,31 @@ Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
|
||||||
cy.contains(tableName).click()
|
cy.contains(tableName).click()
|
||||||
cy.contains("Create New Column").click()
|
cy.contains("Create New Column").click()
|
||||||
|
|
||||||
cy.get("[placeholder=Name]").type(columnName)
|
// Configure column
|
||||||
cy.get("select").select(type)
|
cy.get(".menu-container").within(() => {
|
||||||
|
cy.get("input")
|
||||||
cy.contains("Save Column")
|
.first()
|
||||||
|
.type(columnName)
|
||||||
cy.contains("Save").click()
|
cy.get("select").select(type)
|
||||||
|
cy.contains("Save").click()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addRecord", values => {
|
Cypress.Commands.add("addRecord", values => {
|
||||||
cy.contains("Create New Row").click()
|
cy.contains("Create New Row").click()
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
cy.get(".modal").within(() => {
|
||||||
cy.get(".actions input")
|
for (let i = 0; i < values.length; i++) {
|
||||||
.eq(i)
|
cy.get("input")
|
||||||
.type(values[i])
|
.eq(i)
|
||||||
}
|
.type(values[i])
|
||||||
|
}
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
cy.contains("Save").click()
|
cy.get(".buttons")
|
||||||
|
.contains("Create")
|
||||||
|
.click()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
||||||
|
@ -114,7 +125,9 @@ Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
||||||
.select(accessLevel)
|
.select(accessLevel)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
cy.get(".create-button > button").click()
|
cy.get(".inputs")
|
||||||
|
.contains("Create")
|
||||||
|
.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addHeadlineComponent", text => {
|
Cypress.Commands.add("addHeadlineComponent", text => {
|
||||||
|
@ -138,12 +151,12 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||||
cy.get(".newscreen").click()
|
cy.contains("Create New Screen").click()
|
||||||
cy.get("[data-cy=new-screen-dialog] input:first").type(screenName)
|
cy.get(".modal").within(() => {
|
||||||
if (route) {
|
cy.get("input:first").type(screenName)
|
||||||
cy.get("[data-cy=new-screen-dialog] input:last").type(route)
|
if (route) {
|
||||||
}
|
cy.get("input:last").type(route)
|
||||||
cy.get("[data-cy=create-screen-footer]").within(() => {
|
}
|
||||||
cy.contains("Create Screen").click()
|
cy.contains("Create Screen").click()
|
||||||
})
|
})
|
||||||
cy.get(".nav-items-container").within(() => {
|
cy.get(".nav-items-container").within(() => {
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.34.6",
|
"@budibase/bbui": "^1.41.0",
|
||||||
"@budibase/client": "^0.1.25",
|
"@budibase/client": "^0.1.25",
|
||||||
"@budibase/colorpicker": "^1.0.1",
|
"@budibase/colorpicker": "^1.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||||
|
@ -79,7 +79,6 @@
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-loading-spinners": "^0.1.1",
|
"svelte-loading-spinners": "^0.1.1",
|
||||||
"svelte-portal": "^0.1.0",
|
"svelte-portal": "^0.1.0",
|
||||||
"svelte-simple-modal": "^0.4.2",
|
|
||||||
"yup": "^0.29.2"
|
"yup": "^0.29.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -113,7 +112,7 @@
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-url": "^2.2.2",
|
"rollup-plugin-url": "^2.2.2",
|
||||||
"start-server-and-test": "^1.11.0",
|
"start-server-and-test": "^1.11.0",
|
||||||
"svelte": "^3.24.1",
|
"svelte": "^3.29.0",
|
||||||
"svelte-jester": "^1.0.6"
|
"svelte-jester": "^1.0.6"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "svelte-simple-modal"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { Router, basepath } from "@sveltech/routify"
|
import { Router, basepath } from "@sveltech/routify"
|
||||||
import { routes } from "../routify/routes"
|
import { routes } from "../routify/routes"
|
||||||
import { store, initialise } from "builderStore"
|
import { initialise } from "builderStore"
|
||||||
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
|
@ -159,3 +159,7 @@
|
||||||
background: #fef4f6;
|
background: #fef4f6;
|
||||||
color: #f0506e;
|
color: #f0506e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
|
@ -37,9 +37,9 @@ export default function({ componentInstanceId, screen, components, models }) {
|
||||||
.filter(isInstanceInSharedContext(walkResult))
|
.filter(isInstanceInSharedContext(walkResult))
|
||||||
.map(componentInstanceToBindable(walkResult)),
|
.map(componentInstanceToBindable(walkResult)),
|
||||||
|
|
||||||
...walkResult.target._contexts
|
...(walkResult.target?._contexts
|
||||||
.map(contextToBindables(models, walkResult))
|
.map(contextToBindables(models, walkResult))
|
||||||
.flat(),
|
.flat() ?? []),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,15 @@ const componentInstanceToBindable = walkResult => i => {
|
||||||
|
|
||||||
const contextToBindables = (models, walkResult) => context => {
|
const contextToBindables = (models, walkResult) => context => {
|
||||||
const contextParentPath = getParentPath(walkResult, context)
|
const contextParentPath = getParentPath(walkResult, context)
|
||||||
|
const isModel = context.model?.isModel || typeof context.model === "string"
|
||||||
|
const modelId =
|
||||||
|
typeof context.model === "string" ? context.model : context.model.modelId
|
||||||
|
const model = models.find(model => model._id === modelId)
|
||||||
|
|
||||||
|
// Avoid crashing whenever no data source has been selected
|
||||||
|
if (model == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const newBindable = key => ({
|
const newBindable = key => ({
|
||||||
type: "context",
|
type: "context",
|
||||||
|
@ -80,15 +89,12 @@ const contextToBindables = (models, walkResult) => context => {
|
||||||
// how the binding expression persists, and is used in the app at runtime
|
// how the binding expression persists, and is used in the app at runtime
|
||||||
runtimeBinding: `${contextParentPath}data.${key}`,
|
runtimeBinding: `${contextParentPath}data.${key}`,
|
||||||
// how the binding exressions looks to the user of the builder
|
// how the binding exressions looks to the user of the builder
|
||||||
readableBinding: `${context.instance._instanceName}.${context.model.label}.${key}`,
|
readableBinding: `${context.instance._instanceName}.${model.name}.${key}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
// see ModelViewSelect.svelte for the format of context.model
|
// see ModelViewSelect.svelte for the format of context.model
|
||||||
// ... this allows us to bind to Model scheams, or View schemas
|
// ... this allows us to bind to Model schemas, or View schemas
|
||||||
const model = models.find(m => m._id === context.model.modelId)
|
const schema = isModel ? model.schema : model.views[context.model.name].schema
|
||||||
const schema = context.model.isModel
|
|
||||||
? model.schema
|
|
||||||
: model.views[context.model.name].schema
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Object.keys(schema)
|
Object.keys(schema)
|
||||||
|
|
|
@ -51,9 +51,9 @@
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import Arrow from "./Arrow.svelte"
|
import Arrow from "./Arrow.svelte"
|
||||||
import { flip } from "svelte/animate"
|
import { flip } from "svelte/animate"
|
||||||
import { fade, fly } from "svelte/transition"
|
import { fade, fly } from "svelte/transition"
|
||||||
|
import { automationStore } from "builderStore"
|
||||||
|
|
||||||
export let automation
|
export let automation
|
||||||
export let onSelect
|
export let onSelect
|
||||||
|
@ -17,8 +18,16 @@
|
||||||
blocks = blocks.concat(automation.definition.steps || [])
|
blocks = blocks.concat(automation.definition.steps || [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: automationCount = $automationStore.automations?.length ?? 0
|
||||||
</script>
|
</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}
|
||||||
<section class="canvas">
|
<section class="canvas">
|
||||||
{#each blocks as block, idx (block.id)}
|
{#each blocks as block, idx (block.id)}
|
||||||
<div
|
<div
|
||||||
|
@ -35,6 +44,13 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
i {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--grey-4);
|
||||||
|
padding: var(--spacing-xl) 40px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
|
|
@ -1,32 +1,20 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "svelte-simple-modal"
|
import { onMount } from "svelte"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { automationStore } from "builderStore"
|
||||||
import { onMount, getContext } from "svelte"
|
|
||||||
import { backendUiStore, automationStore } from "builderStore"
|
|
||||||
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
let modal
|
||||||
|
|
||||||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
|
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
|
||||||
|
|
||||||
function newAutomation() {
|
|
||||||
open(
|
|
||||||
CreateAutomationModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
automationStore.actions.fetch()
|
automationStore.actions.fetch()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Button purple wide on:click={newAutomation}>Create New Automation</Button>
|
<Button primary wide on:click={modal.show}>Create New Automation</Button>
|
||||||
<ul>
|
<ul>
|
||||||
{#each $automationStore.automations as automation}
|
{#each $automationStore.automations as automation}
|
||||||
<li
|
<li
|
||||||
|
@ -39,6 +27,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<CreateAutomationModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
|
@ -57,7 +48,7 @@
|
||||||
color: var(--grey-6);
|
color: var(--grey-6);
|
||||||
}
|
}
|
||||||
i.live {
|
i.live {
|
||||||
color: var(--purple);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
|
|
@ -1,84 +1,49 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, automationStore } from "builderStore"
|
import { store, backendUiStore, automationStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import { Input, ModalContent } from "@budibase/bbui"
|
||||||
import { Input } from "@budibase/bbui"
|
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
let name
|
let name
|
||||||
|
|
||||||
$: valid = !!name
|
$: valid = !!name
|
||||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
$: instanceId = $backendUiStore.selectedDatabase._id
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(resolve, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function createAutomation() {
|
async function createAutomation() {
|
||||||
await automationStore.actions.create({
|
await automationStore.actions.create({
|
||||||
name,
|
name,
|
||||||
instanceId,
|
instanceId,
|
||||||
})
|
})
|
||||||
onClosed()
|
|
||||||
notifier.success(`Automation ${name} created.`)
|
notifier.success(`Automation ${name} created.`)
|
||||||
analytics.captureEvent("Automation Created", { name })
|
analytics.captureEvent("Automation Created", { name })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<ModalContent
|
||||||
<header>
|
title="Create Automation"
|
||||||
<i class="ri-stackshare-line" />
|
confirmText="Create"
|
||||||
Create Automation
|
onConfirm={createAutomation}
|
||||||
</header>
|
disabled={!valid}>
|
||||||
<div class="content">
|
<Input bind:value={name} label="Name" />
|
||||||
<Input bind:value={name} label="Name" />
|
<div slot="footer">
|
||||||
</div>
|
<a
|
||||||
<footer>
|
target="_blank"
|
||||||
<a href="https://docs.budibase.com">
|
href="https://docs.budibase.com/automate/introduction-to-automate">
|
||||||
<i class="ri-information-line" />
|
<i class="ri-information-line" />
|
||||||
<span>Learn about automations</span>
|
<span>Learn about automations</span>
|
||||||
</a>
|
</a>
|
||||||
<ActionButton secondary on:click={onClosed}>Cancel</ActionButton>
|
</div>
|
||||||
<ActionButton disabled={!valid} on:click={createAutomation}>
|
</ModalContent>
|
||||||
Save
|
|
||||||
</ActionButton>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
a {
|
||||||
padding: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
color: var(--ink);
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
header i {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
font-size: 20px;
|
|
||||||
background: var(--purple);
|
|
||||||
color: var(--white);
|
|
||||||
padding: var(--spacing-s);
|
|
||||||
border-radius: var(--border-radius-m);
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: var(--spacing-xl) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: grid;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
grid-auto-columns: 3fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
footer a {
|
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -86,10 +51,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
footer a span {
|
a span {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
footer i {
|
i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import AutomationList from "./AutomationList/AutomationList.svelte"
|
import AutomationList from "./AutomationList/AutomationList.svelte"
|
||||||
import BlockList from "./BlockList/BlockList.svelte"
|
import BlockList from "./BlockList/BlockList.svelte"
|
||||||
|
import { Heading } from "@budibase/bbui"
|
||||||
|
import { Spacer } from "@budibase/bbui"
|
||||||
|
|
||||||
let selectedTab = "AUTOMATIONS"
|
let selectedTab = "AUTOMATIONS"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<Heading black small>
|
||||||
<span
|
<span
|
||||||
data-cy="automation-list"
|
data-cy="automation-list"
|
||||||
class="hoverable automation-header"
|
class="hoverable automation-header"
|
||||||
|
@ -23,7 +25,8 @@
|
||||||
Steps
|
Steps
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</Heading>
|
||||||
|
<Spacer medium />
|
||||||
{#if selectedTab === 'AUTOMATIONS'}
|
{#if selectedTab === 'AUTOMATIONS'}
|
||||||
<AutomationList />
|
<AutomationList />
|
||||||
{:else if selectedTab === 'ADD'}
|
{:else if selectedTab === 'ADD'}
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
<div class="block-label">{block.name}</div>
|
<div class="block-label">{block.name}</div>
|
||||||
{#each inputs as [key, value]}
|
{#each inputs as [key, value]}
|
||||||
<div class="bb-margin-xl block-field">
|
<div class="bb-margin-xl block-field">
|
||||||
<div class="field-label">{value.title}</div>
|
<Label extraSmall grey>{value.title}</Label>
|
||||||
{#if value.type === 'string' && value.enum}
|
{#if value.type === 'string' && value.enum}
|
||||||
<Select bind:value={block.inputs[key]} thin secondary>
|
<Select bind:value={block.inputs[key]} thin secondary>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
|
@ -80,15 +80,6 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-label {
|
|
||||||
color: var(--ink);
|
|
||||||
margin-bottom: 12px;
|
|
||||||
display: flex;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-label {
|
.block-label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, backendUiStore, automationStore } from "builderStore"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
let name
|
|
||||||
|
|
||||||
$: valid = !!name
|
|
||||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
|
||||||
|
|
||||||
async function deleteAutomation() {
|
|
||||||
await automationStore.actions.delete({
|
|
||||||
instanceId,
|
|
||||||
automation: $automationStore.selectedAutomation.automation,
|
|
||||||
})
|
|
||||||
onClosed()
|
|
||||||
notifier.danger("Automation deleted.")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<i class="ri-stackshare-line" />
|
|
||||||
Delete Automation
|
|
||||||
</header>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete this automation? This action can't be
|
|
||||||
undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<a href="https://docs.budibase.com">
|
|
||||||
<i class="ri-information-line" />
|
|
||||||
Learn about automations
|
|
||||||
</a>
|
|
||||||
<ActionButton on:click={onClosed}>Cancel</ActionButton>
|
|
||||||
<ActionButton alert on:click={deleteAutomation}>Delete</ActionButton>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
header {
|
|
||||||
font-size: 24px;
|
|
||||||
color: var(--ink);
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header i {
|
|
||||||
margin-right: 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
background: var(--blue-light);
|
|
||||||
color: var(--grey-4);
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
padding: 0 30px 30px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: grid;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
grid-gap: 5px;
|
|
||||||
grid-auto-columns: 3fr 1fr 1fr;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
color: var(--primary);
|
|
||||||
font-size: 14px;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer i {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -28,42 +28,34 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if schemaFields.length}
|
{#if schemaFields.length}
|
||||||
<div class="bb-margin-xl block-field">
|
<div class="schema-fields">
|
||||||
{#each schemaFields as [field, schema]}
|
{#each schemaFields as [field, schema]}
|
||||||
<div class="bb-margin-xl capitalise">
|
{#if schemaHasOptions(schema)}
|
||||||
{#if schemaHasOptions(schema)}
|
<Select label={field} thin secondary bind:value={value[field]}>
|
||||||
<div class="field-label">{field}</div>
|
<option value="">Choose an option</option>
|
||||||
<Select thin secondary bind:value={value[field]}>
|
{#each schema.constraints.inclusion as option}
|
||||||
<option value="">Choose an option</option>
|
<option value={option}>{option}</option>
|
||||||
{#each schema.constraints.inclusion as option}
|
{/each}
|
||||||
<option value={option}>{option}</option>
|
</Select>
|
||||||
{/each}
|
{:else if schema.type === 'string' || schema.type === 'number'}
|
||||||
</Select>
|
<BindableInput
|
||||||
{:else if schema.type === 'string' || schema.type === 'number'}
|
thin
|
||||||
<BindableInput
|
bind:value={value[field]}
|
||||||
thin
|
label={field}
|
||||||
bind:value={value[field]}
|
type="string"
|
||||||
label={field}
|
{bindings} />
|
||||||
type="string"
|
{/if}
|
||||||
{bindings} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.field-label {
|
.schema-fields {
|
||||||
color: var(--ink);
|
display: grid;
|
||||||
margin-bottom: 12px;
|
grid-gap: var(--spacing-xl);
|
||||||
display: flex;
|
margin-top: var(--spacing-xl);
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
|
.schema-fields :global(label) {
|
||||||
.capitalise :global(label),
|
|
||||||
.field-label {
|
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,27 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { backendUiStore, automationStore } from "builderStore"
|
import { backendUiStore, automationStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
|
||||||
import DeleteAutomationModal from "./DeleteAutomationModal.svelte"
|
|
||||||
import { Button, Input, Label } from "@budibase/bbui"
|
import { Button, Input, Label } from "@budibase/bbui"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
let selectedTab = "SETUP"
|
let selectedTab = "SETUP"
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
|
$: instanceId = $backendUiStore.selectedDatabase._id
|
||||||
$: automation = $automationStore.selectedAutomation?.automation
|
$: automation = $automationStore.selectedAutomation?.automation
|
||||||
$: allowDeleteBlock =
|
$: allowDeleteBlock =
|
||||||
$automationStore.selectedBlock?.type !== "TRIGGER" ||
|
$automationStore.selectedBlock?.type !== "TRIGGER" ||
|
||||||
!automation?.definition?.steps?.length
|
!automation?.definition?.steps?.length
|
||||||
|
$: name = automation?.name ?? ""
|
||||||
function deleteAutomation() {
|
|
||||||
open(
|
|
||||||
DeleteAutomationModal,
|
|
||||||
{ onClosed: close },
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteAutomationBlock() {
|
function deleteAutomationBlock() {
|
||||||
automationStore.actions.deleteAutomationBlock(
|
automationStore.actions.deleteAutomationBlock(
|
||||||
|
@ -42,11 +34,19 @@
|
||||||
|
|
||||||
async function saveAutomation() {
|
async function saveAutomation() {
|
||||||
await automationStore.actions.save({
|
await automationStore.actions.save({
|
||||||
instanceId: $backendUiStore.selectedDatabase._id,
|
instanceId,
|
||||||
automation,
|
automation,
|
||||||
})
|
})
|
||||||
notifier.success(`Automation ${automation.name} saved.`)
|
notifier.success(`Automation ${automation.name} saved.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteAutomation() {
|
||||||
|
await automationStore.actions.delete({
|
||||||
|
instanceId,
|
||||||
|
automation,
|
||||||
|
})
|
||||||
|
notifier.success("Automation deleted.")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -93,11 +93,18 @@
|
||||||
on:click={saveAutomation}>
|
on:click={saveAutomation}>
|
||||||
Save Automation
|
Save Automation
|
||||||
</Button>
|
</Button>
|
||||||
<Button red wide on:click={deleteAutomation}>Delete Automation</Button>
|
<Button red wide on:click={() => confirmDeleteDialog.show()}>
|
||||||
|
Delete Automation
|
||||||
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
title="Confirm Delete"
|
||||||
|
body={`Are you sure you wish to delete the automation '${name}'?`}
|
||||||
|
okText="Delete Automation"
|
||||||
|
onOk={deleteAutomation} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import CreateRowButton from "./buttons/CreateRowButton.svelte"
|
||||||
|
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
|
||||||
|
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
||||||
|
import ExportButton from "./buttons/ExportButton.svelte"
|
||||||
|
import * as api from "./api"
|
||||||
|
import Table from "./Table.svelte"
|
||||||
|
|
||||||
|
let data = []
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
$: title = $backendUiStore.selectedModel.name
|
||||||
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
|
$: modelView = {
|
||||||
|
schema,
|
||||||
|
name: $backendUiStore.selectedView.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch records for specified model
|
||||||
|
$: {
|
||||||
|
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
|
||||||
|
loading = true
|
||||||
|
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
||||||
|
data = records || []
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Table {title} {schema} {data} allowEditing={true} {loading}>
|
||||||
|
<CreateColumnButton />
|
||||||
|
{#if Object.keys(schema).length > 0}
|
||||||
|
<CreateRowButton />
|
||||||
|
<CreateViewButton />
|
||||||
|
<ExportButton view={modelView} />
|
||||||
|
{/if}
|
||||||
|
</Table>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
|
||||||
|
import Dropzone from "components/common/Dropzone.svelte"
|
||||||
|
import { capitalise } from "../../../helpers"
|
||||||
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
|
|
||||||
|
export let meta
|
||||||
|
export let value = meta.type === "boolean" ? false : ""
|
||||||
|
|
||||||
|
$: type = meta.type
|
||||||
|
$: label = capitalise(meta.name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === 'options'}
|
||||||
|
<Select thin secondary {label} data-cy="{meta.name}-select" bind:value>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each meta.constraints.inclusion as opt}
|
||||||
|
<option value={opt}>{opt}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{:else if type === 'datetime'}
|
||||||
|
<DatePicker {label} bind:value />
|
||||||
|
{:else if type === 'attachment'}
|
||||||
|
<div>
|
||||||
|
<Label extraSmall grey forAttr={'dropzone-label'}>{label}</Label>
|
||||||
|
<Dropzone bind:files={value} />
|
||||||
|
</div>
|
||||||
|
{:else if type === 'boolean'}
|
||||||
|
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
|
||||||
|
{:else if type === 'link'}
|
||||||
|
<LinkedRecordSelector bind:linkedRecords={value} schema={meta} />
|
||||||
|
{:else}
|
||||||
|
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
|
||||||
|
{/if}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script>
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import Table from "./Table.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
export let modelId
|
||||||
|
export let recordId
|
||||||
|
export let fieldName
|
||||||
|
|
||||||
|
let record
|
||||||
|
let title
|
||||||
|
|
||||||
|
$: data = record?.[fieldName] ?? []
|
||||||
|
$: linkedModelId = data?.length ? data[0].modelId : null
|
||||||
|
$: linkedModel = $backendUiStore.models.find(
|
||||||
|
model => model._id === linkedModelId
|
||||||
|
)
|
||||||
|
$: schema = linkedModel?.schema
|
||||||
|
$: model = $backendUiStore.models.find(model => model._id === modelId)
|
||||||
|
$: fetchData(modelId, recordId)
|
||||||
|
$: {
|
||||||
|
let recordLabel = record?.[model?.primaryDisplay]
|
||||||
|
if (recordLabel) {
|
||||||
|
title = `${recordLabel} - ${fieldName}`
|
||||||
|
} else {
|
||||||
|
title = fieldName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData(modelId, recordId) {
|
||||||
|
const QUERY_VIEW_URL = `/api/${modelId}/${recordId}/enrich`
|
||||||
|
const response = await api.get(QUERY_VIEW_URL)
|
||||||
|
record = await response.json()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if record && record._id === recordId}
|
||||||
|
<Table {title} {schema} {data} />
|
||||||
|
{/if}
|
|
@ -1,71 +1,60 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { goto, params } from "@sveltech/routify"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import fsort from "fast-sort"
|
import fsort from "fast-sort"
|
||||||
import getOr from "lodash/fp/getOr"
|
import getOr from "lodash/fp/getOr"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
import { Button, Icon } from "@budibase/bbui"
|
import { Button, Icon } from "@budibase/bbui"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import LinkedRecord from "./LinkedRecord.svelte"
|
|
||||||
import AttachmentList from "./AttachmentList.svelte"
|
import AttachmentList from "./AttachmentList.svelte"
|
||||||
import TablePagination from "./TablePagination.svelte"
|
import TablePagination from "./TablePagination.svelte"
|
||||||
|
import CreateEditRecordModal from "./modals/CreateEditRecordModal.svelte"
|
||||||
|
import RowPopover from "./buttons/CreateRowButton.svelte"
|
||||||
|
import ColumnPopover from "./buttons/CreateColumnButton.svelte"
|
||||||
|
import ViewPopover from "./buttons/CreateViewButton.svelte"
|
||||||
|
import ColumnHeaderPopover from "./popovers/ColumnPopover.svelte"
|
||||||
|
import EditRowPopover from "./popovers/RowPopover.svelte"
|
||||||
|
import CalculationPopover from "./buttons/CalculateButton.svelte"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import RowPopover from "./popovers/Row.svelte"
|
|
||||||
import ColumnPopover from "./popovers/Column.svelte"
|
|
||||||
import ViewPopover from "./popovers/View.svelte"
|
|
||||||
import ExportPopover from "./popovers/Export.svelte"
|
|
||||||
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
|
||||||
import EditRowPopover from "./popovers/EditRow.svelte"
|
|
||||||
import * as api from "./api"
|
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 10
|
const ITEMS_PER_PAGE = 10
|
||||||
// Internal headers we want to hide from the user
|
|
||||||
const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"]
|
|
||||||
|
|
||||||
let modalOpen = false
|
export let schema = []
|
||||||
let data = []
|
export let data = []
|
||||||
let headers = []
|
export let title
|
||||||
|
export let allowEditing = false
|
||||||
|
export let loading = false
|
||||||
|
|
||||||
let currentPage = 0
|
let currentPage = 0
|
||||||
let search
|
|
||||||
let loading
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (
|
|
||||||
$backendUiStore.selectedView &&
|
|
||||||
$backendUiStore.selectedView.name.startsWith("all_")
|
|
||||||
) {
|
|
||||||
loading = true
|
|
||||||
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
|
||||||
data = records || []
|
|
||||||
loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$: columns = schema ? Object.keys(schema) : []
|
||||||
$: sort = $backendUiStore.sort
|
$: sort = $backendUiStore.sort
|
||||||
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
||||||
$: paginatedData = sorted
|
$: paginatedData =
|
||||||
? sorted.slice(
|
sorted && sorted.length
|
||||||
currentPage * ITEMS_PER_PAGE,
|
? sorted.slice(
|
||||||
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
currentPage * ITEMS_PER_PAGE,
|
||||||
)
|
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
||||||
: []
|
)
|
||||||
|
: []
|
||||||
|
$: modelId = data?.length ? data[0].modelId : null
|
||||||
|
|
||||||
$: headers = Object.keys($backendUiStore.selectedModel.schema)
|
function selectRelationship(record, fieldName) {
|
||||||
.sort()
|
if (!record?.[fieldName]?.length) {
|
||||||
.filter(id => !INTERNAL_HEADERS.includes(id))
|
return
|
||||||
|
}
|
||||||
$: schema = $backendUiStore.selectedModel.schema
|
$goto(
|
||||||
$: modelView = {
|
`/${$params.application}/backend/model/${modelId}/relationship/${record._id}/${fieldName}`
|
||||||
schema: $backendUiStore.selectedModel.schema,
|
)
|
||||||
name: $backendUiStore.selectedView.name,
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="table-controls">
|
<div class="table-controls">
|
||||||
<h2 class="title">
|
<h2 class="title">
|
||||||
<span>{$backendUiStore.selectedModel.name}</span>
|
<span>{title}</span>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div transition:fade>
|
<div transition:fade>
|
||||||
<Spinner size="10" />
|
<Spinner size="10" />
|
||||||
|
@ -73,41 +62,54 @@
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="popovers">
|
<div class="popovers">
|
||||||
<ColumnPopover />
|
<slot />
|
||||||
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
|
|
||||||
<RowPopover />
|
|
||||||
<ViewPopover />
|
|
||||||
<ExportPopover view={modelView} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="bb-table">
|
<table class="bb-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="edit-header">
|
{#if allowEditing}
|
||||||
<div>Edit</div>
|
<th class="edit-header">
|
||||||
</th>
|
<div>Edit</div>
|
||||||
{#each headers as header}
|
</th>
|
||||||
|
{/if}
|
||||||
|
{#each columns as header}
|
||||||
<th>
|
<th>
|
||||||
<ColumnHeaderPopover
|
{#if allowEditing}
|
||||||
field={$backendUiStore.selectedModel.schema[header]} />
|
<ColumnHeaderPopover field={schema[header]} />
|
||||||
|
{:else}
|
||||||
|
<div class="header">{header}</div>
|
||||||
|
{/if}
|
||||||
</th>
|
</th>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if paginatedData.length === 0}
|
{#if paginatedData.length === 0}
|
||||||
<div class="no-data">No Data.</div>
|
{#if allowEditing}
|
||||||
|
<td class="no-border">No data.</td>
|
||||||
|
{/if}
|
||||||
|
{#each columns as header, idx}
|
||||||
|
<td class="no-border">
|
||||||
|
{#if idx === 0 && !allowEditing}No data.{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{#each paginatedData as row}
|
{#each paginatedData as row}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
{#if allowEditing}
|
||||||
<EditRowPopover {row} />
|
<td>
|
||||||
</td>
|
<EditRowPopover {row} />
|
||||||
{#each headers as header}
|
</td>
|
||||||
|
{/if}
|
||||||
|
{#each columns as header}
|
||||||
<td>
|
<td>
|
||||||
{#if schema[header].type === 'link'}
|
{#if schema[header].type === 'link'}
|
||||||
<LinkedRecord field={schema[header]} ids={row[header]} />
|
<div
|
||||||
|
class:link={row[header] && row[header].length}
|
||||||
|
on:click={() => selectRelationship(row, header)}>
|
||||||
|
{row[header] ? row[header].length : 0} related row(s)
|
||||||
|
</div>
|
||||||
{:else if schema[header].type === 'attachment'}
|
{:else if schema[header].type === 'attachment'}
|
||||||
<AttachmentList files={row[header] || []} />
|
<AttachmentList files={row[header] || []} />
|
||||||
{:else}{getOr('', header, row)}{/if}
|
{:else}{getOr('', header, row)}{/if}
|
||||||
|
@ -125,19 +127,17 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
margin-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title > span {
|
.title > span {
|
||||||
margin-right: var(--spacing-xs);
|
margin-right: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
@ -145,54 +145,54 @@
|
||||||
table {
|
table {
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 3px;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
height: 40px;
|
|
||||||
background: var(--grey-3);
|
background: var(--grey-3);
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
text-transform: capitalize;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
transition: 0.5s all;
|
transition: 0.5s all;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
height: 48px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-header {
|
thead th:hover {
|
||||||
width: 100px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-header:hover {
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
th:hover {
|
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
overflow: hidden;
|
white-space: nowrap;
|
||||||
white-space: pre;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-l) var(--spacing-m);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
td.no-border {
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
border-bottom: 1px solid var(--grey-4);
|
border-bottom: 1px solid var(--grey-4);
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:hover {
|
tbody tr:hover {
|
||||||
|
@ -207,7 +207,26 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-data {
|
:global(.popovers > div) {
|
||||||
padding: 14px;
|
margin-right: var(--spacing-m);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-header {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-header:hover {
|
||||||
|
cursor: default;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: var(--grey-6);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,14 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
export let currentPage
|
export let currentPage
|
||||||
export let pageItemCount
|
export let pageItemCount
|
||||||
export let ITEMS_PER_PAGE
|
export let ITEMS_PER_PAGE
|
||||||
|
|
||||||
let numPages = 0
|
let numPages = 0
|
||||||
|
$: numPages = Math.ceil((data?.length ?? 0) / ITEMS_PER_PAGE)
|
||||||
$: numPages = Math.ceil(data.length / ITEMS_PER_PAGE)
|
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (currentPage + 1 === numPages) return
|
if (currentPage + 1 === numPages) return
|
||||||
|
@ -27,8 +24,7 @@
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<div class="pagination__buttons">
|
<div class="pagination__buttons">
|
||||||
<button on:click={previous}>Previous</button>
|
<button on:click={previous} disabled={currentPage === 0}><</button>
|
||||||
<button on:click={next}>Next</button>
|
|
||||||
{#each Array(numPages) as _, idx}
|
{#each Array(numPages) as _, idx}
|
||||||
<button
|
<button
|
||||||
class:selected={idx === currentPage}
|
class:selected={idx === currentPage}
|
||||||
|
@ -36,8 +32,19 @@
|
||||||
{idx + 1}
|
{idx + 1}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
<button
|
||||||
|
on:click={next}
|
||||||
|
disabled={currentPage === numPages - 1 || numPages === 0}>
|
||||||
|
>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p>Showing {pageItemCount} of {data.length} entries</p>
|
|
||||||
|
<p>
|
||||||
|
{#if numPages > 1}
|
||||||
|
Showing {ITEMS_PER_PAGE * currentPage + 1} - {ITEMS_PER_PAGE * currentPage + pageItemCount}
|
||||||
|
of {data.length} rows
|
||||||
|
{:else if numPages === 1}Showing all {data.length} row(s){/if}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -51,26 +58,36 @@
|
||||||
|
|
||||||
.pagination__buttons {
|
.pagination__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
border: 1px solid var(--grey-4);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination__buttons button {
|
.pagination__buttons button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px;
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid var(--grey-4);
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
border-right: 1px solid var(--grey-4);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
border-radius: 3px;
|
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
}
|
}
|
||||||
|
.pagination__buttons button:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
.pagination__buttons button:hover {
|
.pagination__buttons button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--grey-1);
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
.pagination__buttons button.selected {
|
||||||
|
background: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
.selected {
|
p {
|
||||||
color: var(--blue);
|
font-size: var(--font-size-s);
|
||||||
|
margin: var(--spacing-xl) 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script>
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import Table from "./Table.svelte"
|
||||||
|
import CalculateButton from "./buttons/CalculateButton.svelte"
|
||||||
|
import GroupByButton from "./buttons/GroupByButton.svelte"
|
||||||
|
import FilterButton from "./buttons/FilterButton.svelte"
|
||||||
|
import ExportButton from "./buttons/ExportButton.svelte"
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
$: name = view.name
|
||||||
|
|
||||||
|
// Fetch records for specified view
|
||||||
|
$: {
|
||||||
|
if (!name.startsWith("all_")) {
|
||||||
|
fetchViewData(name, view.field, view.groupBy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchViewData(name, field, groupBy) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
if (field) {
|
||||||
|
params.set("field", field)
|
||||||
|
params.set("stats", true)
|
||||||
|
}
|
||||||
|
if (groupBy) {
|
||||||
|
params.set("group", groupBy)
|
||||||
|
}
|
||||||
|
const QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
||||||
|
const response = await api.get(QUERY_VIEW_URL)
|
||||||
|
data = await response.json()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Table title={decodeURI(name)} schema={view.schema} {data}>
|
||||||
|
<FilterButton {view} />
|
||||||
|
<CalculateButton {view} />
|
||||||
|
{#if view.calculation}
|
||||||
|
<GroupByButton {view} />
|
||||||
|
{/if}
|
||||||
|
<ExportButton {view} />
|
||||||
|
</Table>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, TextButton, Icon } from "@budibase/bbui"
|
||||||
|
import CalculatePopover from "../popovers/CalculatePopover.svelte"
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<TextButton text small on:click={dropdown.show} active={!!view.field}>
|
||||||
|
<Icon name="calculate" />
|
||||||
|
Calculate
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdown} {anchor} align="left">
|
||||||
|
<CalculatePopover {view} onClosed={dropdown.hide} />
|
||||||
|
</Popover>
|
|
@ -1,20 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui"
|
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui"
|
||||||
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
import CreateEditColumnPopover from "../popovers/CreateEditColumnPopover.svelte"
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let dropdown
|
let dropdown
|
||||||
|
let fieldName
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div bind:this={anchor}>
|
||||||
<Button text small on:click={dropdown.show}>
|
<Button text small on:click={dropdown.show}>
|
||||||
<Icon name="addrow" />
|
<Icon name="addcolumn" />
|
||||||
Create New Row
|
Create New Column
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
||||||
<h5>Add New Row</h5>
|
<h5>Create Column</h5>
|
||||||
<CreateEditRecord onClosed={dropdown.hide} />
|
<CreateEditColumnPopover onClosed={dropdown.hide} />
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
<style>
|
<style>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
|
||||||
|
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button text small on:click={modal.show}>
|
||||||
|
<Icon name="addrow" />
|
||||||
|
Create New Row
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<CreateEditRecordModal />
|
||||||
|
</Modal>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, TextButton, Icon } from "@budibase/bbui"
|
||||||
|
import CreateViewPopover from "../popovers/CreateViewPopover.svelte"
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<TextButton text small on:click={dropdown.show}>
|
||||||
|
<Icon name="view" />
|
||||||
|
Create New View
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdown} {anchor} align="left">
|
||||||
|
<CreateViewPopover onClosed={dropdown.hide} />
|
||||||
|
</Popover>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
import { TextButton, Icon, Popover } from "@budibase/bbui"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import ExportPopover from "../popovers/ExportPopover.svelte"
|
||||||
|
|
||||||
|
export let view
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<TextButton text small on:click={dropdown.show}>
|
||||||
|
<Icon name="download" />
|
||||||
|
Export
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdown} {anchor} align="left">
|
||||||
|
<ExportPopover {view} onClosed={dropdown.hide} />
|
||||||
|
</Popover>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, TextButton, Icon } from "@budibase/bbui"
|
||||||
|
import FilterPopover from "../popovers/FilterPopover.svelte"
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<TextButton
|
||||||
|
text
|
||||||
|
small
|
||||||
|
on:click={dropdown.show}
|
||||||
|
active={view.filters && view.filters.length}>
|
||||||
|
<Icon name="filter" />
|
||||||
|
Filter
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdown} {anchor} align="left">
|
||||||
|
<FilterPopover {view} onClosed={dropdown.hide} />
|
||||||
|
</Popover>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, TextButton, Icon } from "@budibase/bbui"
|
||||||
|
import GroupByPopover from "../popovers/GroupByPopover.svelte"
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<TextButton text small active={!!view.groupBy} on:click={dropdown.show}>
|
||||||
|
<Icon name="group" />
|
||||||
|
Group By
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdown} {anchor} align="left">
|
||||||
|
<GroupByPopover {view} onClosed={dropdown.hide} />
|
||||||
|
</Popover>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import RecordFieldControl from "../RecordFieldControl.svelte"
|
||||||
|
import * as api from "../api"
|
||||||
|
import { ModalContent } from "@budibase/bbui"
|
||||||
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
|
||||||
|
export let record = {}
|
||||||
|
|
||||||
|
let errors = []
|
||||||
|
|
||||||
|
$: creating = record?._id == null
|
||||||
|
$: model = record.modelId
|
||||||
|
? $backendUiStore.models.find(model => model._id === record?.modelId)
|
||||||
|
: $backendUiStore.selectedModel
|
||||||
|
$: modelSchema = Object.entries(model?.schema ?? {})
|
||||||
|
|
||||||
|
async function saveRecord() {
|
||||||
|
const recordResponse = await api.saveRecord(
|
||||||
|
{ ...record, modelId: model._id },
|
||||||
|
model._id
|
||||||
|
)
|
||||||
|
if (recordResponse.errors) {
|
||||||
|
errors = Object.keys(recordResponse.errors)
|
||||||
|
.map(k => ({ dataPath: k, message: recordResponse.errors[k] }))
|
||||||
|
.flat()
|
||||||
|
// Prevent modal closing if there were errors
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
notifier.success("Record saved successfully.")
|
||||||
|
backendUiStore.actions.records.save(recordResponse)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title={creating ? 'Create Row' : 'Edit Row'}
|
||||||
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
|
onConfirm={saveRecord}>
|
||||||
|
<ErrorsBox {errors} />
|
||||||
|
{#each modelSchema as [key, meta]}
|
||||||
|
<div>
|
||||||
|
<RecordFieldControl {meta} bind:value={record[key]} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</ModalContent>
|
|
@ -1,12 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Button, Input, Select } from "@budibase/bbui"
|
||||||
Popover,
|
|
||||||
TextButton,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
@ -19,9 +12,7 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
export let onClosed
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewModel = $backendUiStore.models.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||||
|
@ -36,18 +27,12 @@
|
||||||
if (!view.calculation) view.calculation = "stats"
|
if (!view.calculation) view.calculation = "stats"
|
||||||
backendUiStore.actions.views.save(view)
|
backendUiStore.actions.views.save(view)
|
||||||
notifier.success(`View ${view.name} saved.`)
|
notifier.success(`View ${view.name} saved.`)
|
||||||
|
onClosed()
|
||||||
analytics.captureEvent("Added View Calculate", { field: view.field })
|
analytics.captureEvent("Added View Calculate", { field: view.field })
|
||||||
dropdown.hide()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div class="actions">
|
||||||
<TextButton text small on:click={dropdown.show} active={!!view.field}>
|
|
||||||
<Icon name="calculate" />
|
|
||||||
Calculate
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Calculate</h5>
|
<h5>Calculate</h5>
|
||||||
<div class="input-group-row">
|
<div class="input-group-row">
|
||||||
<!-- <p>The</p>
|
<!-- <p>The</p>
|
||||||
|
@ -66,30 +51,33 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="footer">
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
<Button primary on:click={saveView}>Save</Button>
|
<Button primary on:click={saveView}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.actions {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
margin-bottom: var(--spacing-l);
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.footer {
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-row {
|
.input-group-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr 20px 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
margin-bottom: var(--spacing-l);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
import CreateEditColumnPopover from "./CreateEditColumnPopover.svelte"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import { notifier } from "../../../../builderStore/store/notifications"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let dropdown
|
let dropdown
|
||||||
|
|
||||||
let editing
|
let editing
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
$: sortColumn = $backendUiStore.sort && $backendUiStore.sort.column
|
$: sortColumn = $backendUiStore.sort && $backendUiStore.sort.column
|
||||||
$: sortDirection = $backendUiStore.sort && $backendUiStore.sort.direction
|
$: sortDirection = $backendUiStore.sort && $backendUiStore.sort.direction
|
||||||
|
$: type = field?.type
|
||||||
|
|
||||||
function showEditor() {
|
function showEditor() {
|
||||||
editing = true
|
editing = true
|
||||||
|
@ -23,8 +26,18 @@
|
||||||
editing = false
|
editing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteField() {
|
function showDelete() {
|
||||||
backendUiStore.actions.models.deleteField(field)
|
dropdown.hide()
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteColumn() {
|
||||||
|
if (field.name === $backendUiStore.selectedModel.primaryDisplay) {
|
||||||
|
notifier.danger("You cannot delete the primary display column")
|
||||||
|
} else {
|
||||||
|
backendUiStore.actions.models.deleteField(field)
|
||||||
|
notifier.success("Column deleted")
|
||||||
|
}
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,21 +50,23 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor} on:click={dropdown.show}>
|
<div class="container" bind:this={anchor} on:click={dropdown.show}>
|
||||||
{field.name}
|
<span>{field.name}</span>
|
||||||
<Icon name="arrowdown" />
|
<Icon name="arrowdown" />
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
||||||
{#if editing}
|
{#if editing}
|
||||||
<h5>Edit Column</h5>
|
<h5>Edit Column</h5>
|
||||||
<CreateEditColumn onClosed={hideEditor} {field} />
|
<CreateEditColumnPopover onClosed={hideEditor} {field} />
|
||||||
{:else}
|
{:else}
|
||||||
<ul>
|
<ul>
|
||||||
<li data-cy="edit-column-header" on:click={showEditor}>
|
{#if type !== 'link'}
|
||||||
<Icon name="edit" />
|
<li data-cy="edit-column-header" on:click={showEditor}>
|
||||||
Edit
|
<Icon name="edit" />
|
||||||
</li>
|
Edit
|
||||||
<li data-cy="delete-column-header" on:click={deleteField}>
|
</li>
|
||||||
|
{/if}
|
||||||
|
<li data-cy="delete-column-header" on:click={showDelete}>
|
||||||
<Icon name="delete" />
|
<Icon name="delete" />
|
||||||
Delete
|
Delete
|
||||||
</li>
|
</li>
|
||||||
|
@ -70,8 +85,25 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`}
|
||||||
|
okText="Delete Column"
|
||||||
|
onOk={deleteColumn}
|
||||||
|
title="Confirm Delete" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
.container span {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
||||||
margin: 0;
|
margin: 0;
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
TextArea,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
Toggle,
|
||||||
|
Label,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { cloneDeep, merge } from "lodash/fp"
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import { FIELDS } from "constants/backend"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
||||||
|
import NumberBox from "components/common/NumberBox.svelte"
|
||||||
|
import ValuesList from "components/common/ValuesList.svelte"
|
||||||
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
import Checkbox from "components/common/Checkbox.svelte"
|
||||||
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
import DatePicker from "components/common/DatePicker.svelte"
|
||||||
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
|
import * as api from "../api"
|
||||||
|
|
||||||
|
let fieldDefinitions = cloneDeep(FIELDS)
|
||||||
|
|
||||||
|
export let onClosed
|
||||||
|
export let field = {
|
||||||
|
type: "string",
|
||||||
|
constraints: fieldDefinitions.STRING.constraints,
|
||||||
|
}
|
||||||
|
|
||||||
|
let originalName = field.name
|
||||||
|
$: modelOptions = $backendUiStore.models.filter(
|
||||||
|
model => model._id !== $backendUiStore.draftModel._id
|
||||||
|
)
|
||||||
|
$: required = !!field?.constraints?.presence
|
||||||
|
|
||||||
|
async function saveColumn() {
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
backendUiStore.actions.models.saveField({
|
||||||
|
originalName,
|
||||||
|
field,
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
onClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFieldConstraints(event) {
|
||||||
|
const { type, constraints } = fieldDefinitions[
|
||||||
|
event.target.value.toUpperCase()
|
||||||
|
]
|
||||||
|
field.type = type
|
||||||
|
field.constraints = constraints
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeRequired(e) {
|
||||||
|
const req = e.target.checked
|
||||||
|
field.constraints.presence = req ? { allowEmpty: false } : false
|
||||||
|
required = req
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<Input label="Name" thin bind:value={field.name} />
|
||||||
|
|
||||||
|
<Select
|
||||||
|
secondary
|
||||||
|
thin
|
||||||
|
label="Type"
|
||||||
|
on:change={handleFieldConstraints}
|
||||||
|
bind:value={field.type}>
|
||||||
|
{#each Object.values(fieldDefinitions) as field}
|
||||||
|
<option value={field.type}>{field.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{#if field.type !== 'link'}
|
||||||
|
<Toggle
|
||||||
|
checked={required}
|
||||||
|
on:change={onChangeRequired}
|
||||||
|
thin
|
||||||
|
text="Required" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if field.type === 'string'}
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
type="number"
|
||||||
|
label="Max Length"
|
||||||
|
bind:value={field.constraints.length.maximum} />
|
||||||
|
{:else if field.type === 'options'}
|
||||||
|
<ValuesList
|
||||||
|
label="Options (one per line)"
|
||||||
|
bind:values={field.constraints.inclusion} />
|
||||||
|
{:else if field.type === 'datetime'}
|
||||||
|
<DatePicker
|
||||||
|
label="Earliest"
|
||||||
|
bind:value={field.constraints.datetime.earliest} />
|
||||||
|
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
|
||||||
|
{:else if field.type === 'number'}
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
type="number"
|
||||||
|
label="Min Value"
|
||||||
|
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
type="number"
|
||||||
|
label="Max Value"
|
||||||
|
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
|
||||||
|
{:else if field.type === 'link'}
|
||||||
|
<Select label="Table" thin secondary bind:value={field.modelId}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each modelOptions as model}
|
||||||
|
<option value={model._id}>{model.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
label={`Column Name in Other Table`}
|
||||||
|
thin
|
||||||
|
bind:value={field.fieldName} />
|
||||||
|
{/if}
|
||||||
|
<footer>
|
||||||
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
|
<Button primary on:click={saveColumn}>Save Column</Button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.actions {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,19 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Button, Input, Select } from "@budibase/bbui"
|
||||||
Popover,
|
|
||||||
TextButton,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
let anchor
|
export let onClosed
|
||||||
let dropdown
|
|
||||||
|
|
||||||
let name
|
let name
|
||||||
let field
|
let field
|
||||||
|
@ -36,45 +28,35 @@
|
||||||
field,
|
field,
|
||||||
})
|
})
|
||||||
notifier.success(`View ${name} created`)
|
notifier.success(`View ${name} created`)
|
||||||
dropdown.hide()
|
onClosed()
|
||||||
analytics.captureEvent("View Created", { name })
|
analytics.captureEvent("View Created", { name })
|
||||||
$goto(`../../../view/${name}`)
|
$goto(`../../../view/${name}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div class="actions">
|
||||||
<TextButton text small on:click={dropdown.show}>
|
|
||||||
<Icon name="view" />
|
|
||||||
Create New View
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Create View</h5>
|
<h5>Create View</h5>
|
||||||
<div class="input-group-column">
|
<Input label="View Name" thin bind:value={name} />
|
||||||
<Input placeholder="View Name" thin bind:value={name} />
|
<div class="footer">
|
||||||
</div>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
<div class="button-group">
|
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
|
||||||
<Button primary on:click={saveView}>Save View</Button>
|
<Button primary on:click={saveView}>Save View</Button>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h5 {
|
h5 {
|
||||||
margin-bottom: var(--spacing-l);
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.actions {
|
||||||
margin-top: var(--spacing-l);
|
display: grid;
|
||||||
display: flex;
|
grid-gap: var(--spacing-xl);
|
||||||
justify-content: flex-end;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-column {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: flex-end;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script>
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import { Button, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const FORMATS = [
|
||||||
|
{
|
||||||
|
name: "CSV",
|
||||||
|
key: "csv",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON",
|
||||||
|
key: "json",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export let view
|
||||||
|
export let onClosed
|
||||||
|
|
||||||
|
let exportFormat = FORMATS[0].key
|
||||||
|
|
||||||
|
async function exportView() {
|
||||||
|
const response = await api.post(
|
||||||
|
`/api/views/export?format=${exportFormat}`,
|
||||||
|
view
|
||||||
|
)
|
||||||
|
const downloadInfo = await response.json()
|
||||||
|
onClosed()
|
||||||
|
window.location = downloadInfo.url
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="popover">
|
||||||
|
<h5>Export Data</h5>
|
||||||
|
<Select label="Format" secondary thin bind:value={exportFormat}>
|
||||||
|
{#each FORMATS as format}
|
||||||
|
<option value={format.key}>{format.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<div class="footer">
|
||||||
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
|
<Button primary on:click={exportView}>Export</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.popover {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,13 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Button, Input, Select, DatePicker } from "@budibase/bbui"
|
||||||
Popover,
|
|
||||||
TextButton,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
DatePicker,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
@ -51,9 +43,7 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
export let onClosed
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewModel = $backendUiStore.models.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||||
|
@ -63,7 +53,7 @@
|
||||||
function saveView() {
|
function saveView() {
|
||||||
backendUiStore.actions.views.save(view)
|
backendUiStore.actions.views.save(view)
|
||||||
notifier.success(`View ${view.name} saved.`)
|
notifier.success(`View ${view.name} saved.`)
|
||||||
dropdown.hide()
|
onClosed()
|
||||||
analytics.captureEvent("Added View Filter", {
|
analytics.captureEvent("Added View Filter", {
|
||||||
filters: JSON.stringify(view.filters),
|
filters: JSON.stringify(view.filters),
|
||||||
})
|
})
|
||||||
|
@ -115,96 +105,93 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div class="actions">
|
||||||
<TextButton
|
|
||||||
text
|
|
||||||
small
|
|
||||||
on:click={dropdown.show}
|
|
||||||
active={view.filters && view.filters.length}>
|
|
||||||
<Icon name="filter" />
|
|
||||||
Filter
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Filter</h5>
|
<h5>Filter</h5>
|
||||||
<div class="input-group-row">
|
{#if view.filters.length}
|
||||||
{#each view.filters as filter, idx}
|
<div class="input-group-row">
|
||||||
{#if idx === 0}
|
{#each view.filters as filter, idx}
|
||||||
<p>Where</p>
|
{#if idx === 0}
|
||||||
{:else}
|
<p>Where</p>
|
||||||
<Select secondary thin bind:value={filter.conjunction}>
|
{:else}
|
||||||
|
<Select secondary thin bind:value={filter.conjunction}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each CONJUNCTIONS as conjunction}
|
||||||
|
<option value={conjunction.key}>{conjunction.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{/if}
|
||||||
|
<Select
|
||||||
|
secondary
|
||||||
|
thin
|
||||||
|
bind:value={filter.key}
|
||||||
|
on:change={fieldChanged(filter)}>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each CONJUNCTIONS as conjunction}
|
{#each fields as field}
|
||||||
<option value={conjunction.key}>{conjunction.name}</option>
|
<option value={field}>{field}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{/if}
|
<Select secondary thin bind:value={filter.condition}>
|
||||||
<Select
|
|
||||||
secondary
|
|
||||||
thin
|
|
||||||
bind:value={filter.key}
|
|
||||||
on:change={fieldChanged(filter)}>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
{#each fields as field}
|
|
||||||
<option value={field}>{field}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
<Select secondary thin bind:value={filter.condition}>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
{#each CONDITIONS as condition}
|
|
||||||
<option value={condition.key}>{condition.name}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{#if filter.key && isMultipleChoice(filter.key)}
|
|
||||||
<Select secondary thin bind:value={filter.value}>
|
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each fieldOptions(filter.key) as option}
|
{#each CONDITIONS as condition}
|
||||||
<option value={option}>{option.toString()}</option>
|
<option value={condition.key}>{condition.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{:else if filter.key && isDate(filter.key)}
|
{#if filter.key && isMultipleChoice(filter.key)}
|
||||||
<DatePicker
|
<Select secondary thin bind:value={filter.value}>
|
||||||
bind:value={filter.value}
|
<option value="">Choose an option</option>
|
||||||
placeholder={filter.key || fields[0]} />
|
{#each fieldOptions(filter.key) as option}
|
||||||
{:else if filter.key && isNumber(filter.key)}
|
<option value={option}>{option.toString()}</option>
|
||||||
<Input
|
{/each}
|
||||||
thin
|
</Select>
|
||||||
bind:value={filter.value}
|
{:else if filter.key && isDate(filter.key)}
|
||||||
placeholder={filter.key || fields[0]}
|
<DatePicker
|
||||||
type="number" />
|
bind:value={filter.value}
|
||||||
{:else}
|
placeholder={filter.key || fields[0]} />
|
||||||
<Input
|
{:else if filter.key && isNumber(filter.key)}
|
||||||
thin
|
<Input
|
||||||
placeholder={filter.key || fields[0]}
|
thin
|
||||||
bind:value={filter.value} />
|
bind:value={filter.value}
|
||||||
{/if}
|
placeholder={filter.key || fields[0]}
|
||||||
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} />
|
type="number" />
|
||||||
{/each}
|
{:else}
|
||||||
</div>
|
<Input
|
||||||
<div class="button-group">
|
thin
|
||||||
|
placeholder={filter.key || fields[0]}
|
||||||
|
bind:value={filter.value} />
|
||||||
|
{/if}
|
||||||
|
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="footer">
|
||||||
<Button text on:click={addFilter}>Add Filter</Button>
|
<Button text on:click={addFilter}>Add Filter</Button>
|
||||||
<div>
|
<div class="buttons">
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
<Button primary on:click={saveView}>Save</Button>
|
<Button primary on:click={saveView}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.actions {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
margin-bottom: var(--spacing-l);
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.footer {
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
:global(.button-group > div > button) {
|
display: flex;
|
||||||
margin-left: var(--spacing-m);
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ri-close-circle-fill {
|
.ri-close-circle-fill {
|
||||||
|
@ -215,7 +202,6 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(50px, auto) 1fr 1fr 1fr 15px;
|
grid-template-columns: minmax(50px, auto) 1fr 1fr 1fr 15px;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
margin-bottom: var(--spacing-l);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Button, Input, Select } from "@budibase/bbui"
|
||||||
Popover,
|
|
||||||
TextButton,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
|
||||||
const CALCULATIONS = [
|
|
||||||
{
|
|
||||||
name: "Statistics",
|
|
||||||
key: "stats",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
export let onClosed
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewModel = $backendUiStore.models.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||||
|
@ -30,20 +14,14 @@
|
||||||
function saveView() {
|
function saveView() {
|
||||||
backendUiStore.actions.views.save(view)
|
backendUiStore.actions.views.save(view)
|
||||||
notifier.success(`View ${view.name} saved.`)
|
notifier.success(`View ${view.name} saved.`)
|
||||||
dropdown.hide()
|
onClosed()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div class="actions">
|
||||||
<TextButton text small active={!!view.groupBy} on:click={dropdown.show}>
|
<h5>Group</h5>
|
||||||
<Icon name="group" />
|
|
||||||
Group By
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Group By</h5>
|
|
||||||
<div class="input-group-row">
|
<div class="input-group-row">
|
||||||
<p>Group By</p>
|
<p>By</p>
|
||||||
<Select secondary thin bind:value={view.groupBy}>
|
<Select secondary thin bind:value={view.groupBy}>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each fields as field}
|
{#each fields as field}
|
||||||
|
@ -51,30 +29,33 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="footer">
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
<Button primary on:click={saveView}>Save</Button>
|
<Button primary on:click={saveView}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.actions {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
margin-bottom: var(--spacing-l);
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.footer {
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-row {
|
.input-group-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 75px 1fr 20px 1fr;
|
grid-template-columns: 20px 1fr;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
margin-bottom: var(--spacing-l);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
|
||||||
|
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||||
|
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
|
||||||
|
let modal
|
||||||
|
|
||||||
|
function showModal() {
|
||||||
|
dropdown.hide()
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDelete() {
|
||||||
|
dropdown.hide()
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRow() {
|
||||||
|
await api.deleteRecord(row)
|
||||||
|
notifier.success("Record deleted")
|
||||||
|
backendUiStore.actions.records.delete(row)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor} on:click={dropdown.show}>
|
||||||
|
<i class="ri-more-line" />
|
||||||
|
</div>
|
||||||
|
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
||||||
|
<ul>
|
||||||
|
<li data-cy="edit-row" on:click={showModal}>
|
||||||
|
<Icon name="edit" />
|
||||||
|
<span>Edit</span>
|
||||||
|
</li>
|
||||||
|
<li data-cy="delete-row" on:click={showDelete}>
|
||||||
|
<Icon name="delete" />
|
||||||
|
<span>Delete</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
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" />
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<CreateEditRecordModal record={row} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ri-more-line:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--spacing-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--ink);
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
margin: auto 0px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:active {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -19,26 +19,24 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.indented {
|
.indented {
|
||||||
grid-template-columns: 50px 1fr 20px;
|
grid-template-columns: 46px 1fr 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indented i {
|
.indented i {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
padding: 0 10px 0 10px;
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
height: 36px;
|
border-radius: var(--border-radius-m);
|
||||||
border-radius: 5px;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px 1fr 20px;
|
grid-template-columns: 20px 1fr 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: 4px;
|
margin-bottom: var(--spacing-xs);
|
||||||
margin-bottom: 4px;
|
grid-gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
@ -53,6 +51,5 @@
|
||||||
i {
|
i {
|
||||||
color: var(--grey-7);
|
color: var(--grey-7);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin-right: 8px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,16 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { slide } from "svelte/transition"
|
|
||||||
import { Switcher } from "@budibase/bbui"
|
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import ListItem from "./ListItem.svelte"
|
import ListItem from "./ListItem.svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||||
import CreateTablePopover from "./CreateTable.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
import EditTablePopover from "./EditTable.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import EditViewPopover from "./EditView.svelte"
|
import { Heading } from "@budibase/bbui"
|
||||||
|
import { Spacer } from "@budibase/bbui"
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
$: selectedView =
|
$: selectedView =
|
||||||
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
||||||
|
@ -30,8 +26,9 @@
|
||||||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||||
<div class="hierarchy">
|
<div class="hierarchy">
|
||||||
<div class="components-list-container">
|
<div class="components-list-container">
|
||||||
<h4>Tables</h4>
|
<Heading small>Tables</Heading>
|
||||||
<CreateTablePopover />
|
<Spacer medium />
|
||||||
|
<CreateTableModal />
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
<ListItem
|
<ListItem
|
||||||
|
@ -63,17 +60,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h4 {
|
h5 {
|
||||||
font-weight: 500;
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-root {
|
.items-root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 100%;
|
justify-content: flex-start;
|
||||||
height: 100%;
|
align-items: stretch;
|
||||||
background: var(--white);
|
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hierarchy {
|
.hierarchy {
|
||||||
|
@ -82,7 +80,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.hierarchy-items-container {
|
.hierarchy-items-container {
|
||||||
margin-top: 20px;
|
margin-top: var(--spacing-xl);
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -154,7 +154,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
padding: var(--spacing-s) var(--spacing-l);
|
padding: var(--spacing-m) var(--spacing-l);
|
||||||
transition: all 0.2s ease 0s;
|
transition: all 0.2s ease 0s;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
@ -169,6 +169,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--grey-2);
|
background-color: var(--grey-2);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
|
line-height: normal;
|
||||||
|
border: var(--border-transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.omit-button {
|
.omit-button {
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } 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 TableDataImport from "../TableDataImport.svelte"
|
||||||
|
import analytics from "analytics"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let name
|
||||||
|
let dataImport
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
name = ""
|
||||||
|
dataImport = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTable() {
|
||||||
|
const model = await backendUiStore.actions.models.save({
|
||||||
|
name,
|
||||||
|
schema: dataImport.schema || {},
|
||||||
|
dataImport,
|
||||||
|
})
|
||||||
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
|
$goto(`./model/${model._id}`)
|
||||||
|
analytics.captureEvent("Table Created", { name })
|
||||||
|
}
|
||||||
|
</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={!name || (dataImport && !dataImport.valid)}>
|
||||||
|
<Input
|
||||||
|
data-cy="table-name-input"
|
||||||
|
thin
|
||||||
|
label="Table Name"
|
||||||
|
bind:value={name} />
|
||||||
|
<div>
|
||||||
|
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||||
|
<TableDataImport bind:dataImport />
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
|
@ -0,0 +1,141 @@
|
||||||
|
<script>
|
||||||
|
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 ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
|
||||||
|
export let table
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
let editing
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
|
$: fields = Object.keys(table.schema)
|
||||||
|
|
||||||
|
function showEditor() {
|
||||||
|
editing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideEditor() {
|
||||||
|
dropdown?.hide()
|
||||||
|
editing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function showModal() {
|
||||||
|
hideEditor()
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTable() {
|
||||||
|
await backendUiStore.actions.models.delete(table)
|
||||||
|
notifier.success("Table deleted")
|
||||||
|
hideEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
await backendUiStore.actions.models.save(table)
|
||||||
|
notifier.success("Table renamed successfully")
|
||||||
|
hideEditor()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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} />
|
||||||
|
<Select
|
||||||
|
label="Primary Display Column"
|
||||||
|
thin
|
||||||
|
secondary
|
||||||
|
bind:value={table.primaryDisplay}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each fields as field}
|
||||||
|
<option value={field}>{field}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<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-table" on:click={showModal}>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</DropdownMenu>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`}
|
||||||
|
okText="Delete Table"
|
||||||
|
onOk={deleteTable}
|
||||||
|
title="Confirm Delete" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.icon {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.icon i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
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,20 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import DeleteViewModal from "components/database/DataTable/modals/DeleteView.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let dropdown
|
let dropdown
|
||||||
|
|
||||||
let editing
|
let editing
|
||||||
let originalName = view.name
|
let originalName = view.name
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
function showEditor() {
|
function showEditor() {
|
||||||
editing = true
|
editing = true
|
||||||
|
@ -23,76 +21,96 @@
|
||||||
function hideEditor() {
|
function hideEditor() {
|
||||||
dropdown.hide()
|
dropdown.hide()
|
||||||
editing = false
|
editing = false
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteView = () => {
|
function showDelete() {
|
||||||
open(
|
dropdown.hide()
|
||||||
DeleteViewModal,
|
confirmDeleteDialog.show()
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
viewName: view.name,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
async function save() {
|
||||||
backendUiStore.actions.views.save({
|
await backendUiStore.actions.views.save({
|
||||||
originalName,
|
originalName,
|
||||||
...view,
|
...view,
|
||||||
})
|
})
|
||||||
notifier.success("Renamed View Successfully.")
|
notifier.success("View renamed successfully")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteView() {
|
||||||
|
const name = view.name
|
||||||
|
const id = view.modelId
|
||||||
|
await backendUiStore.actions.views.delete(name)
|
||||||
|
notifier.success("View deleted")
|
||||||
|
$goto(`./model/${id}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor} on:click={dropdown.show}>
|
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
||||||
<i class="ri-more-line" />
|
<i class="ri-more-line" />
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
<DropdownMenu align="left" {anchor} bind:this={dropdown}>
|
||||||
{#if editing}
|
{#if editing}
|
||||||
<h5>Edit View</h5>
|
<div class="actions">
|
||||||
<div class="container">
|
<h5>Edit View</h5>
|
||||||
<Input placeholder="View Name" thin bind:value={view.name} />
|
<Input label="View Name" thin bind:value={view.name} />
|
||||||
</div>
|
<footer>
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={hideEditor}>Cancel</Button>
|
<Button secondary on:click={hideEditor}>Cancel</Button>
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button primary on:click={save}>Save</Button>
|
<Button primary on:click={save}>Save</Button>
|
||||||
</div>
|
</footer>
|
||||||
</footer>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<ul>
|
<ul>
|
||||||
<li on:click={showEditor}>
|
<li on:click={showEditor}>
|
||||||
<Icon name="edit" />
|
<Icon name="edit" />
|
||||||
Edit
|
Edit
|
||||||
</li>
|
</li>
|
||||||
<li data-cy="delete-view" on:click={deleteView}>
|
<li data-cy="delete-view" on:click={showDelete}>
|
||||||
<Icon name="delete" />
|
<Icon name="delete" />
|
||||||
Delete
|
Delete
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
</DropdownMenu>
|
</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" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
div.icon {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.icon i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
footer {
|
||||||
padding: var(--spacing-xl);
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 0;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--spacing-s) 0;
|
padding: var(--spacing-s) 0;
|
||||||
}
|
}
|
||||||
|
@ -115,29 +133,4 @@
|
||||||
li:active {
|
li:active {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-1 {
|
|
||||||
grid-column-start: 1;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -1,57 +1,31 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal, Button, Heading, Spacer } from "@budibase/bbui"
|
import { Modal, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
export let title = ""
|
export let title = ""
|
||||||
export let body = ""
|
export let body = ""
|
||||||
export let okText = "OK"
|
export let okText = "Confirm"
|
||||||
export let cancelText = "Cancel"
|
export let cancelText = "Cancel"
|
||||||
export let onOk = () => {}
|
export let onOk = undefined
|
||||||
export let onCancel = () => {}
|
export let onCancel = undefined
|
||||||
|
|
||||||
|
let modal
|
||||||
|
|
||||||
export const show = () => {
|
export const show = () => {
|
||||||
theModal.show()
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hide = () => {
|
export const hide = () => {
|
||||||
theModal.hide()
|
modal.hide()
|
||||||
}
|
|
||||||
|
|
||||||
let theModal
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
hide()
|
|
||||||
onCancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
const ok = () => {
|
|
||||||
const result = onOk()
|
|
||||||
// allow caller to return false, to cancel the "ok"
|
|
||||||
if (result === false) return
|
|
||||||
hide()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal id={title} bind:this={theModal}>
|
<Modal bind:this={modal} on:hide={onCancel}>
|
||||||
<h2>{title}</h2>
|
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
||||||
<Spacer extraLarge />
|
<div class="body">{body}</div>
|
||||||
<slot class="rows">{body}</slot>
|
</ModalContent>
|
||||||
<Spacer extraLarge />
|
|
||||||
<div class="modal-footer">
|
|
||||||
<Button red wide on:click={ok}>{okText}</Button>
|
|
||||||
<Button secondary wide on:click={cancel}>{cancelText}</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h2 {
|
.body {
|
||||||
font-size: var(--font-size-xl);
|
font-size: var(--font-size-s);
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-s);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,6 +11,4 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bb-margin-m">
|
<DatePicker {label} on:change={onChange} {value} />
|
||||||
<DatePicker placeholder={label} on:change={onChange} {value} />
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -5,9 +5,25 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasErrors}
|
{#if hasErrors}
|
||||||
<div class="bb__alert bb__alert--danger">
|
<div class="container bb__alert bb__alert--danger">
|
||||||
{#each errors as error}
|
{#each errors as error}
|
||||||
<div>{error.dataPath} {error.message}</div>
|
<div class="error">{error.dataPath} {error.message}</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.error:first-letter {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -2,101 +2,52 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
import { Select, Label, Multiselect } from "@budibase/bbui"
|
||||||
|
import { capitalise } from "../../helpers"
|
||||||
|
|
||||||
export let modelId
|
export let schema
|
||||||
export let linkName
|
export let linkedRecords = []
|
||||||
export let linked = []
|
|
||||||
|
|
||||||
let records = []
|
let records = []
|
||||||
let model = {}
|
|
||||||
|
|
||||||
let linkedRecords = new Set(linked)
|
$: label = capitalise(schema.name)
|
||||||
|
$: linkedModelId = schema.modelId
|
||||||
|
$: linkedModel = $backendUiStore.models.find(
|
||||||
|
model => model._id === linkedModelId
|
||||||
|
)
|
||||||
|
$: fetchRecords(linkedModelId)
|
||||||
|
|
||||||
$: linked = [...linkedRecords]
|
async function fetchRecords(linkedModelId) {
|
||||||
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel.name]
|
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records`
|
||||||
$: schema = $backendUiStore.selectedModel.schema
|
try {
|
||||||
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
async function fetchRecords() {
|
records = await response.json()
|
||||||
const FETCH_RECORDS_URL = `/api/${modelId}/records`
|
} catch (error) {
|
||||||
const response = await api.get(FETCH_RECORDS_URL)
|
console.log(error)
|
||||||
const modelResponse = await api.get(`/api/models/${modelId}`)
|
records = []
|
||||||
|
|
||||||
model = await modelResponse.json()
|
|
||||||
records = await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
function linkRecord(id) {
|
|
||||||
if (linkedRecords.has(id)) {
|
|
||||||
linkedRecords.delete(id)
|
|
||||||
} else {
|
|
||||||
linkedRecords.add(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
linkedRecords = linkedRecords
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
function getPrettyName(record) {
|
||||||
fetchRecords()
|
return record[linkedModel.primaryDisplay || "_id"]
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
{#if linkedModel.primaryDisplay == null}
|
||||||
<header>
|
<Label extraSmall grey>{label}</Label>
|
||||||
<h3>{linkName}</h3>
|
<Label small black>
|
||||||
</header>
|
Please choose a primary display column for the
|
||||||
{#each records as record}
|
<b>{linkedModel.name}</b>
|
||||||
<div class="linked-record" on:click={() => linkRecord(record._id)}>
|
table.
|
||||||
<div class="fields" class:selected={linkedRecords.has(record._id)}>
|
</Label>
|
||||||
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
{:else}
|
||||||
<div class="field">
|
<Multiselect
|
||||||
<span>{model.schema[key].name}</span>
|
secondary
|
||||||
<p>{record[key]}</p>
|
bind:value={linkedRecords}
|
||||||
</div>
|
{label}
|
||||||
{/each}
|
placeholder="Choose some options">
|
||||||
</div>
|
{#each records as record}
|
||||||
</div>
|
<option value={record._id}>{getPrettyName(record)}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</Multiselect>
|
||||||
|
{/if}
|
||||||
<style>
|
|
||||||
.fields.selected {
|
|
||||||
background: var(--grey-2);
|
|
||||||
border: var(--purple) 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fields {
|
|
||||||
padding: 15px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
grid-gap: 20px;
|
|
||||||
background: var(--white);
|
|
||||||
border: 1px solid var(--grey);
|
|
||||||
border-radius: 5px;
|
|
||||||
transition: 0.5s all;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fields:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field span {
|
|
||||||
color: var(--ink-lighter);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field p {
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
word-break: break-word;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { notificationStore } from "builderStore/store/notifications"
|
import { notificationStore } from "builderStore/store/notifications"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
|
|
||||||
export let themes = {
|
export let themes = {
|
||||||
danger: "#E26D69",
|
danger: "#E26D69",
|
||||||
|
@ -24,36 +24,42 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="notifications">
|
<div class="notifications">
|
||||||
{#each $notificationStore.notifications as notification (notification.id)}
|
{#each $notificationStore.notifications as notification (notification.id)}
|
||||||
<li
|
<div
|
||||||
class="toast"
|
class="toast"
|
||||||
style="background: {themes[notification.type]};"
|
style="background: {themes[notification.type]};"
|
||||||
transition:fade>
|
transition:fly={{ y: -30 }}>
|
||||||
<div class="content">{notification.message}</div>
|
<div class="content">{notification.message}</div>
|
||||||
{#if notification.icon}
|
{#if notification.icon}
|
||||||
<i class={notification.icon} />
|
<i class={notification.icon} />
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.notifications {
|
.notifications {
|
||||||
width: 40vw;
|
|
||||||
list-style: none;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 10px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-left: auto;
|
margin: 0 auto;
|
||||||
margin-right: auto;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
|
flex: 0 0 auto;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
/* The toasts now support being auto sized, so this static width could be removed */
|
||||||
|
width: 40vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
.numberbox {
|
.numberbox {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { join } from "lodash/fp"
|
import { join } from "lodash/fp"
|
||||||
|
import { TextArea, Label } from "@budibase/bbui"
|
||||||
|
|
||||||
export let values
|
export let values
|
||||||
export let label
|
export let label
|
||||||
|
@ -15,35 +16,12 @@
|
||||||
$: valuesText = join("\n")(values)
|
$: valuesText = join("\n")(values)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="margin">
|
<div class="container">
|
||||||
<label class="label">{label}</label>
|
<TextArea {label} value={valuesText} thin on:change={inputChanged} />
|
||||||
|
|
||||||
<textarea value={valuesText} on:change={inputChanged} />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.margin {
|
.container :global(textarea) {
|
||||||
margin-bottom: 16px;
|
min-height: 100px;
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
font-size: 14px;
|
|
||||||
height: 200px;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: none;
|
|
||||||
cursor: text;
|
|
||||||
background: var(--grey-2);
|
|
||||||
padding: 12px;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: Inter;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
export let ids = []
|
|
||||||
export let field
|
|
||||||
|
|
||||||
let records = []
|
|
||||||
let open = false
|
|
||||||
let model
|
|
||||||
|
|
||||||
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel.name]
|
|
||||||
|
|
||||||
async function fetchRecords() {
|
|
||||||
const response = await api.post("/api/records/search", {
|
|
||||||
keys: ids,
|
|
||||||
})
|
|
||||||
const modelResponse = await api.get(`/api/models/${field.modelId}`)
|
|
||||||
records = await response.json()
|
|
||||||
model = await modelResponse.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
$: ids && fetchRecords()
|
|
||||||
|
|
||||||
function toggleOpen() {
|
|
||||||
open = !open
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
fetchRecords()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<a on:click={toggleOpen}>{records.length}</a>
|
|
||||||
{#if open}
|
|
||||||
<div class="popover" transition:fade>
|
|
||||||
<header>
|
|
||||||
<h3>{field.name}</h3>
|
|
||||||
<i class="ri-close-circle-fill" on:click={toggleOpen} />
|
|
||||||
</header>
|
|
||||||
{#each records as record}
|
|
||||||
<div class="linked-record">
|
|
||||||
<div class="fields">
|
|
||||||
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
|
||||||
<div class="field">
|
|
||||||
<span>{model.schema[key].name}</span>
|
|
||||||
<p>{record[key]}</p>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
display: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 24px;
|
|
||||||
color: var(--ink-lighter);
|
|
||||||
}
|
|
||||||
|
|
||||||
i:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
|
||||||
width: 500px;
|
|
||||||
position: absolute;
|
|
||||||
right: 15%;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border: 1px solid var(--grey);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fields {
|
|
||||||
padding: 15px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
grid-gap: 20px;
|
|
||||||
background: var(--white);
|
|
||||||
border: 1px solid var(--grey);
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field span {
|
|
||||||
color: var(--ink-lighter);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field p {
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
word-break: break-word;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,149 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import fsort from "fast-sort"
|
|
||||||
import getOr from "lodash/fp/getOr"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import { Button, Icon } from "@budibase/bbui"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import AttachmentList from "./AttachmentList.svelte"
|
|
||||||
import TablePagination from "./TablePagination.svelte"
|
|
||||||
import RowPopover from "./popovers/Row.svelte"
|
|
||||||
import ColumnPopover from "./popovers/Column.svelte"
|
|
||||||
import ViewPopover from "./popovers/View.svelte"
|
|
||||||
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
|
||||||
import EditRowPopover from "./popovers/EditRow.svelte"
|
|
||||||
import CalculationPopover from "./popovers/Calculate.svelte"
|
|
||||||
|
|
||||||
export let schema = []
|
|
||||||
export let data = []
|
|
||||||
export let title
|
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 10
|
|
||||||
|
|
||||||
let currentPage = 0
|
|
||||||
|
|
||||||
$: columns = schema ? Object.keys(schema) : []
|
|
||||||
|
|
||||||
$: sort = $backendUiStore.sort
|
|
||||||
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
|
||||||
$: paginatedData =
|
|
||||||
sorted && sorted.length
|
|
||||||
? sorted.slice(
|
|
||||||
currentPage * ITEMS_PER_PAGE,
|
|
||||||
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="table-controls">
|
|
||||||
<h2 class="title">{title}</h2>
|
|
||||||
<div class="popovers">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="bb-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{#each columns as header}
|
|
||||||
<th>{header}</th>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#if paginatedData.length === 0}
|
|
||||||
<div class="no-data">No Data.</div>
|
|
||||||
{/if}
|
|
||||||
{#each paginatedData as row}
|
|
||||||
<tr>
|
|
||||||
{#each columns as header}
|
|
||||||
<td>
|
|
||||||
{#if schema[header].type === 'attachment'}
|
|
||||||
<AttachmentList files={row[header] || []} />
|
|
||||||
{:else}{getOr('', header, row)}{/if}
|
|
||||||
</td>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<TablePagination
|
|
||||||
{data}
|
|
||||||
bind:currentPage
|
|
||||||
pageItemCount={paginatedData.length}
|
|
||||||
{ITEMS_PER_PAGE} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
height: 40px;
|
|
||||||
background: var(--grey-3);
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
thead th {
|
|
||||||
color: var(--ink);
|
|
||||||
text-transform: capitalize;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
transition: 0.5s all;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:hover {
|
|
||||||
color: var(--blue);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
max-width: 200px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
border-bottom: 1px solid var(--grey-4);
|
|
||||||
transition: 0.3s background-color;
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:hover {
|
|
||||||
background: var(--grey-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-controls {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popovers {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.popovers > div) {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data {
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,55 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import fsort from "fast-sort"
|
|
||||||
import getOr from "lodash/fp/getOr"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import { Button, Icon } from "@budibase/bbui"
|
|
||||||
import Table from "./Table.svelte"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import LinkedRecord from "./LinkedRecord.svelte"
|
|
||||||
import TablePagination from "./TablePagination.svelte"
|
|
||||||
import RowPopover from "./popovers/Row.svelte"
|
|
||||||
import ColumnPopover from "./popovers/Column.svelte"
|
|
||||||
import ViewPopover from "./popovers/View.svelte"
|
|
||||||
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
|
||||||
import EditRowPopover from "./popovers/EditRow.svelte"
|
|
||||||
import CalculationPopover from "./popovers/Calculate.svelte"
|
|
||||||
import GroupByPopover from "./popovers/GroupBy.svelte"
|
|
||||||
import FilterPopover from "./popovers/Filter.svelte"
|
|
||||||
import ExportPopover from "./popovers/Export.svelte"
|
|
||||||
|
|
||||||
export let view = {}
|
|
||||||
|
|
||||||
let data = []
|
|
||||||
|
|
||||||
$: name = view.name
|
|
||||||
$: filters = view.filters
|
|
||||||
$: field = view.field
|
|
||||||
$: groupBy = view.groupBy
|
|
||||||
$: !name.startsWith("all_") && filters && fetchViewData(name, field, groupBy)
|
|
||||||
|
|
||||||
async function fetchViewData(name, field, groupBy) {
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
|
|
||||||
if (field) {
|
|
||||||
params.set("field", field)
|
|
||||||
params.set("stats", true)
|
|
||||||
}
|
|
||||||
if (groupBy) params.set("group", groupBy)
|
|
||||||
|
|
||||||
let QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
|
||||||
|
|
||||||
const response = await api.get(QUERY_VIEW_URL)
|
|
||||||
data = await response.json()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Table title={decodeURI(name)} schema={view.schema} {data}>
|
|
||||||
<FilterPopover {view} />
|
|
||||||
<CalculationPopover {view} />
|
|
||||||
{#if view.calculation}
|
|
||||||
<GroupByPopover {view} />
|
|
||||||
{/if}
|
|
||||||
<ExportPopover {view} />
|
|
||||||
</Table>
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./ModelDataTable.svelte"
|
|
|
@ -1,161 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { Input, TextArea, Button, Select } from "@budibase/bbui"
|
|
||||||
import { cloneDeep, merge } from "lodash/fp"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { FIELDS } from "constants/backend"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
|
||||||
import NumberBox from "components/common/NumberBox.svelte"
|
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
|
||||||
import Checkbox from "components/common/Checkbox.svelte"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import DatePicker from "components/common/DatePicker.svelte"
|
|
||||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
|
||||||
import * as api from "../api"
|
|
||||||
|
|
||||||
let fieldDefinitions = cloneDeep(FIELDS)
|
|
||||||
|
|
||||||
export let onClosed
|
|
||||||
export let field = {
|
|
||||||
type: "string",
|
|
||||||
constraints: fieldDefinitions.STRING.constraints,
|
|
||||||
}
|
|
||||||
|
|
||||||
let originalName = field.name
|
|
||||||
$: required = field && field.constraints && field.constraints.presence
|
|
||||||
|
|
||||||
async function saveColumn() {
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
backendUiStore.actions.models.saveField({
|
|
||||||
originalName,
|
|
||||||
field,
|
|
||||||
})
|
|
||||||
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
onClosed()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFieldConstraints(event) {
|
|
||||||
const { type, constraints } = fieldDefinitions[
|
|
||||||
event.target.value.toUpperCase()
|
|
||||||
]
|
|
||||||
|
|
||||||
field.type = type
|
|
||||||
field.constraints = constraints
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPresence = required => (required ? { allowEmpty: false } : false)
|
|
||||||
|
|
||||||
const requiredChanged = ev => {
|
|
||||||
const req = ev.target.checked
|
|
||||||
field.constraints.presence = req ? { allowEmpty: false } : false
|
|
||||||
required = req
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<Input placeholder="Name" thin bind:value={field.name} />
|
|
||||||
|
|
||||||
<Select
|
|
||||||
secondary
|
|
||||||
thin
|
|
||||||
on:change={handleFieldConstraints}
|
|
||||||
bind:value={field.type}>
|
|
||||||
{#each Object.values(fieldDefinitions) as field}
|
|
||||||
<option value={field.type}>{field.name}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<div class="field">
|
|
||||||
<label>Required</label>
|
|
||||||
<input type="checkbox" checked={required} on:change={requiredChanged} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if field.type === 'string' && field.constraints}
|
|
||||||
<NumberBox
|
|
||||||
label="Max Length"
|
|
||||||
bind:value={field.constraints.length.maximum} />
|
|
||||||
<ValuesList
|
|
||||||
label="Categories"
|
|
||||||
bind:values={field.constraints.inclusion} />
|
|
||||||
{:else if field.type === 'datetime' && field.constraints}
|
|
||||||
<DatePicker
|
|
||||||
label="Earliest"
|
|
||||||
bind:value={field.constraints.datetime.earliest} />
|
|
||||||
<DatePicker
|
|
||||||
label="Latest"
|
|
||||||
bind:value={field.constraints.datetime.latest} />
|
|
||||||
{:else if field.type === 'number' && field.constraints}
|
|
||||||
<NumberBox
|
|
||||||
label="Min Value"
|
|
||||||
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
|
|
||||||
<NumberBox
|
|
||||||
label="Max Value"
|
|
||||||
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
|
|
||||||
{:else if field.type === 'link'}
|
|
||||||
<div class="field">
|
|
||||||
<label>Link</label>
|
|
||||||
<select class="budibase__input" bind:value={field.modelId}>
|
|
||||||
<option value={''} />
|
|
||||||
{#each $backendUiStore.models as model}
|
|
||||||
{#if model._id !== $backendUiStore.draftModel._id}
|
|
||||||
<option value={model._id}>{model.name}</option>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button primary on:click={saveColumn}>Save Column</Button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.actions {
|
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px 30px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 20px 1fr;
|
|
||||||
align-items: center;
|
|
||||||
grid-gap: 5px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: var(--spacing-l);
|
|
||||||
font-family: var(--font-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,93 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount, tick } from "svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { compose, map, get, flatten } from "lodash/fp"
|
|
||||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
|
||||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
|
||||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
|
||||||
import * as api from "../api"
|
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
|
||||||
|
|
||||||
export let record = {}
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
let errors = []
|
|
||||||
let selectedModel
|
|
||||||
|
|
||||||
$: modelSchema = $backendUiStore.selectedModel
|
|
||||||
? Object.entries($backendUiStore.selectedModel.schema)
|
|
||||||
: []
|
|
||||||
|
|
||||||
async function saveRecord() {
|
|
||||||
const recordResponse = await api.saveRecord(
|
|
||||||
{
|
|
||||||
...record,
|
|
||||||
modelId: $backendUiStore.selectedModel._id,
|
|
||||||
},
|
|
||||||
$backendUiStore.selectedModel._id
|
|
||||||
)
|
|
||||||
if (recordResponse.errors) {
|
|
||||||
errors = Object.keys(recordResponse.errors)
|
|
||||||
.map(k => ({ dataPath: k, message: recordResponse.errors[k] }))
|
|
||||||
.flat()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onClosed()
|
|
||||||
notifier.success("Record saved successfully.")
|
|
||||||
backendUiStore.actions.records.save(recordResponse)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<ErrorsBox {errors} />
|
|
||||||
<form on:submit|preventDefault>
|
|
||||||
{#each modelSchema as [key, meta]}
|
|
||||||
<div class="bb-margin-xl">
|
|
||||||
{#if meta.type === 'link'}
|
|
||||||
<LinkedRecordSelector
|
|
||||||
bind:linked={record[key]}
|
|
||||||
linkName={meta.name}
|
|
||||||
modelId={meta.modelId} />
|
|
||||||
{:else}
|
|
||||||
<RecordFieldControl {meta} bind:value={record[key]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button primary on:click={saveRecord}>Save</Button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.actions {
|
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px 30px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,61 +0,0 @@
|
||||||
<script>
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import * as api from "../api"
|
|
||||||
|
|
||||||
export let record
|
|
||||||
export let onClosed
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="content">
|
|
||||||
<header>
|
|
||||||
<i class="ri-information-line alert" />
|
|
||||||
<h4 class="budibase__title--4">Delete Record</h4>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete this record? All of your data will be
|
|
||||||
permanently removed. This action cannot be undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<ActionButton on:click={onClosed}>Cancel</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
alert
|
|
||||||
on:click={async () => {
|
|
||||||
await api.deleteRecord(record)
|
|
||||||
notifier.danger('Record deleted')
|
|
||||||
backendUiStore.actions.records.delete(record)
|
|
||||||
onClosed()
|
|
||||||
}}>
|
|
||||||
Delete
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.alert {
|
|
||||||
color: rgba(255, 0, 31, 1);
|
|
||||||
background: var(--grey-1);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-actions {
|
|
||||||
padding: 10px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,64 +0,0 @@
|
||||||
<script>
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import * as api from "../api"
|
|
||||||
|
|
||||||
export let table
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
function deleteTable() {
|
|
||||||
backendUiStore.actions.models.delete(table)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="content">
|
|
||||||
<header>
|
|
||||||
<i class="ri-information-line alert" />
|
|
||||||
<h4 class="budibase__title--4">Delete Table</h4>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete this table? All of your data will be
|
|
||||||
permanently removed. This action cannot be undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<ActionButton on:click={onClosed}>Cancel</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
alert
|
|
||||||
on:click={async () => {
|
|
||||||
await backendUiStore.actions.models.delete(table)
|
|
||||||
notifier.danger('Table deleted')
|
|
||||||
onClosed()
|
|
||||||
}}>
|
|
||||||
Delete
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.alert {
|
|
||||||
color: rgba(255, 0, 31, 1);
|
|
||||||
background: var(--grey-1);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-actions {
|
|
||||||
padding: 10px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import * as api from "../api"
|
|
||||||
|
|
||||||
export let viewName
|
|
||||||
export let onClosed
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="content">
|
|
||||||
<header>
|
|
||||||
<i class="ri-information-line alert" />
|
|
||||||
<h4 class="budibase__title--4">Delete View</h4>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete this view? All of your data will be
|
|
||||||
permanently removed. This action cannot be undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<ActionButton on:click={onClosed}>Cancel</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
alert
|
|
||||||
on:click={async () => {
|
|
||||||
await backendUiStore.actions.views.delete(viewName)
|
|
||||||
notifier.danger(`View ${viewName} deleted.`)
|
|
||||||
$goto(`./backend`)
|
|
||||||
onClosed()
|
|
||||||
}}>
|
|
||||||
Delete
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.alert {
|
|
||||||
color: rgba(255, 0, 31, 1);
|
|
||||||
background: var(--grey-1);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-actions {
|
|
||||||
padding: 10px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,75 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, Select, Label, DatePicker } from "@budibase/bbui"
|
|
||||||
import Dropzone from "components/common/Dropzone.svelte"
|
|
||||||
|
|
||||||
export let meta
|
|
||||||
export let value = meta.type === "boolean" ? false : ""
|
|
||||||
export let originalValue
|
|
||||||
|
|
||||||
let isSelect =
|
|
||||||
meta.type === "string" &&
|
|
||||||
meta.constraints &&
|
|
||||||
meta.constraints.inclusion &&
|
|
||||||
meta.constraints.inclusion.length > 0
|
|
||||||
|
|
||||||
let type = determineInputType(meta)
|
|
||||||
|
|
||||||
function determineInputType(meta) {
|
|
||||||
if (meta.type === "datetime") return "date"
|
|
||||||
if (meta.type === "number") return "number"
|
|
||||||
if (meta.type === "boolean") return "checkbox"
|
|
||||||
if (meta.type === "attachment") return "file"
|
|
||||||
if (isSelect) return "select"
|
|
||||||
|
|
||||||
return "text"
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInput = event => {
|
|
||||||
if (event.target.type === "checkbox") {
|
|
||||||
value = event.target.checked
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.target.type === "number") {
|
|
||||||
value = parseInt(event.target.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value = event.target.value
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if type === 'select'}
|
|
||||||
<Select thin secondary data-cy="{meta.name}-select" bind:value>
|
|
||||||
<option />
|
|
||||||
{#each meta.constraints.inclusion as opt}
|
|
||||||
<option value={opt}>{opt}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{:else if type === 'date'}
|
|
||||||
<Label small forAttr={'datepicker-label'}>{meta.name}</Label>
|
|
||||||
<DatePicker bind:value />
|
|
||||||
{:else if type === 'file'}
|
|
||||||
<Label small forAttr={'dropzone-label'}>{meta.name}</Label>
|
|
||||||
<Dropzone bind:files={value} />
|
|
||||||
{:else}
|
|
||||||
{#if type === 'checkbox'}
|
|
||||||
<label>{meta.name}</label>
|
|
||||||
{/if}
|
|
||||||
<Input
|
|
||||||
thin
|
|
||||||
placeholder={meta.name}
|
|
||||||
data-cy="{meta.name}-input"
|
|
||||||
checked={value}
|
|
||||||
{type}
|
|
||||||
{value}
|
|
||||||
on:change={handleInput} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
label {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
|
|
||||||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script>
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
TextButton as Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { FIELDS } from "constants/backend"
|
|
||||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
let fieldName
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
|
||||||
<Button text small on:click={dropdown.show}>
|
|
||||||
<Icon name="addcolumn" />
|
|
||||||
Create New Column
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Create Column</h5>
|
|
||||||
<CreateEditColumn onClosed={dropdown.hide} />
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h5 {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,103 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Heading,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { FIELDS } from "constants/backend"
|
|
||||||
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
|
||||||
import DeleteRecordModal from "../modals/DeleteRecord.svelte"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
export let row
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
let editing
|
|
||||||
|
|
||||||
function showEditor() {
|
|
||||||
editing = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideEditor() {
|
|
||||||
dropdown.hide()
|
|
||||||
editing = false
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteRow = () => {
|
|
||||||
open(
|
|
||||||
DeleteRecordModal,
|
|
||||||
{
|
|
||||||
onClosed: hideEditor,
|
|
||||||
record: row,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor} on:click={dropdown.show}>
|
|
||||||
<i class="ri-more-line" />
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
{#if editing}
|
|
||||||
<h5>Edit Row</h5>
|
|
||||||
<CreateEditRecord onClosed={hideEditor} record={row} />
|
|
||||||
{:else}
|
|
||||||
<ul>
|
|
||||||
<li data-cy="edit-row" on:click={showEditor}>
|
|
||||||
<Icon name="edit" />
|
|
||||||
<span>Edit</span>
|
|
||||||
</li>
|
|
||||||
<li data-cy="delete-row" on:click={deleteRow}>
|
|
||||||
<Icon name="delete" />
|
|
||||||
<span>Delete</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.ri-more-line:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: var(--spacing-s) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
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,71 +0,0 @@
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
TextButton,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Popover,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
const FORMATS = [
|
|
||||||
{
|
|
||||||
name: "CSV",
|
|
||||||
key: "csv",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "JSON",
|
|
||||||
key: "json",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export let view
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
let exportFormat
|
|
||||||
|
|
||||||
async function exportView() {
|
|
||||||
const response = await api.post(
|
|
||||||
`/api/views/export?format=${exportFormat}`,
|
|
||||||
view
|
|
||||||
)
|
|
||||||
const downloadInfo = await response.json()
|
|
||||||
window.location = downloadInfo.url
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
|
||||||
<TextButton text small on:click={dropdown.show}>
|
|
||||||
<Icon name="download" />
|
|
||||||
Export
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Export Format</h5>
|
|
||||||
<Select secondary thin bind:value={exportFormat}>
|
|
||||||
<option value={''}>Select an option</option>
|
|
||||||
{#each FORMATS as format}
|
|
||||||
<option value={format.key}>{format.name}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
<div class="button-group">
|
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
|
||||||
<Button primary on:click={exportView}>Export</Button>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h5 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,113 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
Button,
|
|
||||||
Label,
|
|
||||||
Heading,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Dropzone,
|
|
||||||
Spacer,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import TableDataImport from "./TableDataImport.svelte"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import analytics from "analytics"
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
let name
|
|
||||||
let dataImport
|
|
||||||
let loading
|
|
||||||
|
|
||||||
async function saveTable() {
|
|
||||||
loading = true
|
|
||||||
const model = await backendUiStore.actions.models.save({
|
|
||||||
name,
|
|
||||||
schema: dataImport.schema || {},
|
|
||||||
dataImport,
|
|
||||||
})
|
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
|
||||||
$goto(`./model/${model._id}`)
|
|
||||||
analytics.captureEvent("Table Created", { name })
|
|
||||||
name = ""
|
|
||||||
dropdown.hide()
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClosed = () => {
|
|
||||||
name = ""
|
|
||||||
dropdown.hide()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
|
||||||
<Button primary wide on:click={dropdown.show}>Create New Table</Button>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
<div class="container">
|
|
||||||
<h5>Create Table</h5>
|
|
||||||
<Label grey extraSmall>Name</Label>
|
|
||||||
<Input
|
|
||||||
data-cy="table-name-input"
|
|
||||||
placeholder="Table Name"
|
|
||||||
thin
|
|
||||||
bind:value={name} />
|
|
||||||
<Spacer medium />
|
|
||||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
|
||||||
<TableDataImport bind:dataImport />
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button
|
|
||||||
disabled={!name || (dataImport && !dataImport.valid)}
|
|
||||||
primary
|
|
||||||
on:click={saveTable}>
|
|
||||||
<span style={`margin-right: ${loading ? '10px' : 0};`}>Save</span>
|
|
||||||
{#if loading}
|
|
||||||
<Spinner size="10" />
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h5 {
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
margin-top: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: var(--spacing-l);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,137 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
|
||||||
import { FIELDS } from "constants/backend"
|
|
||||||
import DeleteTableModal from "components/database/DataTable/modals/DeleteTable.svelte"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
export let table
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
let editing
|
|
||||||
|
|
||||||
function showEditor() {
|
|
||||||
editing = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideEditor() {
|
|
||||||
dropdown.hide()
|
|
||||||
editing = false
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteTable = () => {
|
|
||||||
open(
|
|
||||||
DeleteTableModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
table,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
backendUiStore.actions.models.save(table)
|
|
||||||
hideEditor()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor} on:click={dropdown.show}>
|
|
||||||
<i class="ri-more-line" />
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
{#if editing}
|
|
||||||
<h5>Edit Table</h5>
|
|
||||||
<div class="container">
|
|
||||||
<Input placeholder="Table Name" thin bind:value={table.name} />
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={hideEditor}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button primary on:click={save}>Save</Button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
{:else}
|
|
||||||
<ul>
|
|
||||||
<li on:click={showEditor}>
|
|
||||||
<Icon name="edit" />
|
|
||||||
Edit
|
|
||||||
</li>
|
|
||||||
<li data-cy="delete-table" on:click={deleteTable}>
|
|
||||||
<Icon name="delete" />
|
|
||||||
Delete
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h5 {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-1 {
|
|
||||||
grid-column-start: 1;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<script>
|
|
||||||
import { isActive, url, goto } from "@sveltech/routify"
|
|
||||||
|
|
||||||
export let label = ""
|
|
||||||
export let href
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
on:click={() => $goto(href)}
|
|
||||||
class="budibase__nav-item backend-nav-item"
|
|
||||||
class:selected={$isActive(href)}>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.backend-nav-item {
|
|
||||||
padding-left: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,32 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "./Modal.svelte"
|
import SettingsModal from "./SettingsModal.svelte"
|
||||||
import { SettingsIcon } from "components/common/Icons/"
|
import { SettingsIcon } from "components/common/Icons/"
|
||||||
import { getContext } from "svelte"
|
import { Modal } from "@budibase/bbui"
|
||||||
import { isActive, goto, layout } from "@sveltech/routify"
|
|
||||||
|
|
||||||
// Handle create app modal
|
let modal
|
||||||
const { open } = getContext("simple-modal")
|
|
||||||
|
|
||||||
const showSettingsModal = () => {
|
|
||||||
open(
|
|
||||||
Modal,
|
|
||||||
{
|
|
||||||
name: "Placeholder App Name",
|
|
||||||
description: "This is a hardcoded description that needs to change",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
closeButton: false,
|
|
||||||
closeOnEsc: true,
|
|
||||||
styleContent: { padding: 0 },
|
|
||||||
closeOnOuterClick: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="topnavitemright settings" on:click={showSettingsModal}>
|
<span class="topnavitemright settings" on:click={modal.show}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</span>
|
</span>
|
||||||
|
<Modal bind:this={modal} width="600px">
|
||||||
|
<SettingsModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
span:first-letter {
|
span:first-letter {
|
||||||
|
@ -35,8 +20,7 @@
|
||||||
.topnavitemright {
|
.topnavitemright {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--grey-7);
|
color: var(--grey-7);
|
||||||
margin: 0px 20px 0px 0px;
|
margin: 0 20px 0 0;
|
||||||
padding-top: 4px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
<script>
|
|
||||||
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
|
||||||
|
|
||||||
import { Input, TextArea, Button, Switcher } from "@budibase/bbui"
|
|
||||||
import { SettingsIcon, CloseIcon } from "components/common/Icons/"
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { post } from "builderStore/api"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
export let name = ""
|
|
||||||
export let description = ""
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
title: "General",
|
|
||||||
key: "GENERAL",
|
|
||||||
component: General,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Users",
|
|
||||||
key: "USERS",
|
|
||||||
component: Users,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "API Keys",
|
|
||||||
key: "API_KEYS",
|
|
||||||
component: APIKeys,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Danger Zone",
|
|
||||||
key: "DANGERZONE",
|
|
||||||
component: DangerZone,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
let value = "GENERAL"
|
|
||||||
$: selectedTab = tabs.find(tab => tab.key === value).component
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="body">
|
|
||||||
<div class="heading">
|
|
||||||
<span class="icon">
|
|
||||||
<SettingsIcon />
|
|
||||||
</span>
|
|
||||||
<h3>Settings</h3>
|
|
||||||
</div>
|
|
||||||
<Switcher headings={tabs} bind:value>
|
|
||||||
<svelte:component this={selectedTab} />
|
|
||||||
</Switcher>
|
|
||||||
</div>
|
|
||||||
<div class="close-button" on:click={close}>
|
|
||||||
<CloseIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
height: 36rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
}
|
|
||||||
.close-button :global(svg) {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
display: grid;
|
|
||||||
border-radius: 3px;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-right: 12px;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--blue-light);
|
|
||||||
color: var(--grey-7);
|
|
||||||
}
|
|
||||||
.body {
|
|
||||||
padding: 40px 40px 40px 40px;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
||||||
|
import { Switcher, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: "General",
|
||||||
|
key: "GENERAL",
|
||||||
|
component: General,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Users",
|
||||||
|
key: "USERS",
|
||||||
|
component: Users,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "API Keys",
|
||||||
|
key: "API_KEYS",
|
||||||
|
component: APIKeys,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Danger Zone",
|
||||||
|
key: "DANGERZONE",
|
||||||
|
component: DangerZone,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let value = "GENERAL"
|
||||||
|
|
||||||
|
$: selectedTab = tabs.find(tab => tab.key === value).component
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title="Settings"
|
||||||
|
showConfirmButton={false}
|
||||||
|
showCancelButton={false}>
|
||||||
|
<div class="container">
|
||||||
|
<Switcher headings={tabs} bind:value>
|
||||||
|
<svelte:component this={selectedTab} />
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container :global(section > header) {
|
||||||
|
/* Fix margin defined in BBUI as L rather than XL */
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.container :global(textarea) {
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -14,7 +14,7 @@
|
||||||
bind:value={user.username}
|
bind:value={user.username}
|
||||||
name="Name"
|
name="Name"
|
||||||
placeholder="Username" />
|
placeholder="Username" />
|
||||||
<Select disabled={!editMode} bind:value={user.accessLevelId} thin>
|
<Select disabled={!editMode} bind:value={user.accessLevelId} thin secondary>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
|
@ -37,19 +37,7 @@
|
||||||
.inputs {
|
.inputs {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
grid-gap: 18px;
|
grid-gap: var(--spacing-m);
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 140px;
|
||||||
}
|
|
||||||
.inputs :global(input) {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
}
|
|
||||||
.inputs :global(select) {
|
|
||||||
padding: 9px 12px;
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
}
|
|
||||||
.inputs :global(button) {
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
height: initial;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Button } from "@budibase/bbui"
|
import { Input } from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import posthog from "posthog-js"
|
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
let keys = { budibase: "" }
|
let keys = { budibase: "" }
|
||||||
|
@ -34,23 +32,17 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="background">
|
<Input
|
||||||
<Input
|
on:save={e => updateKey(['budibase', e.detail])}
|
||||||
on:save={e => updateKey(['budibase', e.detail])}
|
thin
|
||||||
thin
|
edit
|
||||||
edit
|
value={keys.budibase}
|
||||||
value={keys.budibase}
|
label="Budibase API Key" />
|
||||||
label="Budibase" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--space);
|
grid-gap: var(--spacing-xl);
|
||||||
}
|
|
||||||
.background {
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 12px 0px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { params, goto } from "@sveltech/routify"
|
import { params, goto } from "@sveltech/routify"
|
||||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
import { Input, TextArea, Button, Body } from "@budibase/bbui"
|
||||||
import { del } from "builderStore/api"
|
import { del } from "builderStore/api"
|
||||||
|
|
||||||
let value = ""
|
let value = ""
|
||||||
|
@ -9,51 +9,50 @@
|
||||||
async function deleteApp() {
|
async function deleteApp() {
|
||||||
loading = true
|
loading = true
|
||||||
const id = $params.application
|
const id = $params.application
|
||||||
const res = await del(`/api/${id}`)
|
await del(`/api/${id}`)
|
||||||
const json = await res.json()
|
|
||||||
|
|
||||||
loading = false
|
loading = false
|
||||||
if (res.ok) {
|
$goto("/")
|
||||||
$goto("/")
|
|
||||||
return json
|
|
||||||
} else {
|
|
||||||
throw new Error(json)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="background">
|
<div class="background">
|
||||||
<p>
|
<Body>
|
||||||
Type DELETE into the textbox, then click the following button to delete your
|
Type
|
||||||
web app:
|
<b>DELETE</b>
|
||||||
</p>
|
into the textbox, then click the following button to delete your entire web
|
||||||
|
app.
|
||||||
|
</Body>
|
||||||
<Input
|
<Input
|
||||||
on:change={e => (value = e.target.value)}
|
on:change={e => (value = e.target.value)}
|
||||||
on:input={e => (value = e.target.value)}
|
on:input={e => (value = e.target.value)}
|
||||||
thin
|
thin
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="" />
|
placeholder="" />
|
||||||
|
<div class="buttons">
|
||||||
<Button
|
<Button
|
||||||
disabled={value !== 'DELETE' || loading}
|
primary
|
||||||
red
|
disabled={value !== 'DELETE' || loading}
|
||||||
wide
|
red
|
||||||
on:click={deleteApp}>
|
on:click={deleteApp}>
|
||||||
Delete Entire Web App
|
Delete Entire App
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.background {
|
.background {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 16px;
|
grid-gap: var(--spacing-xl);
|
||||||
border-radius: 5px;
|
|
||||||
padding: 12px 0px;
|
|
||||||
}
|
}
|
||||||
p {
|
.background :global(p) {
|
||||||
|
line-height: 1.2;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.background :global(button) {
|
|
||||||
max-width: 100%;
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -22,19 +22,18 @@
|
||||||
thin
|
thin
|
||||||
edit
|
edit
|
||||||
value={$store.name}
|
value={$store.name}
|
||||||
label="Name" />
|
label="App Name" />
|
||||||
<TextArea
|
<TextArea
|
||||||
on:save={e => updateApplication({ description: e.detail })}
|
on:save={e => updateApplication({ description: e.detail })}
|
||||||
thin
|
thin
|
||||||
edit
|
edit
|
||||||
value={$store.description}
|
value={$store.description}
|
||||||
label="Description" />
|
label="App Description" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 32px;
|
grid-gap: var(--spacing-xl);
|
||||||
margin-top: 32px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select, Button } from "@budibase/bbui"
|
import { Input, Select, Button, Label } from "@budibase/bbui"
|
||||||
import UserRow from "../UserRow.svelte"
|
import UserRow from "../UserRow.svelte"
|
||||||
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
let password = ""
|
let password = ""
|
||||||
let accessLevelId
|
let accessLevelId = "ADMIN"
|
||||||
|
|
||||||
$: valid = username && password && accessLevelId
|
$: valid = username && password && accessLevelId
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
|
@ -52,8 +52,8 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="background">
|
<div>
|
||||||
<div class="title">Create new user</div>
|
<Label extraSmall grey>Create New User</Label>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
||||||
<Input
|
<Input
|
||||||
|
@ -61,20 +61,18 @@
|
||||||
bind:value={password}
|
bind:value={password}
|
||||||
name="Password"
|
name="Password"
|
||||||
placeholder="Password" />
|
placeholder="Password" />
|
||||||
<Select bind:value={accessLevelId} thin>
|
<Select secondary bind:value={accessLevelId} thin>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
<Button on:click={createUser} primary>Create</Button>
|
||||||
<div class="create-button">
|
|
||||||
<Button on:click={createUser} small primary>Create</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="background-users">
|
<div>
|
||||||
<div class="title">Current Users</div>
|
<Label extraSmall grey>Current Users</Label>
|
||||||
{#await fetchUsersPromise}
|
{#await fetchUsersPromise}
|
||||||
Loading state!
|
Loading...
|
||||||
{:then users}
|
{:then users}
|
||||||
<ul>
|
<ul>
|
||||||
{#each users as user}
|
{#each users as user}
|
||||||
|
@ -95,57 +93,21 @@
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 32px;
|
grid-gap: var(--spacing-xl);
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 12px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-users {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 12px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
.create-button :global(button) {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
min-width: 100px;
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.inputs {
|
.inputs {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin-top: 12px;
|
justify-items: stretch;
|
||||||
grid-gap: 18px;
|
grid-gap: var(--spacing-m);
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr 140px;
|
||||||
}
|
|
||||||
.inputs :global(input) {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
}
|
|
||||||
.inputs :global(select) {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: var(--rounded-small);
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 8px;
|
grid-gap: var(--spacing-m);
|
||||||
margin-top: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,60 +1,35 @@
|
||||||
<script>
|
<script>
|
||||||
import Button from "components/common/Button.svelte"
|
import { TextButton } from "@budibase/bbui"
|
||||||
|
import { Heading } from "@budibase/bbui"
|
||||||
|
import { Spacer } from "@budibase/bbui"
|
||||||
export let name, _id
|
export let name, _id
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="apps-card">
|
<div class="apps-card">
|
||||||
<h3 class="app-title">{name}</h3>
|
<Heading small black>{name}</Heading>
|
||||||
|
<Spacer medium />
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<a href={`/_builder/${_id}`} class="app-button">Open {name}</a>
|
<TextButton text medium blue href="/_builder/{_id}">
|
||||||
|
Open {name} →
|
||||||
|
</TextButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.apps-card {
|
.apps-card {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-xl)
|
||||||
|
var(--spacing-xl);
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-m);
|
||||||
border: var(--border-dark);
|
border: var(--border-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button:hover {
|
|
||||||
background-color: var(--white);
|
|
||||||
color: var(--black);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-title {
|
|
||||||
font-size: var(--font-size-l);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ink);
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--ink);
|
|
||||||
color: var(--white);
|
|
||||||
border: 1.5px var(--ink) solid;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: var(--border-radius-s);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<Heading medium black>Your Apps</Heading>
|
<Heading lh medium black>Your Apps</Heading>
|
||||||
{#await promise}
|
{#await promise}
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<Spinner size="30" />
|
<Spinner size="30" />
|
||||||
|
@ -48,8 +48,4 @@
|
||||||
grid-gap: var(--layout-m);
|
grid-gap: var(--layout-m);
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
|
||||||
margin: 20px 80px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
import { store, automationStore, backendUiStore } from "builderStore"
|
import { store, automationStore, backendUiStore } from "builderStore"
|
||||||
import { string, object } from "yup"
|
import { string, object } from "yup"
|
||||||
import api, { get } from "builderStore/api"
|
import api, { get } from "builderStore/api"
|
||||||
|
@ -11,12 +10,10 @@
|
||||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
|
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { post } from "builderStore/api"
|
import { post } from "builderStore/api"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
|
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
|
||||||
const createAppStore = writable({ currentStep: 0, values: {} })
|
const createAppStore = writable({ currentStep: 0, values: {} })
|
||||||
|
|
||||||
|
@ -172,7 +169,7 @@
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users`, user)
|
const userResp = await api.post(`/api/users`, user)
|
||||||
const json = await userResp.json()
|
const json = await userResp.json()
|
||||||
$goto(`./${appJson._id}`)
|
$goto(`/${appJson._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
@ -197,10 +194,6 @@
|
||||||
|
|
||||||
let onChange = () => {}
|
let onChange = () => {}
|
||||||
|
|
||||||
function _onCancel() {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _onOkay() {
|
async function _onOkay() {
|
||||||
await createNewApp()
|
await createNewApp()
|
||||||
}
|
}
|
||||||
|
@ -253,9 +246,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-button" on:click={_onCancel}>
|
|
||||||
<CloseIcon />
|
|
||||||
</div>
|
|
||||||
<img src="/_builder/assets/bb-logo.svg" alt="budibase icon" />
|
<img src="/_builder/assets/bb-logo.svg" alt="budibase icon" />
|
||||||
{#if submitting}
|
{#if submitting}
|
||||||
<div in:fade class="spinner-container">
|
<div in:fade class="spinner-container">
|
||||||
|
@ -280,16 +270,6 @@
|
||||||
align-content: center;
|
align-content: center;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
.close-button {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
}
|
|
||||||
.close-button :global(svg) {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
.heading {
|
.heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Heading, Body } from "@budibase/bbui"
|
import { Label, Heading, Input } from "@budibase/bbui"
|
||||||
export let validationErrors
|
export let validationErrors
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
let blurred = { appName: false }
|
let blurred = { appName: false }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if template}
|
|
||||||
<Heading small black>Selected Template</Heading>
|
|
||||||
<Body>{template.name}</Body>
|
|
||||||
{/if}
|
|
||||||
<h2>Create your web app</h2>
|
<h2>Create your web app</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{#if template}
|
||||||
|
<div class="template">
|
||||||
|
<Label extraSmall grey>Selected Template</Label>
|
||||||
|
<Heading small>{template.name}</Heading>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.appName = true)}
|
on:input={() => (blurred.appName = true)}
|
||||||
label="Web app name"
|
label="Web App Name"
|
||||||
name="applicationName"
|
name="applicationName"
|
||||||
placeholder="Enter name of your web application"
|
placeholder="Enter name of your web application"
|
||||||
type="name"
|
type="name"
|
||||||
|
@ -24,6 +26,11 @@
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 40px;
|
grid-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template :global(label) {
|
||||||
|
/* Fix layout due to LH 0 on heading */
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
type="password"
|
||||||
error={blurred.password && validationErrors.password} />
|
error={blurred.password && validationErrors.password} />
|
||||||
<Select secondary name="accessLevelId">
|
<Select label="Access Level" secondary name="accessLevelId">
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -30,6 +30,6 @@
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 40px;
|
grid-gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Heading, Body } from "@budibase/bbui"
|
import { Button, Heading, Body, Spacer } from "@budibase/bbui"
|
||||||
import AppCard from "./AppCard.svelte"
|
import AppCard from "./AppCard.svelte"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<Heading medium black>Start With a Template</Heading>
|
<Heading lh medium black>Start With a Template</Heading>
|
||||||
{#await templatesPromise}
|
{#await templatesPromise}
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<Spinner size="30" />
|
<Spinner size="30" />
|
||||||
|
@ -24,11 +24,12 @@
|
||||||
<div class="templates">
|
<div class="templates">
|
||||||
{#each templates as template}
|
{#each templates as template}
|
||||||
<div class="templates-card">
|
<div class="templates-card">
|
||||||
<Heading black medium>{template.name}</Heading>
|
<Heading black small>{template.name}</Heading>
|
||||||
|
<Spacer small />
|
||||||
<Body medium grey>{template.category}</Body>
|
<Body medium grey>{template.category}</Body>
|
||||||
<Body small black>{template.description}</Body>
|
<Body lh small black>{template.description}</Body>
|
||||||
<div>
|
<div>
|
||||||
<img src={template.image} width="300" />
|
<img src={template.image} width="100%" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<Button secondary on:click={() => onSelect(template)}>
|
<Button secondary on:click={() => onSelect(template)}>
|
||||||
|
@ -68,8 +69,4 @@
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
|
||||||
margin: 20px 80px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -42,8 +42,8 @@
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
background: var(--grey-4);
|
background: var(--grey-4);
|
||||||
right: 7px;
|
right: var(--spacing-s);
|
||||||
bottom: 7px;
|
bottom: 9px;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover {
|
||||||
background: var(--grey-5);
|
background: var(--grey-5);
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import groupBy from "lodash/fp/groupBy"
|
import groupBy from "lodash/fp/groupBy"
|
||||||
import { Button, TextArea, Label, Body } from "@budibase/bbui"
|
import {
|
||||||
|
Button,
|
||||||
|
TextArea,
|
||||||
|
Label,
|
||||||
|
Body,
|
||||||
|
Heading,
|
||||||
|
Spacer,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -26,9 +33,10 @@
|
||||||
|
|
||||||
<div class="container" data-cy="binding-dropdown-modal">
|
<div class="container" data-cy="binding-dropdown-modal">
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<Label size="l" color="dark">Objects</Label>
|
<Heading extraSmall>Objects</Heading>
|
||||||
|
<Spacer medium />
|
||||||
{#if context}
|
{#if context}
|
||||||
<Label size="s" color="dark">Table</Label>
|
<Heading extraSmall>Tables</Heading>
|
||||||
<ul>
|
<ul>
|
||||||
{#each context as { readableBinding }}
|
{#each context as { readableBinding }}
|
||||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||||
|
@ -36,7 +44,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if instance}
|
{#if instance}
|
||||||
<Label size="s" color="dark">Components</Label>
|
<Heading extraSmall>Components</Heading>
|
||||||
<ul>
|
<ul>
|
||||||
{#each instance as { readableBinding }}
|
{#each instance as { readableBinding }}
|
||||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||||
|
@ -45,15 +53,21 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Label size="l" color="dark">Data binding</Label>
|
<Heading extraSmall>Data binding</Heading>
|
||||||
<Body size="s" color="dark">
|
<Spacer small />
|
||||||
|
<Body extraSmall lh>
|
||||||
Binding connects one piece of data to another and makes it dynamic. Click
|
Binding connects one piece of data to another and makes it dynamic. Click
|
||||||
the objects on the left, to add them to the textbox.
|
the objects on the left, to add them to the textbox.
|
||||||
</Body>
|
</Body>
|
||||||
<TextArea bind:value placeholder="" />
|
<Spacer large />
|
||||||
|
<TextArea
|
||||||
|
thin
|
||||||
|
bind:value
|
||||||
|
placeholder="Add text, or lick the objects on the left to add them to the
|
||||||
|
textbox." />
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a href="#">
|
<a href="https://docs.budibase.com/design/binding">
|
||||||
<Body size="s" color="light">Learn more about binding</Body>
|
<Body small grey>Learn more about binding</Body>
|
||||||
</a>
|
</a>
|
||||||
<Button on:click={cancel} secondary>Cancel</Button>
|
<Button on:click={cancel} secondary>Cancel</Button>
|
||||||
<Button on:click={close} primary>Done</Button>
|
<Button on:click={close} primary>Done</Button>
|
||||||
|
@ -73,17 +87,19 @@
|
||||||
.controls {
|
.controls {
|
||||||
margin-top: var(--spacing-m);
|
margin-top: var(--spacing-m);
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
grid-gap: var(--spacing-l);
|
grid-gap: var(--spacing-l);
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
}
|
}
|
||||||
.list {
|
.list {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
border-right: 1.5px solid var(--grey-4);
|
border-right: 1.5px solid var(--grey-4);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
display: grid;
|
padding: var(--spacing-xl);
|
||||||
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
.text :global(p) {
|
.text :global(p) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -99,15 +115,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
color: var(--ink);
|
color: var(--grey-7);
|
||||||
padding: var(--spacing-s) var(--spacing-m);
|
padding: var(--spacing-s) 0;
|
||||||
margin: auto 0px;
|
margin: auto 0px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:hover {
|
li:hover {
|
||||||
background-color: var(--grey-2);
|
color: var(--ink);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:active {
|
li:active {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { MoreIcon } from "components/common/Icons"
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
@ -99,9 +98,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor} on:click|stopPropagation={() => {}}>
|
<div bind:this={anchor} on:click|stopPropagation={() => {}}>
|
||||||
<button on:click={dropdown.show}>
|
<div class="icon" on:click={dropdown.show}>
|
||||||
<MoreIcon />
|
<i class="ri-more-line" />
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
class="menu"
|
class="menu"
|
||||||
|
@ -112,27 +111,27 @@
|
||||||
align="left">
|
align="left">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="item" on:click={() => confirmDeleteDialog.show()}>
|
<li class="item" on:click={() => confirmDeleteDialog.show()}>
|
||||||
<i class="icon ri-delete-bin-2-line" />
|
<i class="ri-delete-bin-2-line" />
|
||||||
Delete
|
Delete
|
||||||
</li>
|
</li>
|
||||||
<li class="item" on:click={moveUpComponent}>
|
<li class="item" on:click={moveUpComponent}>
|
||||||
<i class="icon ri-arrow-up-line" />
|
<i class="ri-arrow-up-line" />
|
||||||
Move up
|
Move up
|
||||||
</li>
|
</li>
|
||||||
<li class="item" on:click={moveDownComponent}>
|
<li class="item" on:click={moveDownComponent}>
|
||||||
<i class="icon ri-arrow-down-line" />
|
<i class="ri-arrow-down-line" />
|
||||||
Move down
|
Move down
|
||||||
</li>
|
</li>
|
||||||
<li class="item" on:click={copyComponent}>
|
<li class="item" on:click={copyComponent}>
|
||||||
<i class="icon ri-repeat-one-line" />
|
<i class="ri-repeat-one-line" />
|
||||||
Duplicate
|
Duplicate
|
||||||
</li>
|
</li>
|
||||||
<li class="item" on:click={() => storeComponentForCopy(true)}>
|
<li class="item" on:click={() => storeComponentForCopy(true)}>
|
||||||
<i class="icon ri-scissors-cut-line" />
|
<i class="ri-scissors-cut-line" />
|
||||||
Cut
|
Cut
|
||||||
</li>
|
</li>
|
||||||
<li class="item" on:click={() => storeComponentForCopy(false)}>
|
<li class="item" on:click={() => storeComponentForCopy(false)}>
|
||||||
<i class="icon ri-file-copy-line" />
|
<i class="ri-file-copy-line" />
|
||||||
Copy
|
Copy
|
||||||
</li>
|
</li>
|
||||||
<hr class="hr-style" />
|
<hr class="hr-style" />
|
||||||
|
@ -140,21 +139,21 @@
|
||||||
class="item"
|
class="item"
|
||||||
class:disabled={noPaste}
|
class:disabled={noPaste}
|
||||||
on:click={() => pasteComponent('above')}>
|
on:click={() => pasteComponent('above')}>
|
||||||
<i class="icon ri-insert-row-top" />
|
<i class="ri-insert-row-top" />
|
||||||
Paste above
|
Paste above
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="item"
|
class="item"
|
||||||
class:disabled={noPaste}
|
class:disabled={noPaste}
|
||||||
on:click={() => pasteComponent('below')}>
|
on:click={() => pasteComponent('below')}>
|
||||||
<i class="icon ri-insert-row-bottom" />
|
<i class="ri-insert-row-bottom" />
|
||||||
Paste below
|
Paste below
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="item"
|
class="item"
|
||||||
class:disabled={noPaste || noChildrenAllowed}
|
class:disabled={noPaste || noChildrenAllowed}
|
||||||
on:click={() => pasteComponent('inside')}>
|
on:click={() => pasteComponent('inside')}>
|
||||||
<i class="icon ri-insert-column-right" />
|
<i class="ri-insert-column-right" />
|
||||||
Paste inside
|
Paste inside
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -171,7 +170,7 @@
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: var(--spacing-s) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
@ -180,44 +179,29 @@
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
padding: var(--spacing-s) var(--spacing-m);
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
margin: auto 0px;
|
margin: auto 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
li:not(.disabled):hover {
|
||||||
button {
|
|
||||||
border-style: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--ink);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:hover {
|
|
||||||
background-color: var(--grey-2);
|
background-color: var(--grey-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
li:active {
|
li:active {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
li i {
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
}
|
}
|
||||||
|
li.disabled {
|
||||||
.disabled {
|
|
||||||
color: var(--grey-4);
|
color: var(--grey-4);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.hr-style {
|
.hr-style {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
color: var(--grey-4);
|
color: var(--grey-4);
|
||||||
|
|
|
@ -190,9 +190,9 @@
|
||||||
.item {
|
.item {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto auto auto;
|
grid-template-columns: 1fr auto auto auto;
|
||||||
padding: 0px 5px 0px 15px;
|
padding: 0 var(--spacing-m);
|
||||||
margin: auto 0px;
|
margin: 0;
|
||||||
border-radius: 5px;
|
border-radius: var(--border-radius-m);
|
||||||
height: 36px;
|
height: 36px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@ -205,9 +205,6 @@
|
||||||
.actions {
|
.actions {
|
||||||
display: none;
|
display: none;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
padding: 0 5px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-style: none;
|
border-style: none;
|
||||||
background: rgba(0, 0, 0, 0);
|
background: rgba(0, 0, 0, 0);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { TextButton, Button, Heading, DropdownMenu } from "@budibase/bbui"
|
import {
|
||||||
|
TextButton,
|
||||||
|
Button,
|
||||||
|
Body,
|
||||||
|
DropdownMenu,
|
||||||
|
ModalContent,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
||||||
import actionTypes from "./actions"
|
import actionTypes from "./actions"
|
||||||
|
@ -22,12 +28,6 @@
|
||||||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_MEMBER_NAME])
|
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_MEMBER_NAME])
|
||||||
.component
|
.component
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
dispatch("close")
|
|
||||||
draftEventHandler = { parameters: [] }
|
|
||||||
actions = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEventHandler = (updatedHandler, index) => {
|
const updateEventHandler = (updatedHandler, index) => {
|
||||||
actions[index] = updatedHandler
|
actions[index] = updatedHandler
|
||||||
}
|
}
|
||||||
|
@ -54,20 +54,17 @@
|
||||||
|
|
||||||
const saveEventData = () => {
|
const saveEventData = () => {
|
||||||
dispatch("change", actions)
|
dispatch("change", actions)
|
||||||
closeModal()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<ModalContent title="Actions" confirmText="Save" onConfirm={saveEventData}>
|
||||||
|
<div slot="header">
|
||||||
<div class="header">
|
|
||||||
<Heading small dark>Actions</Heading>
|
|
||||||
<div bind:this={addActionButton}>
|
<div bind:this={addActionButton}>
|
||||||
<TextButton text small blue on:click={addActionDropdown.show}>
|
<TextButton text small blue on:click={addActionDropdown.show}>
|
||||||
Add Action
|
|
||||||
<div style="height: 20px; width: 20px;">
|
<div style="height: 20px; width: 20px;">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</div>
|
</div>
|
||||||
|
Add Action
|
||||||
</TextButton>
|
</TextButton>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
|
@ -89,11 +86,7 @@
|
||||||
{#each actions as action, index}
|
{#each actions as action, index}
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<div class="action-header" on:click={selectAction(action)}>
|
<div class="action-header" on:click={selectAction(action)}>
|
||||||
<p
|
<Body small lh>{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]}</Body>
|
||||||
class="bb-body bb-body--small bb-body--color-dark"
|
|
||||||
style="margin: var(--spacing-s) 0;">
|
|
||||||
{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]}
|
|
||||||
</p>
|
|
||||||
<div class="row-expander" class:rotate={action !== selectedAction}>
|
<div class="row-expander" class:rotate={action !== selectedAction}>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,30 +108,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div slot="footer">
|
||||||
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
||||||
<Button secondary on:click={closeModal}>Cancel</Button>
|
|
||||||
<Button primary on:click={saveEventData}>Save</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
|
||||||
max-height: 50vh;
|
|
||||||
width: 700px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-header {
|
.action-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -166,8 +141,7 @@
|
||||||
|
|
||||||
.actions-container {
|
.actions-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0px;
|
min-height: 0;
|
||||||
padding-bottom: var(--spacing-s);
|
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
border: var(--border-light);
|
border: var(--border-light);
|
||||||
border-width: 0 0 1px 0;
|
border-width: 0 0 1px 0;
|
||||||
|
@ -177,10 +151,6 @@
|
||||||
.action-container {
|
.action-container {
|
||||||
border: var(--border-light);
|
border: var(--border-light);
|
||||||
border-width: 1px 0 0 0;
|
border-width: 1px 0 0 0;
|
||||||
padding-left: var(--spacing-xl);
|
|
||||||
padding-right: var(--spacing-xl);
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-action-container {
|
.selected-action-container {
|
||||||
|
@ -195,22 +165,13 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
a {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
padding-top: var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer > a {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: var(--grey-5);
|
color: var(--grey-5);
|
||||||
font-size: var(--font-size-s);
|
font-size: var(--font-size-s);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
a:hover {
|
||||||
.footer > a:hover {
|
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let name
|
export let name
|
||||||
|
|
||||||
let eventsModal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary small on:click={eventsModal.show}>Define Actions</Button>
|
<Button secondary small on:click={modal.show}>Define Actions</Button>
|
||||||
|
|
||||||
<Modal bind:this={eventsModal} maxWidth="100vw" hideCloseButton padding="0">
|
<Modal bind:this={modal} width="600px">
|
||||||
<EventEditorModal
|
<EventEditorModal event={value} eventType={name} on:change />
|
||||||
event={value}
|
|
||||||
eventType={name}
|
|
||||||
on:change
|
|
||||||
on:close={eventsModal.hide} />
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,29 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { keys, map, includes, filter } from "lodash/fp"
|
||||||
import {
|
|
||||||
keys,
|
|
||||||
map,
|
|
||||||
some,
|
|
||||||
includes,
|
|
||||||
cloneDeep,
|
|
||||||
isEqual,
|
|
||||||
sortBy,
|
|
||||||
filter,
|
|
||||||
difference,
|
|
||||||
} from "lodash/fp"
|
|
||||||
import { pipe } from "components/common/core"
|
|
||||||
import Checkbox from "components/common/Checkbox.svelte"
|
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
|
import { Modal } from "@budibase/bbui"
|
||||||
import { PencilIcon } from "components/common/Icons"
|
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
|
||||||
|
|
||||||
export const EVENT_TYPE = "event"
|
export const EVENT_TYPE = "event"
|
||||||
|
|
||||||
export let component
|
export let component
|
||||||
|
|
||||||
let events = []
|
let events = []
|
||||||
let selectedEvent = null
|
let selectedEvent = null
|
||||||
|
let modal
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
events = Object.keys(component)
|
events = Object.keys(component)
|
||||||
|
@ -35,28 +20,9 @@
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle create app modal
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
const openModal = event => {
|
const openModal = event => {
|
||||||
selectedEvent = event
|
selectedEvent = event
|
||||||
open(
|
modal.show()
|
||||||
EventEditorModal,
|
|
||||||
{
|
|
||||||
eventOptions: events,
|
|
||||||
event: selectedEvent,
|
|
||||||
onClose: () => {
|
|
||||||
close()
|
|
||||||
selectedEvent = null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
closeButton: false,
|
|
||||||
closeOnEsc: false,
|
|
||||||
styleContent: { padding: 0 },
|
|
||||||
closeOnOuterClick: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -81,6 +47,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal bind:this={modal} width="600px">
|
||||||
|
<EventEditorModal eventOptions={events} event={selectedEvent} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative:nth-child(2)) {
|
.root :global(> div:nth-child(2)) {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
grid-column-end: 6;
|
grid-column-end: 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative) {
|
.root :global(> div) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: var(--spacing-l);
|
margin-left: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative:nth-child(2)) {
|
.root :global(> div:nth-child(2)) {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
grid-column-end: 6;
|
grid-column-end: 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,25 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
||||||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
import PageLayout from "components/userInterface/PageLayout.svelte"
|
||||||
import PagesList from "components/userInterface/PagesList.svelte"
|
import PagesList from "components/userInterface/PagesList.svelte"
|
||||||
import NewScreen from "components/userInterface/CreateScreen/NewScreen.svelte"
|
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
||||||
|
import { Button, Spacer, Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
const newScreen = () => {
|
let modal
|
||||||
newScreenPicker.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
let newScreenPicker
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PagesList />
|
<PagesList />
|
||||||
|
|
||||||
<button class="newscreen" on:click={newScreen}>Create New Screen</button>
|
<Spacer medium />
|
||||||
|
<Button primary wide on:click={modal.show}>Create New Screen</Button>
|
||||||
|
<Spacer medium />
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||||
|
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
<ComponentsHierarchy screens={$store.screens} />
|
<ComponentsHierarchy screens={$store.screens} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NewScreen bind:this={newScreenPicker} />
|
<Modal bind:this={modal}>
|
||||||
|
<NewScreenModal />
|
||||||
<style>
|
</Modal>
|
||||||
.newscreen {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--purple);
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin: 20px 0px 12px 0px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--purple);
|
|
||||||
color: var(--white);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 3ms;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newscreen:hover {
|
|
||||||
background: var(--purple-light);
|
|
||||||
color: var(--purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import groupBy from "lodash/fp/groupBy"
|
import groupBy from "lodash/fp/groupBy"
|
||||||
import { TextArea, Label, Body, Button, Popover } from "@budibase/bbui"
|
import {
|
||||||
|
TextArea,
|
||||||
|
Label,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
Spacer,
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -20,11 +28,12 @@
|
||||||
<Popover {anchor} {align} bind:this={popover}>
|
<Popover {anchor} {align} bind:this={popover}>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="bindings">
|
<div class="bindings">
|
||||||
<Label large>Available bindings</Label>
|
<Heading small>Available bindings</Heading>
|
||||||
<div class="bindings__wrapper">
|
<div class="bindings__wrapper">
|
||||||
<div class="bindings__list">
|
<div class="bindings__list">
|
||||||
{#each categories as [categoryName, bindings]}
|
{#each categories as [categoryName, bindings]}
|
||||||
<Label small>{categoryName}</Label>
|
<Heading extraSmall>{categoryName}</Heading>
|
||||||
|
<Spacer extraSmall />
|
||||||
{#each bindings as binding}
|
{#each bindings as binding}
|
||||||
<div class="binding" on:click={() => onClickBinding(binding)}>
|
<div class="binding" on:click={() => onClickBinding(binding)}>
|
||||||
<span class="binding__label">{binding.label}</span>
|
<span class="binding__label">{binding.label}</span>
|
||||||
|
@ -38,14 +47,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor">
|
<div class="editor">
|
||||||
<Label large>Data binding</Label>
|
<Heading small>Data binding</Heading>
|
||||||
<Body small>
|
<Body small lh black>
|
||||||
Binding connects one piece of data to another and makes it dynamic.
|
Binding connects one piece of data to another and makes it dynamic.
|
||||||
Click the objects on the left to add them to the textbox.
|
Click the objects on the left to add them to the textbox.
|
||||||
</Body>
|
</Body>
|
||||||
<TextArea thin bind:value placeholder="..." />
|
<TextArea
|
||||||
|
thin
|
||||||
|
bind:value
|
||||||
|
placeholder="Add options from the left, type text, or do both" />
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a href="#">
|
<a href="https://docs.budibase.com/design/binding">
|
||||||
<Body small>Learn more about binding</Body>
|
<Body small>Learn more about binding</Body>
|
||||||
</a>
|
</a>
|
||||||
<Button on:click={popover.hide} primary>Done</Button>
|
<Button on:click={popover.hide} primary>Done</Button>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon, DropdownMenu } from "@budibase/bbui"
|
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let anchor, dropdown
|
let anchorRight, dropdownRight
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
|
|
||||||
function handleSelected(selected) {
|
function handleSelected(selected) {
|
||||||
dispatch("change", selected)
|
dispatch("change", selected)
|
||||||
dropdown.hide()
|
dropdownRight.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = $backendUiStore.models.map(m => ({
|
const models = $backendUiStore.models.map(m => ({
|
||||||
|
@ -30,21 +30,17 @@
|
||||||
}, [])
|
}, [])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div class="dropdownbutton" bind:this={anchorRight}>
|
||||||
<Button secondary small on:click={dropdown.show}>
|
<Button secondary wide on:click={dropdownRight.show}>
|
||||||
<span>{value.label ? value.label : 'Model / View'}</span>
|
<span>{value.label ? value.label : 'Model / View'}</span>
|
||||||
<Icon name="arrowdown" />
|
<Icon name="arrowdown" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
bind:this={dropdown}
|
<div class="dropdown">
|
||||||
width="175px"
|
<div class="title">
|
||||||
borderColor="#d1d1d1ff"
|
<Heading extraSmall>Tables</Heading>
|
||||||
{anchor}
|
</div>
|
||||||
align="right">
|
|
||||||
<div class="model-view-container">
|
|
||||||
<p>Tables</p>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each models as model}
|
{#each models as model}
|
||||||
<li
|
<li
|
||||||
|
@ -55,7 +51,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<hr />
|
<hr />
|
||||||
<p>Views</p>
|
<div class="title">
|
||||||
|
<Heading extraSmall>Views</Heading>
|
||||||
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each views as view}
|
{#each views as view}
|
||||||
<li
|
<li
|
||||||
|
@ -69,23 +67,19 @@
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.model-view-container {
|
.dropdownbutton {
|
||||||
padding-bottom: 8px;
|
width: 100%;
|
||||||
font: var(--smallheavybodytext);
|
|
||||||
}
|
}
|
||||||
|
.dropdown {
|
||||||
p {
|
padding: var(--spacing-m) 0;
|
||||||
color: var(--grey-7);
|
z-index: 99999999;
|
||||||
margin: 0px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
.title {
|
||||||
span {
|
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 10px 0px 5px 0px;
|
margin: var(--spacing-m) 0 var(--spacing-xl) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
@ -97,7 +91,8 @@
|
||||||
li {
|
li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 5px 8px;
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { Input, Button, Spacer, Select, Modal } from "@budibase/bbui"
|
import { Input, Button, Spacer, Select, ModalContent } from "@budibase/bbui"
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { some } from "lodash/fp"
|
import { some } from "lodash/fp"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const CONTAINER = "@budibase/standard-components/container"
|
const CONTAINER = "@budibase/standard-components/container"
|
||||||
|
|
||||||
export const show = () => {
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
let screens
|
|
||||||
let name = ""
|
let name = ""
|
||||||
let routeError
|
let routeError
|
||||||
let baseComponent = CONTAINER
|
let baseComponent = CONTAINER
|
||||||
let dialog
|
|
||||||
let templateIndex = 0
|
let templateIndex = 0
|
||||||
let draftScreen
|
let draftScreen
|
||||||
|
|
||||||
|
@ -73,7 +65,6 @@
|
||||||
name = ""
|
name = ""
|
||||||
route = ""
|
route = ""
|
||||||
baseComponent = CONTAINER
|
baseComponent = CONTAINER
|
||||||
dialog.hide()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeNameExists = route => {
|
||||||
|
@ -99,64 +90,32 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={dialog} minWidth="500px">
|
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||||
|
|
||||||
<h2>New Screen</h2>
|
<Select
|
||||||
<Spacer extraLarge />
|
label="Choose a Template"
|
||||||
|
bind:value={templateIndex}
|
||||||
|
secondary
|
||||||
|
on:change={templateChanged}>
|
||||||
|
{#if templates}
|
||||||
|
{#each templates as template, index}
|
||||||
|
<option value={index}>{template.name}</option>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Select>
|
||||||
|
|
||||||
<div class="bb-margin-xl">
|
<Input label="Name" bind:value={name} />
|
||||||
<label>Choose a Template</label>
|
|
||||||
<Select bind:value={templateIndex} secondary on:change={templateChanged}>
|
|
||||||
{#if templates}
|
|
||||||
{#each templates as template, index}
|
|
||||||
<option value={index}>{template.name}</option>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div data-cy="new-screen-dialog">
|
|
||||||
<div class="bb-margin-xl">
|
|
||||||
<Input label="Name" bind:value={name} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bb-margin-xl">
|
<Input
|
||||||
<Input
|
label="Url"
|
||||||
label="Url"
|
error={routeError}
|
||||||
error={routeError}
|
bind:value={route}
|
||||||
bind:value={route}
|
on:change={routeChanged} />
|
||||||
on:change={routeChanged} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bb-margin-xl">
|
<Select label="Screen Type" bind:value={baseComponent} secondary>
|
||||||
<label>Screen Type</label>
|
{#each baseComponents as component}
|
||||||
<Select bind:value={baseComponent} secondary>
|
<option value={component}>{componentShortName(component)}</option>
|
||||||
{#each baseComponents as component}
|
{/each}
|
||||||
<option value={component}>{componentShortName(component)}</option>
|
</Select>
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</ModalContent>
|
||||||
|
|
||||||
<Spacer extraLarge />
|
|
||||||
|
|
||||||
<div data-cy="create-screen-footer" class="modal-footer">
|
|
||||||
<Button secondary medium on:click={finished}>Cancel</Button>
|
|
||||||
<Button blue medium on:click={save}>Create Screen</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -10,7 +10,7 @@
|
||||||
} from "builderStore/replaceBindings"
|
} from "builderStore/replaceBindings"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
import BindingDropdown from "components/userInterface/BindingDropdown.svelte"
|
import BindingDropdown from "components/userInterface/BindingDropdown.svelte"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
width: 260px;
|
min-width: 260px;
|
||||||
margin: 8px 0px;
|
margin: 8px 0px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@ -143,13 +143,15 @@
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
height: 90%;
|
||||||
height: 24px;
|
width: 2rem;
|
||||||
width: 24px;
|
background: var(--grey-2);
|
||||||
background: rgb(224, 224, 224);
|
right: 10px;
|
||||||
right: 5px;
|
|
||||||
--spacing-s: 0;
|
--spacing-s: 0;
|
||||||
|
border-left: 0.5px solid var(--grey-3);
|
||||||
|
outline-color: var(--blue);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue