merge master
This commit is contained in:
commit
300f18927f
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.1.17",
|
"version": "0.1.19",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -28,9 +28,5 @@
|
||||||
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
|
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
|
||||||
"test:e2e": "lerna run cy:test",
|
"test:e2e": "lerna run cy:test",
|
||||||
"test:e2e:ci": "lerna run cy:ci"
|
"test:e2e:ci": "lerna run cy:ci"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@material/icon-button": "4.0.0",
|
|
||||||
"date-fns": "^2.10.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,10 @@ context('Create a Table', () => {
|
||||||
it('edits a record', () => {
|
it('edits a record', () => {
|
||||||
cy.get("tbody .ri-more-line").click()
|
cy.get("tbody .ri-more-line").click()
|
||||||
cy.get("[data-cy=edit-row]").click()
|
cy.get("[data-cy=edit-row]").click()
|
||||||
cy.get(".actions input").type("updatedRecord")
|
cy.get(".actions input").type("Updated")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
|
|
||||||
cy.contains('updatedRecord').should('have.text', 'updatedRecord')
|
cy.contains('RoverUpdated').should('have.text', 'RoverUpdated')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes a record', () => {
|
it('deletes a record', () => {
|
||||||
|
@ -49,7 +49,7 @@ context('Create a Table', () => {
|
||||||
cy.get("[data-cy=delete-row]").click()
|
cy.get("[data-cy=delete-row]").click()
|
||||||
cy.get(".modal-actions").contains("Delete").click()
|
cy.get(".modal-actions").contains("Delete").click()
|
||||||
|
|
||||||
cy.contains('updatedRecord').should('not.exist')
|
cy.contains('RoverUpdated').should('not.exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes a column', () => {
|
it('deletes a column', () => {
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
context('Create a View', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
cy.createApp('View App', 'View App Description')
|
||||||
|
cy.createTable('data')
|
||||||
|
cy.addColumn('data', 'group', 'Plain Text')
|
||||||
|
cy.addColumn('data', 'age', 'Number')
|
||||||
|
cy.addColumn('data', 'rating', 'Number')
|
||||||
|
|
||||||
|
cy.addRecord(["Students", 25, 1])
|
||||||
|
cy.addRecord(["Students", 20, 3])
|
||||||
|
cy.addRecord(["Students", 18, 6])
|
||||||
|
cy.addRecord(["Students", 25, 2])
|
||||||
|
cy.addRecord(["Teachers", 49, 5])
|
||||||
|
cy.addRecord(["Teachers", 36, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('creates a stats view based on age', () => {
|
||||||
|
cy.contains("Create New View").click()
|
||||||
|
cy.get("[placeholder='View Name']").type("Test View")
|
||||||
|
cy.contains("Save View").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([
|
||||||
|
"group",
|
||||||
|
"sum",
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
"sumsqr",
|
||||||
|
"count",
|
||||||
|
"avg",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
cy.get("tbody td").should(($values) => {
|
||||||
|
const values = $values.map((i, value) => Cypress.$(value).text())
|
||||||
|
expect(values.get()).to.deep.eq([
|
||||||
|
"null",
|
||||||
|
"173",
|
||||||
|
"18",
|
||||||
|
"49",
|
||||||
|
"5671",
|
||||||
|
"6",
|
||||||
|
"28.833333333333332"
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('groups the stats 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())
|
||||||
|
expect(values.get()).to.deep.eq([
|
||||||
|
"Students",
|
||||||
|
"88",
|
||||||
|
"18",
|
||||||
|
"25",
|
||||||
|
"1974",
|
||||||
|
"4",
|
||||||
|
"22"
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renames a view', () => {
|
||||||
|
cy.contains("[data-cy=model-nav-item]", "Test View").find(".ri-more-line").click()
|
||||||
|
cy.contains("Edit").click()
|
||||||
|
cy.get("[placeholder='View Name']").type(" Updated")
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.contains("Test View Updated").should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deletes a view', () => {
|
||||||
|
cy.contains("[data-cy=model-nav-item]", "Test View Updated").find(".ri-more-line").click()
|
||||||
|
cy.contains("Delete").click()
|
||||||
|
cy.get(".content").contains("button", "Delete").click()
|
||||||
|
cy.contains("TestView Updated").should("not.be.visible")
|
||||||
|
})
|
||||||
|
})
|
|
@ -131,9 +131,9 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
|
|
||||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||||
cy.get(".newscreen").click()
|
cy.get(".newscreen").click()
|
||||||
cy.get(".uk-input:first").type(screenName)
|
cy.get("[data-cy=new-screen-dialog] input:first").type(screenName)
|
||||||
if (route) {
|
if (route) {
|
||||||
cy.get(".uk-input:last").type(route)
|
cy.get("[data-cy=new-screen-dialog] input:last").type(route)
|
||||||
}
|
}
|
||||||
cy.get(".uk-modal-footer").within(() => {
|
cy.get(".uk-modal-footer").within(() => {
|
||||||
cy.contains("Create Screen").click()
|
cy.contains("Create Screen").click()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.1.17",
|
"version": "0.1.19",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -58,26 +58,20 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.27.0",
|
"@budibase/bbui": "^1.27.0",
|
||||||
"@budibase/client": "^0.1.17",
|
"@budibase/client": "^0.1.19",
|
||||||
"@budibase/colorpicker": "^1.0.1",
|
"@budibase/colorpicker": "^1.0.1",
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@svelteschool/svelte-forms": "^0.7.0",
|
"@svelteschool/svelte-forms": "^0.7.0",
|
||||||
"britecharts": "^2.16.0",
|
"britecharts": "^2.16.0",
|
||||||
"codemirror": "^5.51.0",
|
|
||||||
"d3-selection": "^1.4.1",
|
"d3-selection": "^1.4.1",
|
||||||
"date-fns": "^1.29.0",
|
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"fast-sort": "^2.2.0",
|
"fast-sort": "^2.2.0",
|
||||||
"feather-icons": "^4.21.0",
|
"feather-icons": "^4.21.0",
|
||||||
"flatpickr": "^4.5.7",
|
"flatpickr": "^4.5.7",
|
||||||
"lodash": "^4.17.13",
|
"lodash": "^4.17.13",
|
||||||
"lunr": "^2.3.5",
|
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
"posthog-js": "1.3.1",
|
"posthog-js": "1.3.1",
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"string_decoder": "^1.2.0",
|
|
||||||
"svelte-portal": "^0.1.0",
|
"svelte-portal": "^0.1.0",
|
||||||
"svelte-simple-modal": "^0.4.2",
|
"svelte-simple-modal": "^0.4.2",
|
||||||
"uikit": "^3.1.7",
|
"uikit": "^3.1.7",
|
||||||
|
@ -93,20 +87,16 @@
|
||||||
"@sveltech/routify": "1.7.11",
|
"@sveltech/routify": "1.7.11",
|
||||||
"@testing-library/jest-dom": "^5.11.0",
|
"@testing-library/jest-dom": "^5.11.0",
|
||||||
"@testing-library/svelte": "^3.0.0",
|
"@testing-library/svelte": "^3.0.0",
|
||||||
"babel-jest": "^26.2.2",
|
"babel-jest": "^24.8.0",
|
||||||
"browser-sync": "^2.26.7",
|
|
||||||
"cypress": "^4.8.0",
|
"cypress": "^4.8.0",
|
||||||
"cypress-terminal-report": "^1.4.1",
|
"cypress-terminal-report": "^1.4.1",
|
||||||
"eslint-plugin-cypress": "^2.11.1",
|
"eslint-plugin-cypress": "^2.11.1",
|
||||||
"http-proxy-middleware": "^0.19.1",
|
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^26.2.2",
|
"jest": "^24.8.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^1.12.0",
|
"rollup": "^1.12.0",
|
||||||
"rollup-plugin-alias": "^1.5.2",
|
"rollup-plugin-alias": "^1.5.2",
|
||||||
"rollup-plugin-browsersync": "^1.0.0",
|
|
||||||
"rollup-plugin-commonjs": "^10.0.0",
|
"rollup-plugin-commonjs": "^10.0.0",
|
||||||
"rollup-plugin-copy": "^3.0.0",
|
"rollup-plugin-copy": "^3.0.0",
|
||||||
"rollup-plugin-livereload": "^1.0.0",
|
"rollup-plugin-livereload": "^1.0.0",
|
||||||
|
|
|
@ -23,13 +23,10 @@ export const getBackendUiStore = () => {
|
||||||
database: {
|
database: {
|
||||||
select: async db => {
|
select: async db => {
|
||||||
const modelsResponse = await api.get(`/api/models`)
|
const modelsResponse = await api.get(`/api/models`)
|
||||||
const viewsResponse = await api.get(`/api/views`)
|
|
||||||
const models = await modelsResponse.json()
|
const models = await modelsResponse.json()
|
||||||
const views = await viewsResponse.json()
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedDatabase = db
|
state.selectedDatabase = db
|
||||||
state.models = models
|
state.models = models
|
||||||
state.views = views
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -59,8 +56,7 @@ export const getBackendUiStore = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedModel = model
|
state.selectedModel = model
|
||||||
state.draftModel = cloneDeep(model)
|
state.draftModel = cloneDeep(model)
|
||||||
state.selectedField = ""
|
state.selectedView = { name: `all_${model._id}` }
|
||||||
state.selectedView = `all_${model._id}`
|
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
save: async model => {
|
save: async model => {
|
||||||
|
@ -97,12 +93,16 @@ export const getBackendUiStore = () => {
|
||||||
saveField: ({ originalName, field }) => {
|
saveField: ({ originalName, field }) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
// delete the original if renaming
|
// delete the original if renaming
|
||||||
|
if (originalName) {
|
||||||
delete state.draftModel.schema[originalName]
|
delete state.draftModel.schema[originalName]
|
||||||
|
state.draftModel._rename = {
|
||||||
state.draftModel.schema = {
|
old: originalName,
|
||||||
...state.draftModel.schema,
|
updated: field.name,
|
||||||
[field.name]: cloneDeep(field),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.draftModel.schema[field.name] = cloneDeep(field)
|
||||||
|
|
||||||
store.actions.models.save(state.draftModel)
|
store.actions.models.save(state.draftModel)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
@ -119,8 +119,30 @@ export const getBackendUiStore = () => {
|
||||||
select: view =>
|
select: view =>
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedView = view
|
state.selectedView = view
|
||||||
|
state.selectedModel = {}
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
|
delete: async view => {
|
||||||
|
await api.delete(`/api/views/${view}`)
|
||||||
|
await store.actions.models.fetch()
|
||||||
|
},
|
||||||
|
save: async view => {
|
||||||
|
await api.post(`/api/views`, view)
|
||||||
|
|
||||||
|
store.update(state => {
|
||||||
|
const viewModel = state.models.find(
|
||||||
|
model => model._id === view.modelId
|
||||||
|
)
|
||||||
|
// TODO: Cleaner?
|
||||||
|
if (!viewModel.views) viewModel.views = {}
|
||||||
|
if (view.originalName) delete viewModel.views[view.originalName]
|
||||||
|
viewModel.views[view.name] = view
|
||||||
|
|
||||||
|
state.models = state.models
|
||||||
|
state.selectedView = view
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
create: user =>
|
create: user =>
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
<script>
|
|
||||||
export let title
|
|
||||||
export let icon
|
|
||||||
|
|
||||||
export let primary
|
|
||||||
export let secondary
|
|
||||||
export let tertiary
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div on:click class:primary class:secondary class:tertiary>
|
|
||||||
<i class={icon} />
|
|
||||||
<span>{title}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: var(--ink);
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 15px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
transition: 0.3s transform;
|
|
||||||
background: var(--grey-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 24px;
|
|
||||||
color: var(--grey-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 8px;
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--grey-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary {
|
|
||||||
background: var(--ink);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary {
|
|
||||||
background: var(--blue-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tertiary {
|
|
||||||
background: var(--white);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<script>
|
|
||||||
import { JavaScriptIcon } from "../common/Icons"
|
|
||||||
// todo: use https://ace.c9.io
|
|
||||||
export let text = ""
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<textarea class="uk-textarea" bind:value={text} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
textarea {
|
|
||||||
padding: 10px;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background: var(--grey-7);
|
|
||||||
color: var(--white);
|
|
||||||
font-family: "Courier New", Courier, monospace;
|
|
||||||
height: 200px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script>
|
|
||||||
export let name = ""
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4>Coming Sometime: {name}</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h4 {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -29,8 +29,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const ok = () => {
|
const ok = () => {
|
||||||
|
const result = onOk()
|
||||||
|
// allow caller to return false, to cancel the "ok"
|
||||||
|
if (result === false) return
|
||||||
hide()
|
hide()
|
||||||
onOk()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="bb-margin-m">
|
||||||
<label class="uk-form-label">{label}</label>
|
<label class="uk-form-label">{label}</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
<script>
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import Select from "../common/Select.svelte"
|
|
||||||
|
|
||||||
export let selected
|
|
||||||
export let label
|
|
||||||
export let options
|
|
||||||
export let valueMember
|
|
||||||
export let textMember
|
|
||||||
export let multiple = false
|
|
||||||
export let width = "medium"
|
|
||||||
export let size = "small"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="uk-margin">
|
|
||||||
<label class="uk-form-label">{label}</label>
|
|
||||||
<div class="uk-form-controls">
|
|
||||||
{#if multiple}
|
|
||||||
<Select
|
|
||||||
class="uk-select uk-form-width-{width} uk-form-{size}"
|
|
||||||
multiple
|
|
||||||
bind:value={selected}
|
|
||||||
on:change>
|
|
||||||
{#each options as option}
|
|
||||||
<option value={!valueMember ? option : valueMember(option)}>
|
|
||||||
{!textMember ? option : textMember(option)}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{:else}
|
|
||||||
<Select
|
|
||||||
class="uk-select uk-form-width-{width} uk-form-{size}"
|
|
||||||
bind:value={selected}
|
|
||||||
on:change>
|
|
||||||
{#each options as option}
|
|
||||||
<option value={!valueMember ? option : valueMember(option)}>
|
|
||||||
{!textMember ? option : textMember(option)}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { buildStyle } from "../../helpers.js"
|
|
||||||
export let value = ""
|
|
||||||
export let name = ""
|
|
||||||
export let textAlign = "left"
|
|
||||||
export let width = "160px"
|
|
||||||
export let placeholder = ""
|
|
||||||
export let suffix = ""
|
|
||||||
export let onChange = val => {}
|
|
||||||
|
|
||||||
let centerPlaceholder = textAlign === "center"
|
|
||||||
|
|
||||||
let style = buildStyle({ width, textAlign })
|
|
||||||
|
|
||||||
function handleChange(val) {
|
|
||||||
value = val
|
|
||||||
let _value = value !== "auto" ? value + suffix : value
|
|
||||||
onChange(_value)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: displayValue =
|
|
||||||
suffix && value && value.endsWith(suffix)
|
|
||||||
? value.replace(new RegExp(`${suffix}$`), "")
|
|
||||||
: value || ""
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<input
|
|
||||||
{name}
|
|
||||||
class:centerPlaceholder
|
|
||||||
type="text"
|
|
||||||
value={displayValue}
|
|
||||||
{placeholder}
|
|
||||||
{style}
|
|
||||||
on:change={e => handleChange(e.target.value)} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
input {
|
|
||||||
/* width: 32px; */
|
|
||||||
height: 36px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0px 0px 0px 2px;
|
|
||||||
color: var(--ink);
|
|
||||||
padding: 0px 8px;
|
|
||||||
font-family: inter;
|
|
||||||
width: 164px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--grey-2);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerPlaceholder::placeholder {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import Input from "../Input.svelte"
|
|
||||||
|
|
||||||
export let meta = []
|
|
||||||
export let label = ""
|
|
||||||
export let value = ["0", "0", "0", "0"]
|
|
||||||
export let suffix = ""
|
|
||||||
|
|
||||||
export let onChange = () => {}
|
|
||||||
|
|
||||||
function handleChange(val, idx) {
|
|
||||||
value.splice(idx, 1, val !== "auto" && suffix ? val + suffix : val)
|
|
||||||
|
|
||||||
value = value
|
|
||||||
let _value = value.map(v =>
|
|
||||||
suffix && !v.endsWith(suffix) && v !== "auto" ? v + suffix : v
|
|
||||||
)
|
|
||||||
onChange(_value)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: displayValues =
|
|
||||||
value && suffix
|
|
||||||
? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
|
|
||||||
: value || []
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="input-container">
|
|
||||||
<div class="label">{label}</div>
|
|
||||||
<div class="inputs-group">
|
|
||||||
{#each meta as m, i}
|
|
||||||
<Input
|
|
||||||
width="37px"
|
|
||||||
textAlign="center"
|
|
||||||
placeholder={m.placeholder || ''}
|
|
||||||
value={!displayValues || displayValues[i] === '0' ? '' : displayValues[i]}
|
|
||||||
onChange={value => handleChange(value || 0, i)} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.label {
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-group {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,23 +0,0 @@
|
||||||
<button on:click>
|
|
||||||
<slot>+</slot>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
min-width: 1.8rem;
|
|
||||||
min-height: 1.8rem;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<script>
|
|
||||||
import getIcon from "./icon"
|
|
||||||
|
|
||||||
export let icon
|
|
||||||
export let value
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="select-container">
|
|
||||||
{#if icon}
|
|
||||||
<i class={icon} />
|
|
||||||
{/if}
|
|
||||||
<select class:adjusted={icon} on:change bind:value>
|
|
||||||
<slot />
|
|
||||||
</select>
|
|
||||||
<span class="arrow">
|
|
||||||
{@html getIcon('chevron-down', '24')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.select-container {
|
|
||||||
font-size: 14px;
|
|
||||||
position: relative;
|
|
||||||
border: var(--grey-4) 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adjusted {
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
height: 40px;
|
|
||||||
display: block;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ink);
|
|
||||||
padding: 0 40px 0px 20px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
-moz-appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
pointer-events: none;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script>
|
|
||||||
export let text = ""
|
|
||||||
export let label = ""
|
|
||||||
export let width = "medium"
|
|
||||||
export let size = "small"
|
|
||||||
export let margin = true
|
|
||||||
export let infoText = ""
|
|
||||||
export let hasError = false
|
|
||||||
export let disabled = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class:uk-margin={margin}>
|
|
||||||
<label class="uk-form-label">{label}</label>
|
|
||||||
<div class="uk-form-controls">
|
|
||||||
<input
|
|
||||||
data-cy={label}
|
|
||||||
class="budibase__input"
|
|
||||||
class:uk-form-danger={hasError}
|
|
||||||
on:change
|
|
||||||
bind:value={text}
|
|
||||||
{disabled} />
|
|
||||||
</div>
|
|
||||||
{#if infoText}
|
|
||||||
<div class="info-text">{infoText}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.info-text {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: var(--secondary50);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script>
|
|
||||||
import RecordCard from "./RecordCard.svelte"
|
|
||||||
|
|
||||||
export let record = {}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<div class="title">{record.name}</div>
|
|
||||||
<div class="inner">
|
|
||||||
<div class="node-path">{record.nodeKey()}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,15 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import fsort from "fast-sort"
|
import fsort from "fast-sort"
|
||||||
|
import getOr from "lodash/fp/getOr"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { Button, Icon } from "@budibase/bbui"
|
import { Button, Icon } from "@budibase/bbui"
|
||||||
import Select from "components/common/Select.svelte"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import LinkedRecord from "./LinkedRecord.svelte"
|
import LinkedRecord from "./LinkedRecord.svelte"
|
||||||
import TablePagination from "./TablePagination.svelte"
|
import TablePagination from "./TablePagination.svelte"
|
||||||
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
||||||
import RowPopover from "./popovers/Row.svelte"
|
import RowPopover from "./popovers/Row.svelte"
|
||||||
import ColumnPopover from "./popovers/Column.svelte"
|
import ColumnPopover from "./popovers/Column.svelte"
|
||||||
|
import ViewPopover from "./popovers/View.svelte"
|
||||||
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
||||||
import EditRowPopover from "./popovers/EditRow.svelte"
|
import EditRowPopover from "./popovers/EditRow.svelte"
|
||||||
import * as api from "./api"
|
import * as api from "./api"
|
||||||
|
@ -21,12 +22,14 @@
|
||||||
let modalOpen = false
|
let modalOpen = false
|
||||||
let data = []
|
let data = []
|
||||||
let headers = []
|
let headers = []
|
||||||
let views = []
|
|
||||||
let currentPage = 0
|
let currentPage = 0
|
||||||
let search
|
let search
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($backendUiStore.selectedView) {
|
if (
|
||||||
|
$backendUiStore.selectedView &&
|
||||||
|
$backendUiStore.selectedView.name.startsWith("all_")
|
||||||
|
) {
|
||||||
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
||||||
data = records || []
|
data = records || []
|
||||||
})
|
})
|
||||||
|
@ -42,27 +45,11 @@
|
||||||
$: sort = $backendUiStore.sort
|
$: sort = $backendUiStore.sort
|
||||||
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
||||||
|
|
||||||
$: headers = Object.keys($backendUiStore.selectedModel.schema).filter(
|
$: headers = Object.keys($backendUiStore.selectedModel.schema)
|
||||||
id => !INTERNAL_HEADERS.includes(id)
|
.sort()
|
||||||
)
|
.filter(id => !INTERNAL_HEADERS.includes(id))
|
||||||
|
|
||||||
$: schema = $backendUiStore.selectedModel.schema
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
|
|
||||||
const createNewRecord = () => {
|
|
||||||
open(
|
|
||||||
CreateEditRecordModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (views.length) {
|
|
||||||
backendUiStore.actions.views.select(views[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -72,6 +59,7 @@
|
||||||
<ColumnPopover />
|
<ColumnPopover />
|
||||||
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
|
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
|
||||||
<RowPopover />
|
<RowPopover />
|
||||||
|
<ViewPopover />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,7 +90,7 @@
|
||||||
<td>
|
<td>
|
||||||
{#if schema[header].type === 'link'}
|
{#if schema[header].type === 'link'}
|
||||||
<LinkedRecord field={schema[header]} ids={row[header]} />
|
<LinkedRecord field={schema[header]} ids={row[header]} />
|
||||||
{:else}{row[header] || ''}{/if}
|
{:else}{getOr('', header, row)}{/if}
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -190,7 +178,6 @@
|
||||||
|
|
||||||
.popovers {
|
.popovers {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-data {
|
.no-data {
|
|
@ -0,0 +1,144 @@
|
||||||
|
<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 LinkedRecord from "./LinkedRecord.svelte"
|
||||||
|
import TablePagination from "./TablePagination.svelte"
|
||||||
|
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
||||||
|
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 columns = []
|
||||||
|
export let data = []
|
||||||
|
export let title
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 10
|
||||||
|
|
||||||
|
let currentPage = 0
|
||||||
|
|
||||||
|
$: paginatedData =
|
||||||
|
data && data.length
|
||||||
|
? data.slice(
|
||||||
|
currentPage * ITEMS_PER_PAGE,
|
||||||
|
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
$: sort = $backendUiStore.sort
|
||||||
|
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="table-controls">
|
||||||
|
<h2 class="title">{title}</h2>
|
||||||
|
<div class="popovers">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="uk-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{#each columns as header}
|
||||||
|
<th>{header.name}</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>{getOr(row.default || '', header.key, row)}</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<TablePagination
|
||||||
|
{data}
|
||||||
|
bind:currentPage
|
||||||
|
pageItemCount={data.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>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<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 { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
||||||
|
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"
|
||||||
|
|
||||||
|
let COLUMNS = [
|
||||||
|
{
|
||||||
|
name: "group",
|
||||||
|
key: "key",
|
||||||
|
default: "All Records",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sum",
|
||||||
|
key: "value.sum",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min",
|
||||||
|
key: "value.min",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max",
|
||||||
|
key: "value.max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sumsqr",
|
||||||
|
key: "value.sumsqr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "count",
|
||||||
|
key: "value.count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "avg",
|
||||||
|
key: "value.avg",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
$: ({ name, groupBy } = view)
|
||||||
|
$: !name.startsWith("all_") && fetchViewData(name, groupBy)
|
||||||
|
|
||||||
|
async function fetchViewData(name, groupBy) {
|
||||||
|
let QUERY_VIEW_URL = `/api/views/${name}?stats=true`
|
||||||
|
if (groupBy) {
|
||||||
|
QUERY_VIEW_URL += `&group=${groupBy}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.get(QUERY_VIEW_URL)
|
||||||
|
data = await response.json()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Table title={decodeURI(view.name)} columns={COLUMNS} {data}>
|
||||||
|
<CalculationPopover {view} />
|
||||||
|
<GroupByPopover {view} />
|
||||||
|
</Table>
|
|
@ -6,20 +6,6 @@ export async function createUser(user) {
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createDatabase(appname, instanceName) {
|
|
||||||
const CREATE_DATABASE_URL = `/api/${appname}/instances`
|
|
||||||
const response = await api.post(CREATE_DATABASE_URL, {
|
|
||||||
name: instanceName,
|
|
||||||
})
|
|
||||||
return await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteRecord(record) {
|
|
||||||
const DELETE_RECORDS_URL = `/api/${record.modelId}/records/${record._id}/${record._rev}`
|
|
||||||
const response = await api.delete(DELETE_RECORDS_URL)
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveRecord(record, modelId) {
|
export async function saveRecord(record, modelId) {
|
||||||
const SAVE_RECORDS_URL = `/api/${modelId}/records`
|
const SAVE_RECORDS_URL = `/api/${modelId}/records`
|
||||||
const response = await api.post(SAVE_RECORDS_URL, record)
|
const response = await api.post(SAVE_RECORDS_URL, record)
|
||||||
|
@ -27,8 +13,14 @@ export async function saveRecord(record, modelId) {
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchDataForView(viewName) {
|
export async function deleteRecord(record) {
|
||||||
const FETCH_RECORDS_URL = `/api/views/${viewName}`
|
const DELETE_RECORDS_URL = `/api/${record.modelId}/records/${record._id}/${record._rev}`
|
||||||
|
const response = await api.delete(DELETE_RECORDS_URL)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchDataForView(view) {
|
||||||
|
const FETCH_RECORDS_URL = `/api/views/${view.name}`
|
||||||
|
|
||||||
const response = await api.get(FETCH_RECORDS_URL)
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
return await response.json()
|
return await response.json()
|
|
@ -1,11 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { Input, TextArea, Button, Select } from "@budibase/bbui"
|
import { Input, TextArea, Button, Select } from "@budibase/bbui"
|
||||||
|
import { cloneDeep, merge } from "lodash/fp"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import Dropdown from "components/common/Dropdown.svelte"
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
||||||
import NumberBox from "components/common/NumberBox.svelte"
|
import NumberBox from "components/common/NumberBox.svelte"
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
import ValuesList from "components/common/ValuesList.svelte"
|
||||||
|
@ -16,8 +15,13 @@
|
||||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
|
||||||
|
let fieldDefinitions = cloneDeep(FIELDS)
|
||||||
|
|
||||||
export let onClosed
|
export let onClosed
|
||||||
export let field = {}
|
export let field = {
|
||||||
|
type: "string",
|
||||||
|
constraints: fieldDefinitions.STRING.constraints,
|
||||||
|
}
|
||||||
|
|
||||||
let originalName = field.name
|
let originalName = field.name
|
||||||
|
|
||||||
|
@ -25,9 +29,6 @@
|
||||||
field.constraints &&
|
field.constraints &&
|
||||||
field.constraints.presence &&
|
field.constraints.presence &&
|
||||||
!field.constraints.presence.allowEmpty
|
!field.constraints.presence.allowEmpty
|
||||||
$: if (field.type) {
|
|
||||||
field.constraints = FIELDS[field.type.toUpperCase()].constraints
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
|
@ -40,13 +41,26 @@
|
||||||
})
|
})
|
||||||
onClosed()
|
onClosed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFieldConstraints(event) {
|
||||||
|
const { type, constraints } = fieldDefinitions[
|
||||||
|
event.target.value.toUpperCase()
|
||||||
|
]
|
||||||
|
|
||||||
|
field.type = type
|
||||||
|
field.constraints = constraints
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<Input placeholder="Name" thin bind:value={field.name} />
|
<Input placeholder="Name" thin bind:value={field.name} />
|
||||||
|
|
||||||
<Select secondary thin bind:value={field.type}>
|
<Select
|
||||||
{#each Object.values(FIELDS) as field}
|
secondary
|
||||||
|
thin
|
||||||
|
on:change={handleFieldConstraints}
|
||||||
|
bind:value={field.type}>
|
||||||
|
{#each Object.values(fieldDefinitions) as field}
|
||||||
<option value={field.type}>{field.name}</option>
|
<option value={field.type}>{field.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -60,21 +74,21 @@
|
||||||
on:change={() => (field.constraints.presence.allowEmpty = required)} />
|
on:change={() => (field.constraints.presence.allowEmpty = required)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if field.type === 'string'}
|
{#if field.type === 'string' && field.constraints}
|
||||||
<NumberBox
|
<NumberBox
|
||||||
label="Max Length"
|
label="Max Length"
|
||||||
bind:value={field.constraints.length.maximum} />
|
bind:value={field.constraints.length.maximum} />
|
||||||
<ValuesList
|
<ValuesList
|
||||||
label="Categories"
|
label="Categories"
|
||||||
bind:values={field.constraints.inclusion} />
|
bind:values={field.constraints.inclusion} />
|
||||||
{:else if field.type === 'datetime'}
|
{:else if field.type === 'datetime' && field.constraints}
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label="Min Value"
|
label="Min Value"
|
||||||
bind:value={field.constraints.datetime.earliest} />
|
bind:value={field.constraints.datetime.earliest} />
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label="Max Value"
|
label="Max Value"
|
||||||
bind:value={field.constraints.datetime.latest} />
|
bind:value={field.constraints.datetime.latest} />
|
||||||
{:else if field.type === 'number'}
|
{:else if field.type === 'number' && field.constraints}
|
||||||
<NumberBox
|
<NumberBox
|
||||||
label="Min Value"
|
label="Min Value"
|
||||||
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
|
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
|
|
@ -5,7 +5,6 @@
|
||||||
import { compose, map, get, flatten } from "lodash/fp"
|
import { compose, map, get, flatten } from "lodash/fp"
|
||||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
import Select from "components/common/Select.svelte"
|
|
||||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
import RecordFieldControl from "./RecordFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
@ -67,7 +66,7 @@
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
<form on:submit|preventDefault class="uk-form-stacked">
|
<form on:submit|preventDefault class="uk-form-stacked">
|
||||||
{#each modelSchema as [key, meta]}
|
{#each modelSchema as [key, meta]}
|
||||||
<div class="uk-margin">
|
<div class="bb-margin-xl">
|
||||||
{#if meta.type === 'link'}
|
{#if meta.type === 'link'}
|
||||||
<LinkedRecordSelector
|
<LinkedRecordSelector
|
||||||
bind:linked={record[key]}
|
bind:linked={record[key]}
|
|
@ -10,10 +10,10 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<heading>
|
<header>
|
||||||
<i class="ri-information-line alert" />
|
<i class="ri-information-line alert" />
|
||||||
<h4 class="budibase__title--4">Delete Record</h4>
|
<h4 class="budibase__title--4">Delete Record</h4>
|
||||||
</heading>
|
</header>
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to delete this record? All of your data will be
|
Are you sure you want to delete this record? All of your data will be
|
||||||
permanently removed. This action cannot be undone.
|
permanently removed. This action cannot be undone.
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
heading {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
|
@ -14,10 +14,10 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<heading>
|
<header>
|
||||||
<i class="ri-information-line alert" />
|
<i class="ri-information-line alert" />
|
||||||
<h4 class="budibase__title--4">Delete Table</h4>
|
<h4 class="budibase__title--4">Delete Table</h4>
|
||||||
</heading>
|
</header>
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to delete this table? All of your data will be
|
Are you sure you want to delete this table? All of your data will be
|
||||||
permanently removed. This action cannot be undone.
|
permanently removed. This action cannot be undone.
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
heading {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
<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>
|
|
@ -2,12 +2,10 @@
|
||||||
import { Input, Select } from "@budibase/bbui"
|
import { Input, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let value = ""
|
export let value = type === "checkbox" ? false : ""
|
||||||
export let label
|
export let label
|
||||||
export let options = []
|
export let options = []
|
||||||
|
|
||||||
let checked = type === "checkbox" ? value : false
|
|
||||||
|
|
||||||
const handleInput = event => {
|
const handleInput = event => {
|
||||||
if (event.target.type === "checkbox") {
|
if (event.target.type === "checkbox") {
|
||||||
value = event.target.checked
|
value = event.target.checked
|
||||||
|
@ -38,7 +36,7 @@
|
||||||
thin
|
thin
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
data-cy="{label}-input"
|
data-cy="{label}-input"
|
||||||
{checked}
|
checked={value}
|
||||||
{type}
|
{type}
|
||||||
{value}
|
{value}
|
||||||
on:input={handleInput}
|
on:input={handleInput}
|
|
@ -1,4 +1,2 @@
|
||||||
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
|
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
|
||||||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
|
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
|
||||||
export { default as CreateEditViewModal } from "./CreateEditView.svelte"
|
|
||||||
export { default as CreateUserModal } from "./CreateUser.svelte"
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
TextButton,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
||||||
|
|
||||||
|
const CALCULATIONS = [
|
||||||
|
{
|
||||||
|
name: "Statistics",
|
||||||
|
key: "stats",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
|
||||||
|
$: viewModel = $backendUiStore.models.find(
|
||||||
|
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||||
|
)
|
||||||
|
$: fields =
|
||||||
|
viewModel &&
|
||||||
|
Object.keys(viewModel.schema).filter(
|
||||||
|
field => viewModel.schema[field].type === "number"
|
||||||
|
)
|
||||||
|
|
||||||
|
function saveView() {
|
||||||
|
backendUiStore.actions.views.save(view)
|
||||||
|
notifier.success(`View ${view.name} saved.`)
|
||||||
|
dropdown.hide()
|
||||||
|
}
|
||||||
|
</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">
|
||||||
|
<h5>Calculate</h5>
|
||||||
|
<div class="input-group-row">
|
||||||
|
<p>The</p>
|
||||||
|
<Select secondary thin bind:value={view.calculation}>
|
||||||
|
{#each CALCULATIONS as calculation}
|
||||||
|
<option value={calculation.key}>{calculation.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<p>of</p>
|
||||||
|
<Select secondary thin bind:value={view.field}>
|
||||||
|
{#each fields as field}
|
||||||
|
<option value={field}>{field}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
||||||
|
<Button primary on:click={saveView}>Save</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h5 {
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50px 1fr 20px 1fr;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,9 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
TextButton as Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { ModelSetupNav } from "components/nav/ModelSetupNav"
|
|
||||||
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte"
|
|
||||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
||||||
|
|
||||||
let anchor
|
let anchor
|
|
@ -2,8 +2,6 @@
|
||||||
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 { ModelSetupNav } from "components/nav/ModelSetupNav"
|
|
||||||
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte"
|
|
||||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
@ -27,6 +25,7 @@
|
||||||
|
|
||||||
function deleteField() {
|
function deleteField() {
|
||||||
backendUiStore.actions.models.deleteField(field)
|
backendUiStore.actions.models.deleteField(field)
|
||||||
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
function sort(direction, column) {
|
function sort(direction, column) {
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
TextButton,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
||||||
|
|
||||||
|
const CALCULATIONS = [
|
||||||
|
{
|
||||||
|
name: "Statistics",
|
||||||
|
key: "stats",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export let view = {}
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
|
||||||
|
$: viewModel = $backendUiStore.models.find(
|
||||||
|
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||||
|
)
|
||||||
|
$: fields = viewModel && Object.keys(viewModel.schema)
|
||||||
|
|
||||||
|
function saveView() {
|
||||||
|
backendUiStore.actions.views.save(view)
|
||||||
|
notifier.success(`View ${view.name} saved.`)
|
||||||
|
dropdown.hide()
|
||||||
|
}
|
||||||
|
</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">
|
||||||
|
<h5>Group By</h5>
|
||||||
|
<div class="input-group-row">
|
||||||
|
<p>Group By</p>
|
||||||
|
<Select secondary thin bind:value={view.groupBy}>
|
||||||
|
<option value={false} />
|
||||||
|
{#each fields as field}
|
||||||
|
<option value={field}>{field}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
||||||
|
<Button primary on:click={saveView}>Save</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h5 {
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50px 1fr 20px 1fr;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { DropdownMenu, Button, Icon } from "@budibase/bbui"
|
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui"
|
||||||
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
||||||
|
|
||||||
let anchor
|
let anchor
|
|
@ -0,0 +1,84 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
TextButton,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
|
||||||
|
let name
|
||||||
|
let field
|
||||||
|
|
||||||
|
$: fields = Object.keys($backendUiStore.selectedModel.schema).filter(key => {
|
||||||
|
return $backendUiStore.selectedModel.schema[key].type === "number"
|
||||||
|
})
|
||||||
|
$: views = $backendUiStore.models.flatMap(model =>
|
||||||
|
Object.keys(model.views || {})
|
||||||
|
)
|
||||||
|
|
||||||
|
function saveView() {
|
||||||
|
if (views.includes(name)) {
|
||||||
|
notifier.danger(`View exists with name ${name}.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backendUiStore.actions.views.save({
|
||||||
|
name,
|
||||||
|
modelId: $backendUiStore.selectedModel._id,
|
||||||
|
field,
|
||||||
|
})
|
||||||
|
notifier.success(`View ${name} created`)
|
||||||
|
dropdown.hide()
|
||||||
|
$goto(`../../../view/${name}`)
|
||||||
|
}
|
||||||
|
</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">
|
||||||
|
<h5>Create View</h5>
|
||||||
|
<div class="input-group-column">
|
||||||
|
<Input placeholder="View Name" thin bind:value={name} />
|
||||||
|
<Select thin secondary bind:value={field}>
|
||||||
|
{#each fields as field}
|
||||||
|
<option value={field}>{field}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
||||||
|
<Button primary on:click={saveView}>Save View</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h5 {
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,127 +0,0 @@
|
||||||
<script>
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
import CodeArea from "components/common/CodeArea.svelte"
|
|
||||||
import Button from "components/common/Button.svelte"
|
|
||||||
import Dropdown from "components/common/Dropdown.svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { filter, some, map, compose } from "lodash/fp"
|
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
const SNIPPET_EDITORS = {
|
|
||||||
MAP: "Map",
|
|
||||||
FILTER: "Filter",
|
|
||||||
REDUCE: "Reduce",
|
|
||||||
}
|
|
||||||
|
|
||||||
const COUCHDB_FUNCTION = `function(doc) {
|
|
||||||
|
|
||||||
}`
|
|
||||||
|
|
||||||
export let onClosed
|
|
||||||
export let view = {}
|
|
||||||
|
|
||||||
let currentSnippetEditor = SNIPPET_EDITORS.MAP
|
|
||||||
|
|
||||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
|
||||||
|
|
||||||
function deleteView() {}
|
|
||||||
|
|
||||||
async function saveView() {
|
|
||||||
const SAVE_VIEW_URL = `/api/views`
|
|
||||||
const response = await api.post(SAVE_VIEW_URL, view)
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.views = [...state.views, response.view]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
onClosed()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<i class="ri-eye-line button--toggled" />
|
|
||||||
<h3 class="budibase__title--3">Create / Edit View</h3>
|
|
||||||
</div>
|
|
||||||
<form on:submit|preventDefault class="uk-form-stacked root">
|
|
||||||
{#if $store.errors && $store.errors.length > 0}
|
|
||||||
<ErrorsBox errors={$store.errors} />
|
|
||||||
{/if}
|
|
||||||
<div class="main">
|
|
||||||
<div class="uk-grid-small" uk-grid>
|
|
||||||
<div class="uk-width-1-2@s">
|
|
||||||
<Textbox bind:text={view.name} label="Name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="code-snippets">
|
|
||||||
{#each Object.values(SNIPPET_EDITORS) as snippetType}
|
|
||||||
<span
|
|
||||||
class="snippet-selector__heading hoverable"
|
|
||||||
class:highlighted={currentSnippetEditor === snippetType}
|
|
||||||
on:click={() => (currentSnippetEditor = snippetType)}>
|
|
||||||
{snippetType}
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
{#if currentSnippetEditor === SNIPPET_EDITORS.MAP}
|
|
||||||
<CodeArea bind:text={view.map} label="Map" />
|
|
||||||
{:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER}
|
|
||||||
<CodeArea bind:text={view.filter} label="Filter" />
|
|
||||||
{:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE}
|
|
||||||
<CodeArea bind:text={view.reduce} label="Reduce" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<div class="button">
|
|
||||||
<ActionButton secondary on:click={deleteView}>Delete</ActionButton>
|
|
||||||
</div>
|
|
||||||
<ActionButton color="secondary" on:click={saveView}>Save</ActionButton>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlighted {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.snippet-selector__heading {
|
|
||||||
margin-right: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--grey-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px 40px 0 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
margin: 20px 40px 0px 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-snippets {
|
|
||||||
margin: 20px 0px 20px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
background-color: var(--grey-1);
|
|
||||||
margin: 0 40px;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,94 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import * as api from "../api"
|
|
||||||
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
let username
|
|
||||||
let password
|
|
||||||
let accessLevelId
|
|
||||||
|
|
||||||
$: valid = username && password && accessLevelId
|
|
||||||
$: appId = $store.appId
|
|
||||||
|
|
||||||
async function createUser() {
|
|
||||||
const user = { name: username, username, password, accessLevelId }
|
|
||||||
const response = await api.createUser(user)
|
|
||||||
backendUiStore.actions.users.create(response)
|
|
||||||
onClosed()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault class="uk-form-stacked">
|
|
||||||
<div class="main">
|
|
||||||
<div class="heading">
|
|
||||||
<i class="ri-list-settings-line button--toggled" />
|
|
||||||
<div class="title">Create User</div>
|
|
||||||
</div>
|
|
||||||
<div class="uk-margin">
|
|
||||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
|
||||||
<input
|
|
||||||
data-cy="username"
|
|
||||||
class="uk-input"
|
|
||||||
type="text"
|
|
||||||
bind:value={username} />
|
|
||||||
</div>
|
|
||||||
<div class="uk-margin">
|
|
||||||
<label class="uk-form-label" for="form-stacked-text">Password</label>
|
|
||||||
<input
|
|
||||||
data-cy="password"
|
|
||||||
class="uk-input"
|
|
||||||
type="password"
|
|
||||||
bind:value={password} />
|
|
||||||
</div>
|
|
||||||
<div class="uk-margin">
|
|
||||||
<label class="uk-form-label" for="form-stacked-text">Access Level</label>
|
|
||||||
<select
|
|
||||||
data-cy="accessLevel"
|
|
||||||
class="uk-select"
|
|
||||||
bind:value={accessLevelId}>
|
|
||||||
<option value="" />
|
|
||||||
<option value="POWER_USER">Power User</option>
|
|
||||||
<option value="ADMIN">Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button">
|
|
||||||
<ActionButton secondary on:click={onClosed}>Cancel</ActionButton>
|
|
||||||
</div>
|
|
||||||
<ActionButton disabled={!valid} on:click={createUser}>Save</ActionButton>
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.main {
|
|
||||||
padding: 40px 40px 20px 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ink);
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-radius: 0 0 5px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,95 +0,0 @@
|
||||||
<script>
|
|
||||||
import { tick, onMount } from "svelte"
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import { CheckIcon } from "../common/Icons"
|
|
||||||
|
|
||||||
$: instances = $store.appInstances
|
|
||||||
|
|
||||||
async function selectDatabase(database) {
|
|
||||||
backendUiStore.actions.database.select(database)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteDatabase(database) {
|
|
||||||
const DELETE_DATABASE_URL = `/api/instances/${database.name}`
|
|
||||||
const response = await api.delete(DELETE_DATABASE_URL)
|
|
||||||
store.update(state => {
|
|
||||||
state.appInstances = state.appInstances.filter(
|
|
||||||
db => db._id !== database._id
|
|
||||||
)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<ul>
|
|
||||||
{#each $store.appInstances as database}
|
|
||||||
<li>
|
|
||||||
<span class="icon">
|
|
||||||
{#if database._id === $backendUiStore.selectedDatabase._id}
|
|
||||||
<CheckIcon />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
class:active={database._id === $backendUiStore.selectedDatabase._id}
|
|
||||||
on:click={() => {
|
|
||||||
$goto(`./database/${database._id}`), selectDatabase(database)
|
|
||||||
}}>
|
|
||||||
{database.name}
|
|
||||||
</button>
|
|
||||||
<i
|
|
||||||
class="ri-delete-bin-7-line hoverable alignment"
|
|
||||||
on:click={() => deleteDatabase(database)} />
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--ink);
|
|
||||||
position: relative;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alignment {
|
|
||||||
margin-left: auto;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 0px 0px 10px 0px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0 0 0 6px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
background: rgba(0, 0, 0, 0);
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,45 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
import getIcon from "../common/icon"
|
|
||||||
import { CreateEditViewModal } from "components/database/ModelDataTable/modals"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
export let node
|
|
||||||
export let type
|
|
||||||
export let onSelect
|
|
||||||
|
|
||||||
let navActive = ""
|
|
||||||
|
|
||||||
const ICON_MAP = {
|
|
||||||
index: "ri-eye-line",
|
|
||||||
model: "ri-list-settings-line",
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
on:click={() => onSelect(node)}
|
|
||||||
class="budibase__nav-item hierarchy-item"
|
|
||||||
class:capitalized={type === 'model'}
|
|
||||||
class:selected={$backendUiStore.selectedView === `all_${node._id}`}>
|
|
||||||
<i class={ICON_MAP[type]} />
|
|
||||||
<span style="margin-left: 1rem">{node.name}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.hierarchy-item {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.capitalized {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -17,6 +17,12 @@
|
||||||
})
|
})
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
$goto(`./model/${model._id}`)
|
$goto(`./model/${model._id}`)
|
||||||
|
name = ""
|
||||||
|
dropdown.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClosed = () => {
|
||||||
|
name = ""
|
||||||
dropdown.hide()
|
dropdown.hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -35,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="button-margin-3">
|
<div class="button-margin-3">
|
||||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-margin-4">
|
<div class="button-margin-4">
|
||||||
<Button primary on:click={saveTable}>Save</Button>
|
<Button primary on:click={saveTable}>Save</Button>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
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 DeleteTableModal from "components/database/ModelDataTable/modals/DeleteTable.svelte"
|
import DeleteTableModal from "components/database/DataTable/modals/DeleteTable.svelte"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
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 DeleteViewModal from "components/database/DataTable/modals/DeleteView.svelte"
|
||||||
|
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
export let view
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
|
||||||
|
let editing
|
||||||
|
let originalName = view.name
|
||||||
|
|
||||||
|
function showEditor() {
|
||||||
|
editing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideEditor() {
|
||||||
|
dropdown.hide()
|
||||||
|
editing = false
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteView = () => {
|
||||||
|
open(
|
||||||
|
DeleteViewModal,
|
||||||
|
{
|
||||||
|
onClosed: close,
|
||||||
|
viewName: view.name,
|
||||||
|
},
|
||||||
|
{ styleContent: { padding: "0" } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
backendUiStore.actions.views.save({
|
||||||
|
originalName,
|
||||||
|
...view,
|
||||||
|
})
|
||||||
|
notifier.success("Renamed View Successfully.")
|
||||||
|
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 View</h5>
|
||||||
|
<div class="container">
|
||||||
|
<Input placeholder="View Name" thin bind:value={view.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-view" on:click={deleteView}>
|
||||||
|
<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>
|
|
@ -6,15 +6,24 @@
|
||||||
export let indented
|
export let indented
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class:selected on:click class={className}>
|
<div
|
||||||
<i class:indented class={icon} />
|
data-cy="model-nav-item"
|
||||||
|
class:indented
|
||||||
|
class:selected
|
||||||
|
on:click
|
||||||
|
class={className}>
|
||||||
|
<i class={icon} />
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.indented {
|
.indented {
|
||||||
margin-left: 10px;
|
grid-template-columns: 50px 1fr 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indented i {
|
||||||
|
justify-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -8,20 +8,21 @@
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button } from "@budibase/bbui"
|
||||||
import CreateTablePopover from "./CreateTable.svelte"
|
import CreateTablePopover from "./CreateTable.svelte"
|
||||||
import EditTablePopover from "./EditTable.svelte"
|
import EditTablePopover from "./EditTable.svelte"
|
||||||
|
import EditViewPopover from "./EditView.svelte"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL
|
$: selectedView =
|
||||||
|
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
||||||
|
|
||||||
function selectModel(model, fieldId) {
|
function selectModel(model) {
|
||||||
backendUiStore.actions.models.select(model)
|
backendUiStore.actions.models.select(model)
|
||||||
$goto(`./model/${model._id}`)
|
$goto(`./model/${model._id}`)
|
||||||
if (fieldId) {
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.selectedField = fieldId
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectView(view) {
|
||||||
|
backendUiStore.actions.views.select(view)
|
||||||
|
$goto(`./view/${view.name}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -34,12 +35,26 @@
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
<ListItem
|
<ListItem
|
||||||
selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id}
|
selected={selectedView === `all_${model._id}`}
|
||||||
title={model.name}
|
title={model.name}
|
||||||
icon="ri-table-fill"
|
icon="ri-table-fill"
|
||||||
on:click={() => selectModel(model)}>
|
on:click={() => selectModel(model)}>
|
||||||
<EditTablePopover table={model} />
|
<EditTablePopover table={model} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
{#each Object.keys(model.views || {}) as viewName}
|
||||||
|
<ListItem
|
||||||
|
indented
|
||||||
|
selected={selectedView === viewName}
|
||||||
|
title={viewName}
|
||||||
|
icon="ri-eye-line"
|
||||||
|
on:click={() => selectView({
|
||||||
|
name: viewName,
|
||||||
|
...model.views[viewName],
|
||||||
|
})}>
|
||||||
|
<EditViewPopover
|
||||||
|
view={{ name: viewName, ...model.views[viewName] }} />
|
||||||
|
</ListItem>
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
<script>
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { Button } from "@budibase/bbui"
|
|
||||||
import Dropdown from "components/common/Dropdown.svelte"
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
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 { keys, cloneDeep } from "lodash/fp"
|
|
||||||
|
|
||||||
const FIELD_TYPES = ["string", "number", "boolean", "link"]
|
|
||||||
|
|
||||||
let field = {}
|
|
||||||
|
|
||||||
$: field =
|
|
||||||
$backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {}
|
|
||||||
$: required =
|
|
||||||
field.constraints &&
|
|
||||||
field.constraints.presence &&
|
|
||||||
!field.constraints.presence.allowEmpty
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<div class="field-box">
|
|
||||||
<header>Name</header>
|
|
||||||
<input class="budibase__input" type="text" bind:value={field.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<div class="field-box">
|
|
||||||
<header>Type</header>
|
|
||||||
<span>{field.type}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<div class="field">
|
|
||||||
<label>Required</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked={required}
|
|
||||||
on:change={() => (field.constraints.presence.allowEmpty = required)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if field.type === 'string'}
|
|
||||||
<NumberBox
|
|
||||||
label="Max Length"
|
|
||||||
bind:value={field.constraints.length.maximum} />
|
|
||||||
<ValuesList label="Categories" bind:values={field.constraints.inclusion} />
|
|
||||||
{:else if field.type === 'datetime'}
|
|
||||||
<DatePicker
|
|
||||||
label="Min Value"
|
|
||||||
bind:value={field.constraints.datetime.earliest} />
|
|
||||||
<DatePicker
|
|
||||||
label="Max Value"
|
|
||||||
bind:value={field.constraints.datetime.latest} />
|
|
||||||
{:else if field.type === 'number'}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.info {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-box header {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-box span {
|
|
||||||
background: var(--grey-2);
|
|
||||||
color: var(--grey-6);
|
|
||||||
font-weight: 400;
|
|
||||||
height: 36px;
|
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 12px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,156 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { Button, Switcher } from "@budibase/bbui"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import ModelFieldEditor from "./ModelFieldEditor.svelte"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
const ITEMS = [
|
|
||||||
{
|
|
||||||
title: "Setup",
|
|
||||||
key: "SETUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Delete",
|
|
||||||
key: "DELETE",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
let edited = false
|
|
||||||
|
|
||||||
$: selectedTab = $backendUiStore.tabs.SETUP_PANEL
|
|
||||||
|
|
||||||
$: edited =
|
|
||||||
$backendUiStore.selectedField ||
|
|
||||||
($backendUiStore.draftModel &&
|
|
||||||
$backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name)
|
|
||||||
|
|
||||||
async function deleteModel() {
|
|
||||||
const model = $backendUiStore.selectedModel
|
|
||||||
const field = $backendUiStore.selectedField
|
|
||||||
|
|
||||||
if (field) {
|
|
||||||
const name = model.schema[field].name
|
|
||||||
delete model.schema[field]
|
|
||||||
backendUiStore.actions.models.save(model)
|
|
||||||
notifier.danger(`Field ${name} deleted.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const DELETE_MODEL_URL = `/api/models/${model._id}/${model._rev}`
|
|
||||||
const response = await api.delete(DELETE_MODEL_URL)
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.selectedView = null
|
|
||||||
state.selectedModel = {}
|
|
||||||
state.draftModel = {}
|
|
||||||
state.models = state.models.filter(({ _id }) => _id !== model._id)
|
|
||||||
notifier.danger(`${model.name} deleted successfully.`)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate() {
|
|
||||||
let errors = []
|
|
||||||
for (let field of Object.values($backendUiStore.draftModel.schema)) {
|
|
||||||
const restrictedFieldNames = ["type", "modelId"]
|
|
||||||
if (field.name.startsWith("_")) {
|
|
||||||
errors.push(`field '${field.name}' - name cannot begin with '_''`)
|
|
||||||
} else if (restrictedFieldNames.includes(field.name)) {
|
|
||||||
errors.push(
|
|
||||||
`field '${field.name}' - is a restricted name, please rename`
|
|
||||||
)
|
|
||||||
} else if (!field.name || !field.name.trim()) {
|
|
||||||
errors.push("field name cannot be blank")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$backendUiStore.draftModel.name) {
|
|
||||||
errors.push("Table name cannot be blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveModel() {
|
|
||||||
const errors = validate()
|
|
||||||
if (errors.length > 0) {
|
|
||||||
notifier.danger(errors.join("/n"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await backendUiStore.actions.models.save($backendUiStore.draftModel)
|
|
||||||
notifier.success(
|
|
||||||
"Success! Your changes have been saved. Please continue on with your greatness."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="items-root">
|
|
||||||
<Switcher headings={ITEMS} bind:value={$backendUiStore.tabs.SETUP_PANEL}>
|
|
||||||
{#if selectedTab === 'SETUP'}
|
|
||||||
{#if $backendUiStore.selectedField}
|
|
||||||
<ModelFieldEditor />
|
|
||||||
{:else if $backendUiStore.draftModel.schema}
|
|
||||||
<div class="titled-input">
|
|
||||||
<header>Name</header>
|
|
||||||
<input
|
|
||||||
data-cy="table-name-input"
|
|
||||||
type="text"
|
|
||||||
class="budibase__input"
|
|
||||||
bind:value={$backendUiStore.draftModel.name} />
|
|
||||||
</div>
|
|
||||||
<!-- dont have this capability yet..
|
|
||||||
<div class="titled-input">
|
|
||||||
<header>Import Data</header>
|
|
||||||
<Button wide secondary>Import CSV</Button>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
{/if}
|
|
||||||
<footer>
|
|
||||||
<Button disabled={!edited} green={edited} wide on:click={saveModel}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
{:else if selectedTab === 'DELETE'}
|
|
||||||
<div class="titled-input">
|
|
||||||
<header>Danger Zone</header>
|
|
||||||
<Button red wide on:click={deleteModel}>Delete</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Switcher>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
header {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
width: 260px;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-root {
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.titled-input {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titled-input header {
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1 +0,0 @@
|
||||||
export { default as ModelSetupNav } from "./ModelSetupNav.svelte"
|
|
|
@ -1,82 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import getIcon from "../common/icon"
|
|
||||||
import { CheckIcon } from "../common/Icons"
|
|
||||||
|
|
||||||
const getPage = (s, name) => {
|
|
||||||
const props = s.pages[name]
|
|
||||||
return { name, props }
|
|
||||||
}
|
|
||||||
|
|
||||||
$: currentAppInfo = {
|
|
||||||
name: $store.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUsers() {
|
|
||||||
const FETCH_USERS_URL = `/api/users`
|
|
||||||
const response = await api.get(FETCH_USERS_URL)
|
|
||||||
const users = await response.json()
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.users = users
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(fetchUsers)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<ul>
|
|
||||||
{#each $backendUiStore.users as user}
|
|
||||||
<li>
|
|
||||||
<i class="ri-user-4-line" />
|
|
||||||
<button class:active={user.username === $store.currentUserName}>
|
|
||||||
{user.name}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--secondary50);
|
|
||||||
font-weight: bold;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0 0 0 6px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
background: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -13,35 +13,26 @@
|
||||||
<style>
|
<style>
|
||||||
.apps-card {
|
.apps-card {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
padding: 20px 20px 20px 20px;
|
padding: var(--spacing-xl);
|
||||||
max-width: 400px;
|
max-width: 300px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
border-radius: 5px;
|
border-radius: var(--border-radius-m);
|
||||||
border: 1px solid var(--grey-4);
|
border: var(--border-dark);
|
||||||
font-family: Inter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button:hover {
|
.app-button:hover {
|
||||||
background-color: var(--grey-1);
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-title {
|
.app-title {
|
||||||
font-size: 18px;
|
font-size: var(--font-size-l);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-desc {
|
|
||||||
color: var(--grey-7);
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -52,17 +43,18 @@
|
||||||
.app-button {
|
.app-button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--white);
|
background-color: var(--ink);
|
||||||
color: var(--ink);
|
color: var(--white);
|
||||||
|
border: 1.5px var(--ink) solid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 12px 20px;
|
padding: 8px 16px;
|
||||||
border-radius: 5px;
|
border-radius: var(--border-radius-s);
|
||||||
border: 1px var(--grey-2) solid;
|
font-size: var(--font-size-xs);
|
||||||
font-size: 14px;
|
font-weight: 500;
|
||||||
font-weight: 400;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
<style>
|
<style>
|
||||||
.apps {
|
.apps {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
grid-gap: 20px 40px;
|
grid-gap: var(--layout-m);
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,16 +208,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
{#if $createAppStore.currentStep > 0}
|
{#if $createAppStore.currentStep > 0}
|
||||||
<Button secondary on:click={back}>Back</Button>
|
<Button medium secondary on:click={back}>Back</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $createAppStore.currentStep < steps.length - 1}
|
{#if $createAppStore.currentStep < steps.length - 1}
|
||||||
<Button secondary on:click={next} disabled={!currentStepIsValid}>
|
<Button medium blue on:click={next} disabled={!currentStepIsValid}>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $createAppStore.currentStep === steps.length - 1}
|
{#if $createAppStore.currentStep === steps.length - 1}
|
||||||
<Button
|
<Button
|
||||||
secondary
|
medium
|
||||||
|
blue
|
||||||
on:click={signUp}
|
on:click={signUp}
|
||||||
disabled={!fullFormIsValid || submitting}>
|
disabled={!fullFormIsValid || submitting}>
|
||||||
{submitting ? 'Loading...' : 'Submit'}
|
{submitting ? 'Loading...' : 'Submit'}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
let blurred = { appName: false }
|
let blurred = { appName: false }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2>Create your first web app</h2>
|
<h2>Create your web app</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.appName = true)}
|
on:input={() => (blurred.appName = true)}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="pasword"
|
type="pasword"
|
||||||
error={blurred.password && validationErrors.password} />
|
error={blurred.password && validationErrors.password} />
|
||||||
<Select name="accessLevelId">
|
<Select 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>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
|
|
||||||
<TextArea
|
<TextArea
|
||||||
bind:value
|
bind:value
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import UIkit from "uikit"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
|
||||||
import CodeMirror from "codemirror"
|
|
||||||
import "codemirror/mode/javascript/javascript.js"
|
|
||||||
|
|
||||||
export let onCodeChanged
|
|
||||||
export let code
|
|
||||||
|
|
||||||
export const show = () => {
|
|
||||||
UIkit.modal(codeModal).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
let codeModal
|
|
||||||
let editor
|
|
||||||
let cmInstance
|
|
||||||
|
|
||||||
$: currentCode = code
|
|
||||||
$: originalCode = code
|
|
||||||
$: {
|
|
||||||
if (editor) {
|
|
||||||
if (!cmInstance) {
|
|
||||||
cmInstance = CodeMirror.fromTextArea(editor, {
|
|
||||||
mode: "javascript",
|
|
||||||
lineNumbers: false,
|
|
||||||
lineWrapping: true,
|
|
||||||
smartIndent: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
readOnly: false,
|
|
||||||
})
|
|
||||||
cmInstance.on("change", () => (currentCode = cmInstance.getValue()))
|
|
||||||
}
|
|
||||||
cmInstance.focus()
|
|
||||||
cmInstance.setValue(code || "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
UIkit.modal(codeModal).hide()
|
|
||||||
currentCode = originalCode
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
originalCode = currentCode
|
|
||||||
onCodeChanged(currentCode)
|
|
||||||
UIkit.modal(codeModal).hide()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={codeModal} uk-modal>
|
|
||||||
<div class="uk-modal-dialog" uk-overflow-auto>
|
|
||||||
|
|
||||||
<div class="uk-modal-header">
|
|
||||||
<h3>Code</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="uk-modal-body uk-form-horizontal">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Use the code box below to control how this component is displayed, with
|
|
||||||
javascript.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="editor-code-surround">
|
|
||||||
function(render, context, state, route) {'{'}
|
|
||||||
</div>
|
|
||||||
<div class="editor">
|
|
||||||
<textarea bind:this={editor} />
|
|
||||||
</div>
|
|
||||||
<div class="editor-code-surround">{'}'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="uk-modal-footer">
|
|
||||||
<ButtonGroup>
|
|
||||||
<ActionButton primary on:click={save}>Save</ActionButton>
|
|
||||||
<ActionButton alert on:click={cancel}>Close</ActionButton>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h3 {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #8997ab;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #333;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor {
|
|
||||||
border-style: dotted;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: gainsboro;
|
|
||||||
padding: 10px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-code-surround {
|
|
||||||
font-family: "Courier New", Courier, monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,320 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import Swatch from "./Swatch.svelte"
|
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
|
||||||
import { buildStyle } from "./helpers.js"
|
|
||||||
import {
|
|
||||||
getColorFormat,
|
|
||||||
convertToHSVA,
|
|
||||||
convertHsvaToFormat,
|
|
||||||
} from "./utils.js"
|
|
||||||
import Slider from "./Slider.svelte"
|
|
||||||
import Palette from "./Palette.svelte"
|
|
||||||
import ButtonGroup from "./ButtonGroup.svelte"
|
|
||||||
import Input from "./Input.svelte"
|
|
||||||
import Portal from "./Portal.svelte"
|
|
||||||
|
|
||||||
export let value = "#3ec1d3ff"
|
|
||||||
export let open = false
|
|
||||||
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
|
|
||||||
export let disableSwatches = false
|
|
||||||
export let format = "hexa"
|
|
||||||
export let style = ""
|
|
||||||
export let pickerHeight = 0
|
|
||||||
export let pickerWidth = 0
|
|
||||||
|
|
||||||
let colorPicker = null
|
|
||||||
let adder = null
|
|
||||||
|
|
||||||
let h = null
|
|
||||||
let s = null
|
|
||||||
let v = null
|
|
||||||
let a = null
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (!swatches.length > 0) {
|
|
||||||
//Don't use locally stored recent colors if swatches have been passed as props
|
|
||||||
getRecentColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colorPicker) {
|
|
||||||
colorPicker.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format) {
|
|
||||||
convertAndSetHSVA()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function getRecentColors() {
|
|
||||||
let colorStore = localStorage.getItem("cp:recent-colors")
|
|
||||||
if (colorStore) {
|
|
||||||
swatches = JSON.parse(colorStore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleEscape(e) {
|
|
||||||
if (open && e.key === "Escape") {
|
|
||||||
open = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRecentColor(color) {
|
|
||||||
if (swatches.length === 12) {
|
|
||||||
swatches.splice(0, 1)
|
|
||||||
}
|
|
||||||
if (!swatches.includes(color)) {
|
|
||||||
swatches = [...swatches, color]
|
|
||||||
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertAndSetHSVA() {
|
|
||||||
let hsva = convertToHSVA(value, format)
|
|
||||||
setHSVA(hsva)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHSVA([hue, sat, val, alpha]) {
|
|
||||||
h = hue
|
|
||||||
s = sat
|
|
||||||
v = val
|
|
||||||
a = alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
//fired by choosing a color from the palette
|
|
||||||
function setSaturationAndValue({ detail }) {
|
|
||||||
s = detail.s
|
|
||||||
v = detail.v
|
|
||||||
value = convertHsvaToFormat([h, s, v, a], format)
|
|
||||||
dispatchValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHue({ color, isDrag }) {
|
|
||||||
h = color
|
|
||||||
value = convertHsvaToFormat([h, s, v, a], format)
|
|
||||||
if (!isDrag) {
|
|
||||||
dispatchValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAlpha({ color, isDrag }) {
|
|
||||||
a = color === "1.00" ? "1" : color
|
|
||||||
value = convertHsvaToFormat([h, s, v, a], format)
|
|
||||||
if (!isDrag) {
|
|
||||||
dispatchValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchValue() {
|
|
||||||
dispatch("change", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFormatAndConvert(f) {
|
|
||||||
format = f
|
|
||||||
value = convertHsvaToFormat([h, s, v, a], format)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleColorInput(text) {
|
|
||||||
let format = getColorFormat(text)
|
|
||||||
if (format) {
|
|
||||||
value = text
|
|
||||||
convertAndSetHSVA()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchInputChange() {
|
|
||||||
if (format) {
|
|
||||||
dispatchValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSwatch() {
|
|
||||||
if (format) {
|
|
||||||
dispatch("addswatch", value)
|
|
||||||
setRecentColor(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSwatch(idx) {
|
|
||||||
let removedSwatch = swatches.splice(idx, 1)
|
|
||||||
swatches = swatches
|
|
||||||
dispatch("removeswatch", removedSwatch)
|
|
||||||
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
|
|
||||||
}
|
|
||||||
|
|
||||||
function applySwatch(color) {
|
|
||||||
if (value !== color) {
|
|
||||||
format = getColorFormat(color)
|
|
||||||
if (format) {
|
|
||||||
value = color
|
|
||||||
convertAndSetHSVA()
|
|
||||||
dispatchValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
|
|
||||||
$: selectedColorStyle = buildStyle({ background: value, border })
|
|
||||||
$: shrink = swatches.length > 0
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Portal>
|
|
||||||
<div
|
|
||||||
class="colorpicker-container"
|
|
||||||
transition:fade
|
|
||||||
bind:this={colorPicker}
|
|
||||||
{style}
|
|
||||||
tabindex="0"
|
|
||||||
on:keydown={handleEscape}
|
|
||||||
bind:clientHeight={pickerHeight}
|
|
||||||
bind:clientWidth={pickerWidth}>
|
|
||||||
|
|
||||||
<div class="palette-panel">
|
|
||||||
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-panel">
|
|
||||||
<div class="alpha-hue-panel">
|
|
||||||
<div>
|
|
||||||
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
|
||||||
<div class="selected-color" style={selectedColorStyle} />
|
|
||||||
</CheckedBackground>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Slider
|
|
||||||
type="hue"
|
|
||||||
value={h}
|
|
||||||
on:change={hue => setHue(hue.detail)}
|
|
||||||
on:dragend={dispatchValue} />
|
|
||||||
|
|
||||||
<CheckedBackground borderRadius="10px" backgroundSize="7px">
|
|
||||||
<Slider
|
|
||||||
type="alpha"
|
|
||||||
value={a}
|
|
||||||
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)}
|
|
||||||
on:dragend={dispatchValue} />
|
|
||||||
</CheckedBackground>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !disableSwatches}
|
|
||||||
<div transition:fade class="swatch-panel">
|
|
||||||
{#if swatches.length > 0}
|
|
||||||
{#each swatches as color, idx}
|
|
||||||
<Swatch
|
|
||||||
{color}
|
|
||||||
on:click={() => applySwatch(color)}
|
|
||||||
on:removeswatch={() => removeSwatch(idx)} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{#if swatches.length !== 12}
|
|
||||||
<div
|
|
||||||
bind:this={adder}
|
|
||||||
transition:fade
|
|
||||||
class="adder"
|
|
||||||
on:click={addSwatch}
|
|
||||||
class:shrink>
|
|
||||||
<span>+</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="format-input-panel">
|
|
||||||
<ButtonGroup {format} onclick={changeFormatAndConvert} />
|
|
||||||
<Input
|
|
||||||
{value}
|
|
||||||
on:input={event => handleColorInput(event.target.value)}
|
|
||||||
on:change={dispatchInputChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Portal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.colorpicker-container {
|
|
||||||
position: absolute;
|
|
||||||
outline: none;
|
|
||||||
z-index: 3;
|
|
||||||
display: flex;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 400;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 5px 0px;
|
|
||||||
height: auto;
|
|
||||||
width: 220px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1),
|
|
||||||
0 0 1em 0 rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.palette-panel {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-panel {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 8px;
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #d2d2d2;
|
|
||||||
color: #777373;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alpha-hue-panel {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 25px 1fr;
|
|
||||||
grid-gap: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-color {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swatch-panel {
|
|
||||||
flex: 0 0 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 0 5px;
|
|
||||||
max-height: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adder {
|
|
||||||
flex: 1;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
transition: flex 0.5s;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: #f1f3f4;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #d4d4d4;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-top: 3px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shrink {
|
|
||||||
flex: 0 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.format-input-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 3px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,157 +0,0 @@
|
||||||
<script>
|
|
||||||
import Colorpicker from "./Colorpicker.svelte"
|
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
|
||||||
import { createEventDispatcher, beforeUpdate } from "svelte"
|
|
||||||
|
|
||||||
import { buildStyle } from "./helpers.js"
|
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import { getColorFormat } from "./utils.js"
|
|
||||||
|
|
||||||
export let value = "#3ec1d3ff"
|
|
||||||
export let swatches = []
|
|
||||||
export let disableSwatches = false
|
|
||||||
export let open = false
|
|
||||||
export let width = "25px"
|
|
||||||
export let height = "25px"
|
|
||||||
|
|
||||||
let format = "hexa"
|
|
||||||
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 }
|
|
||||||
let positionSide = "top"
|
|
||||||
let colorPreview = null
|
|
||||||
|
|
||||||
let previewHeight = null
|
|
||||||
let previewWidth = null
|
|
||||||
let pickerWidth = 0
|
|
||||||
let pickerHeight = 0
|
|
||||||
|
|
||||||
let errorMsg = null
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
beforeUpdate(() => {
|
|
||||||
format = getColorFormat(value)
|
|
||||||
if (!format) {
|
|
||||||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value`
|
|
||||||
console.error(errorMsg)
|
|
||||||
} else {
|
|
||||||
errorMsg = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function openColorpicker(event) {
|
|
||||||
if (colorPreview) {
|
|
||||||
open = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onColorChange(color) {
|
|
||||||
value = color.detail
|
|
||||||
dispatch("change", color.detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (open && colorPreview) {
|
|
||||||
const {
|
|
||||||
top: spaceAbove,
|
|
||||||
width,
|
|
||||||
bottom,
|
|
||||||
right,
|
|
||||||
left,
|
|
||||||
} = colorPreview.getBoundingClientRect()
|
|
||||||
|
|
||||||
const spaceBelow = window.innerHeight - bottom
|
|
||||||
const previewCenter = previewWidth / 2
|
|
||||||
|
|
||||||
let y, x
|
|
||||||
|
|
||||||
if (spaceAbove > spaceBelow) {
|
|
||||||
positionSide = "bottom"
|
|
||||||
y = window.innerHeight - spaceAbove
|
|
||||||
} else {
|
|
||||||
positionSide = "top"
|
|
||||||
y = bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
x = left + previewCenter - pickerWidth / 2
|
|
||||||
|
|
||||||
dimensions = { [positionSide]: y.toFixed(1), left: x.toFixed(1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
$: previewStyle = buildStyle({ width, height, background: value })
|
|
||||||
$: errorPreviewStyle = buildStyle({ width, height })
|
|
||||||
$: pickerStyle = buildStyle({
|
|
||||||
[positionSide]: `${dimensions[positionSide]}px`,
|
|
||||||
left: `${dimensions.left}px`,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="color-preview-container">
|
|
||||||
{#if !errorMsg}
|
|
||||||
<CheckedBackground borderRadius="3px" backgroundSize="8px">
|
|
||||||
<div
|
|
||||||
bind:this={colorPreview}
|
|
||||||
bind:clientHeight={previewHeight}
|
|
||||||
bind:clientWidth={previewWidth}
|
|
||||||
class="color-preview"
|
|
||||||
style={previewStyle}
|
|
||||||
on:click={openColorpicker} />
|
|
||||||
</CheckedBackground>
|
|
||||||
|
|
||||||
{#if open}
|
|
||||||
<Colorpicker
|
|
||||||
style={pickerStyle}
|
|
||||||
on:change={onColorChange}
|
|
||||||
on:addswatch
|
|
||||||
on:removeswatch
|
|
||||||
bind:format
|
|
||||||
bind:value
|
|
||||||
bind:pickerHeight
|
|
||||||
bind:pickerWidth
|
|
||||||
bind:open
|
|
||||||
{swatches}
|
|
||||||
{disableSwatches} />
|
|
||||||
<div on:click|self={() => (open = false)} class="overlay" />
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<div class="color-preview preview-error" style={errorPreviewStyle}>
|
|
||||||
<span>×</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.color-preview-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
height: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-preview {
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #dedada;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-error {
|
|
||||||
background: #cccccc;
|
|
||||||
color: #808080;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .picker-container {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
width: fit-content;
|
|
||||||
height: fit-content;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
export let target = document.body
|
|
||||||
|
|
||||||
let targetEl
|
|
||||||
let portal
|
|
||||||
let componentInstance
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (typeof target === "string") {
|
|
||||||
targetEl = document.querySelector(target)
|
|
||||||
// Force exit
|
|
||||||
if (targetEl === null) {
|
|
||||||
return () => {}
|
|
||||||
}
|
|
||||||
} else if (target instanceof HTMLElement) {
|
|
||||||
targetEl = target
|
|
||||||
} else {
|
|
||||||
throw new TypeError(
|
|
||||||
`Unknown target type: ${typeof target}. Allowed types: String (CSS selector), HTMLElement.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
portal = document.createElement("div")
|
|
||||||
targetEl.appendChild(portal)
|
|
||||||
portal.appendChild(componentInstance)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
targetEl.removeChild(portal)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={componentInstance}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import PropsView from "./PropsView.svelte"
|
|
||||||
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import IconButton from "components/common/IconButton.svelte"
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
|
@ -11,8 +10,7 @@
|
||||||
CircleIndicator,
|
CircleIndicator,
|
||||||
EventsIcon,
|
EventsIcon,
|
||||||
} from "components/common/Icons/"
|
} from "components/common/Icons/"
|
||||||
import CodeEditor from "./CodeEditor.svelte"
|
import EventsEditor from "./EventsEditor"
|
||||||
import LayoutEditor from "./LayoutEditor.svelte"
|
|
||||||
import panelStructure from "./temporaryPanelStructure.js"
|
import panelStructure from "./temporaryPanelStructure.js"
|
||||||
import CategoryTab from "./CategoryTab.svelte"
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import DesignView from "./DesignView.svelte"
|
import DesignView from "./DesignView.svelte"
|
||||||
|
@ -24,6 +22,7 @@
|
||||||
let categories = [
|
let categories = [
|
||||||
{ value: "settings", name: "Settings" },
|
{ value: "settings", name: "Settings" },
|
||||||
{ value: "design", name: "Design" },
|
{ value: "design", name: "Design" },
|
||||||
|
{ value: "events", name: "Events" },
|
||||||
]
|
]
|
||||||
let selectedCategory = categories[0]
|
let selectedCategory = categories[0]
|
||||||
|
|
||||||
|
@ -111,6 +110,8 @@
|
||||||
displayNameField={displayName}
|
displayNameField={displayName}
|
||||||
onChange={onPropChanged}
|
onChange={onPropChanged}
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||||
|
{:else if selectedCategory.value === 'events'}
|
||||||
|
<EventsEditor component={componentInstance} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
<script>
|
|
||||||
import { searchAllComponents } from "./pagesParsing/searchComponents"
|
|
||||||
import { store } from "../builderStore"
|
|
||||||
|
|
||||||
export let onComponentChosen = () => {}
|
|
||||||
|
|
||||||
let phrase = ""
|
|
||||||
|
|
||||||
components = $store.components
|
|
||||||
|
|
||||||
$: filteredComponents = !phrase ? [] : searchAllComponents(components, phrase)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
|
|
||||||
<form on:submit|preventDefault class="uk-search uk-search-large">
|
|
||||||
<span uk-search-icon />
|
|
||||||
<input
|
|
||||||
class="uk-search-input"
|
|
||||||
type="search"
|
|
||||||
placeholder="Based on component..."
|
|
||||||
bind:value={phrase} />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{#each filteredComponents as component}
|
|
||||||
<div class="component" on:click={() => onComponentChosen(component)}>
|
|
||||||
<div class="title">{component.name}</div>
|
|
||||||
<div class="description">{component.description}</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.component {
|
|
||||||
padding: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0 0 1px 0;
|
|
||||||
border-color: var(--grey-1);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component:hover {
|
|
||||||
background-color: var(--primary10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.component > .title {
|
|
||||||
font-size: 13pt;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.component > .description {
|
|
||||||
font-size: 10pt;
|
|
||||||
color: var(--blue);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -19,15 +19,12 @@
|
||||||
const joinPath = join("/")
|
const joinPath = join("/")
|
||||||
|
|
||||||
const normalizedName = name =>
|
const normalizedName = name =>
|
||||||
pipe(
|
pipe(name, [
|
||||||
name,
|
|
||||||
[
|
|
||||||
trimCharsStart("./"),
|
trimCharsStart("./"),
|
||||||
trimCharsStart("~/"),
|
trimCharsStart("~/"),
|
||||||
trimCharsStart("../"),
|
trimCharsStart("../"),
|
||||||
trimChars(" "),
|
trimChars(" "),
|
||||||
]
|
])
|
||||||
)
|
|
||||||
|
|
||||||
const changeScreen = screen => {
|
const changeScreen = screen => {
|
||||||
store.setCurrentScreen(screen.props._instanceName)
|
store.setCurrentScreen(screen.props._instanceName)
|
||||||
|
|
|
@ -23,11 +23,7 @@
|
||||||
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||||
const get_name = s => (!s ? "" : last(s.split("/")))
|
const get_name = s => (!s ? "" : last(s.split("/")))
|
||||||
|
|
||||||
const get_capitalised_name = name =>
|
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
||||||
pipe(
|
|
||||||
name,
|
|
||||||
[get_name, capitalise]
|
|
||||||
)
|
|
||||||
const isScreenslot = name => name === "##builtin/screenslot"
|
const isScreenslot = name => name === "##builtin/screenslot"
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
<script>
|
|
||||||
import PropsView from "./PropsView.svelte"
|
|
||||||
import { store } from "../builderStore"
|
|
||||||
import Textbox from "../common/Textbox.svelte"
|
|
||||||
import Button from "../common/Button.svelte"
|
|
||||||
import { LayoutIcon, PaintIcon, TerminalIcon } from "../common/Icons/"
|
|
||||||
|
|
||||||
import { cloneDeep, join, split, last } from "lodash/fp"
|
|
||||||
import { assign } from "lodash"
|
|
||||||
|
|
||||||
$: component = $store.currentPreviewItem
|
|
||||||
$: componentInfo = $store.currentComponentInfo
|
|
||||||
$: components = $store.components
|
|
||||||
|
|
||||||
const updateComponent = doChange => doChange(cloneDeep(component))
|
|
||||||
|
|
||||||
const onPropsChanged = newProps => {
|
|
||||||
updateComponent(newComponent => assign(newComponent.props, newProps))
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<button>
|
|
||||||
<PaintIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button>
|
|
||||||
<LayoutIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button>
|
|
||||||
<TerminalIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="component-props-container">
|
|
||||||
<PropsView {componentInfo} {onPropsChanged} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title > div:nth-child(1) {
|
|
||||||
grid-column-start: name;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title > div:nth-child(2) {
|
|
||||||
grid-column-start: actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-props-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-right: 20px;
|
|
||||||
background: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li button {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 13px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,28 +1,34 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button, Select } from "@budibase/bbui"
|
||||||
|
import Modal from "../../common/Modal.svelte"
|
||||||
import HandlerSelector from "./HandlerSelector.svelte"
|
import HandlerSelector from "./HandlerSelector.svelte"
|
||||||
|
import IconButton from "../../common/IconButton.svelte"
|
||||||
|
import ActionButton from "../../common/ActionButton.svelte"
|
||||||
|
import getIcon from "../../common/icon"
|
||||||
import { CloseIcon } from "components/common/Icons/"
|
import { CloseIcon } from "components/common/Icons/"
|
||||||
|
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
|
|
||||||
export let event = []
|
export let event
|
||||||
export let eventType
|
export let eventOptions = []
|
||||||
|
export let onClose
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
let eventType = ""
|
||||||
let draftEventHandler = { parameters: [] }
|
let draftEventHandler = { parameters: [] }
|
||||||
|
|
||||||
$: handlers = (event && [...event]) || []
|
$: eventData = event || { handlers: [] }
|
||||||
|
$: if (!eventOptions.includes(eventType) && eventOptions.length > 0)
|
||||||
|
eventType = eventOptions[0].name
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
dispatch("close")
|
onClose()
|
||||||
draftEventHandler = { parameters: [] }
|
draftEventHandler = { parameters: [] }
|
||||||
handlers = []
|
eventData = { handlers: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateEventHandler = (updatedHandler, index) => {
|
const updateEventHandler = (updatedHandler, index) => {
|
||||||
handlers[index] = updatedHandler
|
eventData.handlers[index] = updatedHandler
|
||||||
dispatch("change", handlers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDraftEventHandler = updatedHandler => {
|
const updateDraftEventHandler = updatedHandler => {
|
||||||
|
@ -30,8 +36,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteEventHandler = index => {
|
const deleteEventHandler = index => {
|
||||||
handlers.splice(index, 1)
|
eventData.handlers.splice(index, 1)
|
||||||
dispatch("change", handlers)
|
eventData = eventData
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewEventHandler = handler => {
|
const createNewEventHandler = handler => {
|
||||||
|
@ -39,15 +45,37 @@
|
||||||
parameters: {},
|
parameters: {},
|
||||||
[EVENT_TYPE_MEMBER_NAME]: "",
|
[EVENT_TYPE_MEMBER_NAME]: "",
|
||||||
}
|
}
|
||||||
handlers.push(newHandler)
|
eventData.handlers.push(newHandler)
|
||||||
dispatch("change", handlers)
|
eventData = eventData
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteEvent = () => {
|
||||||
|
store.setComponentProp(eventType, [])
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveEventData = () => {
|
||||||
|
store.setComponentProp(eventType, eventData.handlers)
|
||||||
|
closeModal()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h3>{eventType} Event</h3>
|
<h3>
|
||||||
|
{eventData.name ? `${eventData.name} Event` : 'Create a New Component Event'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="event-options">
|
||||||
|
<div class="section">
|
||||||
|
<h4>Event Type</h4>
|
||||||
|
<Select bind:value={eventType}>
|
||||||
|
{#each eventOptions as option}
|
||||||
|
<option value={option.name}>{option.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
@ -61,16 +89,35 @@
|
||||||
}}
|
}}
|
||||||
handler={draftEventHandler} />
|
handler={draftEventHandler} />
|
||||||
</div>
|
</div>
|
||||||
{#each handlers as handler, index}
|
{#if eventData}
|
||||||
|
{#each eventData.handlers as handler, index}
|
||||||
<HandlerSelector
|
<HandlerSelector
|
||||||
{index}
|
{index}
|
||||||
onChanged={updateEventHandler}
|
onChanged={updateEventHandler}
|
||||||
onRemoved={() => deleteEventHandler(index)}
|
onRemoved={() => deleteEventHandler(index)}
|
||||||
{handler} />
|
{handler} />
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
{#if eventData.name}
|
||||||
|
<Button
|
||||||
|
outline
|
||||||
|
on:click={deleteEvent}
|
||||||
|
disabled={eventData.handlers.length === 0}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<div class="save">
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
on:click={saveEventData}
|
||||||
|
disabled={eventData.handlers.length === 0}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="close-button" on:click={closeModal}>
|
<div class="close-button" on:click={closeModal}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,7 +126,6 @@
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 600px;
|
|
||||||
}
|
}
|
||||||
.heading {
|
.heading {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
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 IconButton from "components/common/IconButton.svelte"
|
||||||
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
|
|
||||||
|
import { PencilIcon } from "components/common/Icons"
|
||||||
|
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
||||||
|
|
||||||
|
export const EVENT_TYPE = "event"
|
||||||
|
|
||||||
|
export let component
|
||||||
|
|
||||||
|
let events = []
|
||||||
|
let selectedEvent = null
|
||||||
|
|
||||||
|
$: {
|
||||||
|
events = Object.keys(component)
|
||||||
|
// TODO: use real events
|
||||||
|
.filter(propName => ["onChange", "onClick", "onLoad"].includes(propName))
|
||||||
|
.map(propName => ({
|
||||||
|
name: propName,
|
||||||
|
handlers: component[propName] || [],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle create app modal
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
const openModal = event => {
|
||||||
|
selectedEvent = event
|
||||||
|
open(
|
||||||
|
EventEditorModal,
|
||||||
|
{
|
||||||
|
eventOptions: events,
|
||||||
|
event: selectedEvent,
|
||||||
|
onClose: () => {
|
||||||
|
close()
|
||||||
|
selectedEvent = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeButton: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
styleContent: { padding: 0 },
|
||||||
|
closeOnOuterClick: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button class="newevent" on:click={() => openModal()}>
|
||||||
|
<i class="icon ri-add-circle-fill" />
|
||||||
|
Create New Event
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<form on:submit|preventDefault class="uk-form-stacked form-root">
|
||||||
|
{#each events as event, index}
|
||||||
|
{#if event.handlers.length > 0}
|
||||||
|
<div
|
||||||
|
class:selected={selectedEvent && selectedEvent.index === index}
|
||||||
|
class="handler-container budibase__nav-item"
|
||||||
|
on:click={() => openModal({ ...event, index })}>
|
||||||
|
<span class="event-name">{event.name}</span>
|
||||||
|
<span class="edit-text">EDIT</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
font-size: 10pt;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newevent {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid var(--grey-4);
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0px 0px 12px 0px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 2ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newevent:hover {
|
||||||
|
background: var(--grey-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-root {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handler-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
border: 2px solid #f9f9f9;
|
||||||
|
height: 80px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-name {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(22, 48, 87, 0.6);
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-text {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
align-self: end;
|
||||||
|
justify-self: end;
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgba(35, 65, 105, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
color: var(--blue);
|
||||||
|
background: var(--grey-1) !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Select } from "@budibase/bbui"
|
import { Button, Select } from "@budibase/bbui"
|
||||||
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
import StateBindingCascader from "./StateBindingCascader.svelte"
|
import StateBindingCascader from "./StateBindingCascader.svelte"
|
||||||
import { find, map, keys, reduce, keyBy } from "lodash/fp"
|
import { find, map, keys, reduce, keyBy } from "lodash/fp"
|
||||||
import { pipe } from "components/common/core"
|
import { pipe } from "components/common/core"
|
||||||
|
@ -93,7 +94,7 @@
|
||||||
{#if newHandler}
|
{#if newHandler}
|
||||||
<Button primary thin on:click={onCreate}>Add Action</Button>
|
<Button primary thin on:click={onCreate}>Add Action</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button secondary thin on:click={onRemoved}>Remove Action</Button>
|
<Button outline thin on:click={onRemoved}>Remove Action</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input } from "@budibase/bbui"
|
import { Input, Select } from "@budibase/bbui"
|
||||||
import IconButton from "components/common/IconButton.svelte"
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
import PlusButton from "components/common/PlusButton.svelte"
|
|
||||||
import Select from "components/common/Select.svelte"
|
|
||||||
import { find, map, keys, reduce, keyBy } from "lodash/fp"
|
import { find, map, keys, reduce, keyBy } from "lodash/fp"
|
||||||
import { pipe } from "components/common/core"
|
import { pipe } from "components/common/core"
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
<script>
|
|
||||||
import InputGroup from "../common/Inputs/InputGroup.svelte"
|
|
||||||
import LayoutTemplateControls from "./LayoutTemplateControls.svelte"
|
|
||||||
|
|
||||||
export let onStyleChanged = () => {}
|
|
||||||
export let component
|
|
||||||
|
|
||||||
const tbrl = [
|
|
||||||
{ placeholder: "T" },
|
|
||||||
{ placeholder: "R" },
|
|
||||||
{ placeholder: "B" },
|
|
||||||
{ placeholder: "L" },
|
|
||||||
]
|
|
||||||
|
|
||||||
const se = [{ placeholder: "START" }, { placeholder: "END" }]
|
|
||||||
|
|
||||||
const single = [{ placeholder: "" }]
|
|
||||||
|
|
||||||
$: layout = {
|
|
||||||
...component._styles.position,
|
|
||||||
...component._styles.layout,
|
|
||||||
}
|
|
||||||
|
|
||||||
$: layouts = {
|
|
||||||
templaterows: ["Grid Rows", single],
|
|
||||||
templatecolumns: ["Grid Columns", single],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: display = {
|
|
||||||
direction: ["Direction", single],
|
|
||||||
align: ["Align", single],
|
|
||||||
justify: ["Justify", single],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: positions = {
|
|
||||||
column: ["Column", se],
|
|
||||||
row: ["Row", se],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: spacing = {
|
|
||||||
margin: ["Margin", tbrl, "small"],
|
|
||||||
padding: ["Padding", tbrl, "small"],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: size = {
|
|
||||||
height: ["Height", single],
|
|
||||||
width: ["Width", single],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: zindex = {
|
|
||||||
zindex: ["Z-Index", single],
|
|
||||||
}
|
|
||||||
|
|
||||||
const newValue = n => Array(n).fill("")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Layout</h3>
|
|
||||||
<div class="layout-pos">
|
|
||||||
{#each Object.entries(display) as [key, [name, meta, size]] (component._id + key)}
|
|
||||||
<div class="grid">
|
|
||||||
<h5>{name}:</h5>
|
|
||||||
<LayoutTemplateControls
|
|
||||||
onStyleChanged={_value => onStyleChanged('layout', key, _value)}
|
|
||||||
values={layout[key] || newValue(meta.length)}
|
|
||||||
propertyName={name}
|
|
||||||
{meta}
|
|
||||||
{size}
|
|
||||||
type="text" />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <h4>Positioning</h4>
|
|
||||||
<div class="layout-pos">
|
|
||||||
{#each Object.entries(positions) as [key, [name, meta, size]] (component._id + key)}
|
|
||||||
<div class="grid">
|
|
||||||
<h5>{name}:</h5>
|
|
||||||
<InputGroup
|
|
||||||
onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
|
||||||
values={layout[key] || newValue(meta.length)}
|
|
||||||
{meta}
|
|
||||||
{size} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<h3>Spacing</h3>
|
|
||||||
<div class="layout-spacing">
|
|
||||||
{#each Object.entries(spacing) as [key, [name, meta, size]] (component._id + key)}
|
|
||||||
<div class="grid">
|
|
||||||
<h5>{name}:</h5>
|
|
||||||
<InputGroup
|
|
||||||
onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
|
||||||
values={layout[key] || newValue(meta.length)}
|
|
||||||
{meta}
|
|
||||||
{size}
|
|
||||||
type="text" />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Size</h3>
|
|
||||||
<div class="layout-layer">
|
|
||||||
{#each Object.entries(size) as [key, [name, meta, size]] (component._id + key)}
|
|
||||||
<div class="grid">
|
|
||||||
<h5>{name}:</h5>
|
|
||||||
<InputGroup
|
|
||||||
onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
|
||||||
values={layout[key] || newValue(meta.length)}
|
|
||||||
type="text"
|
|
||||||
{meta}
|
|
||||||
{size} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Order</h3>
|
|
||||||
<div class="layout-layer">
|
|
||||||
{#each Object.entries(zindex) as [key, [name, meta, size]] (component._id + key)}
|
|
||||||
<div class="grid">
|
|
||||||
<h5>{name}:</h5>
|
|
||||||
<InputGroup
|
|
||||||
onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
|
||||||
values={layout[key] || newValue(meta.length)}
|
|
||||||
{meta}
|
|
||||||
{size} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h3 {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ink);
|
|
||||||
opacity: 0.6;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ink);
|
|
||||||
opacity: 0.4;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--ink);
|
|
||||||
opacity: 0.8;
|
|
||||||
padding-top: 13px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div > div {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
grid-gap: 10px;
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
grid-template-columns: 70px 2fr;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import PlusButton from "../common/PlusButton.svelte"
|
|
||||||
|
|
||||||
export let meta = []
|
export let meta = []
|
||||||
export let size = ""
|
export let size = ""
|
||||||
export let values = []
|
export let values = []
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin block-field">
|
<Select thin secondary wide on:change {value}>
|
||||||
<div class="uk-form-controls">
|
|
||||||
<select class="budibase__input" on:change {value}>
|
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={model._id}>{model.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import PropsView from "./PropsView.svelte"
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
import Button from "components/common/Button.svelte"
|
import Button from "components/common/Button.svelte"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
||||||
|
@ -10,6 +8,7 @@
|
||||||
import UIkit from "uikit"
|
import UIkit from "uikit"
|
||||||
import { isRootComponent } from "./pagesParsing/searchComponents"
|
import { isRootComponent } from "./pagesParsing/searchComponents"
|
||||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
||||||
|
import { Input, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
import { find, filter, some, map, includes } from "lodash/fp"
|
import { find, filter, some, map, includes } from "lodash/fp"
|
||||||
import { assign } from "lodash"
|
import { assign } from "lodash"
|
||||||
|
@ -23,8 +22,7 @@
|
||||||
let layoutComponent
|
let layoutComponent
|
||||||
let screens
|
let screens
|
||||||
let name = ""
|
let name = ""
|
||||||
|
let routeError
|
||||||
let saveAttempted = false
|
|
||||||
|
|
||||||
$: layoutComponents = Object.values($store.components).filter(
|
$: layoutComponents = Object.values($store.components).filter(
|
||||||
componentDefinition => componentDefinition.container
|
componentDefinition => componentDefinition.container
|
||||||
|
@ -39,18 +37,21 @@
|
||||||
$: route = !route && $store.screens.length === 0 ? "*" : route
|
$: route = !route && $store.screens.length === 0 ? "*" : route
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
saveAttempted = true
|
if (!route) {
|
||||||
|
routeError = "Url is required"
|
||||||
|
} else {
|
||||||
|
if (routeNameExists(route)) {
|
||||||
|
routeError = "This url is already taken"
|
||||||
|
} else {
|
||||||
|
routeError = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isValid =
|
if (routeError) return false
|
||||||
name.length > 0 &&
|
|
||||||
!screenNameExists(name) &&
|
|
||||||
route.length > 0 &&
|
|
||||||
!routeNameExists(route) &&
|
|
||||||
layoutComponent
|
|
||||||
|
|
||||||
if (!isValid) return
|
|
||||||
|
|
||||||
store.createScreen(name, route, layoutComponent._component)
|
store.createScreen(name, route, layoutComponent._component)
|
||||||
|
name = ""
|
||||||
|
route = ""
|
||||||
dialog.hide()
|
dialog.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,12 +59,6 @@
|
||||||
dialog.hide()
|
dialog.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const screenNameExists = name => {
|
|
||||||
return $store.screens.some(
|
|
||||||
screen => screen.name.toLowerCase() === name.toLowerCase()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeNameExists = route => {
|
||||||
return $store.screens.some(
|
return $store.screens.some(
|
||||||
screen => screen.route.toLowerCase() === route.toLowerCase()
|
screen => screen.route.toLowerCase() === route.toLowerCase()
|
||||||
|
@ -84,72 +79,27 @@
|
||||||
onOk={save}
|
onOk={save}
|
||||||
okText="Create Screen">
|
okText="Create Screen">
|
||||||
|
|
||||||
<div class="uk-form-horizontal">
|
<div data-cy="new-screen-dialog">
|
||||||
<div class="uk-margin">
|
<div class="bb-margin-xl">
|
||||||
<label class="uk-form-label">Name</label>
|
<Input label="Name" bind:value={name} />
|
||||||
<div class="uk-form-controls">
|
|
||||||
<input
|
|
||||||
class="uk-input uk-form-small"
|
|
||||||
class:uk-form-danger={saveAttempted && (name.length === 0 || screenNameExists(name))}
|
|
||||||
bind:value={name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="bb-margin-xl">
|
||||||
<label class="uk-form-label">Route (URL)</label>
|
<Input
|
||||||
<div class="uk-form-controls">
|
label="Url"
|
||||||
<input
|
error={routeError}
|
||||||
class="uk-input uk-form-small"
|
|
||||||
class:uk-form-danger={saveAttempted && (route.length === 0 || routeNameExists(route))}
|
|
||||||
bind:value={route}
|
bind:value={route}
|
||||||
on:change={routeChanged} />
|
on:change={routeChanged} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="bb-margin-xl">
|
||||||
<label class="uk-form-label">Layout Component</label>
|
<label>Layout Component</label>
|
||||||
<div class="uk-form-controls">
|
<Select bind:value={layoutComponent} secondary>
|
||||||
<select
|
|
||||||
class="uk-select uk-form-small"
|
|
||||||
bind:value={layoutComponent}
|
|
||||||
class:uk-form-danger={saveAttempted && !layoutComponent}>
|
|
||||||
{#each layoutComponents as { _component, name }}
|
{#each layoutComponents as { _component, name }}
|
||||||
<option value={_component}>{name}</option>
|
<option value={_component}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<style>
|
|
||||||
.uk-margin {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uk-form-controls {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uk-form-label {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--grey-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uk-input {
|
|
||||||
height: 40px !important;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uk-select {
|
|
||||||
height: 40px !important;
|
|
||||||
font-weight: 500px;
|
|
||||||
color: var(--grey-5);
|
|
||||||
border: 1px solid var(--grey-2);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
export let value = ""
|
|
||||||
export let onChange = value => {}
|
|
||||||
export let options = []
|
|
||||||
export let initialValue = ""
|
|
||||||
export let styleBindingProperty = ""
|
|
||||||
|
|
||||||
const handleStyleBind = value =>
|
|
||||||
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
|
|
||||||
|
|
||||||
$: isOptionsObject = options.every(o => typeof o === "object")
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (!value && !!initialValue) {
|
|
||||||
value = initialValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<select {value} on:change={ev => onChange(ev.target.value)}>
|
|
||||||
{#if isOptionsObject}
|
|
||||||
{#each options as { value, label }}
|
|
||||||
<option {...handleStyleBind(value || label)} value={value || label}>
|
|
||||||
{label}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
{#each options as value}
|
|
||||||
<option {...handleStyleBind(value)} {value}>{value}</option>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</select>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<script>
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
import Dropdown from "components/common/Dropdown.svelte"
|
|
||||||
import Button from "components/common/Button.svelte"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { isRootComponent } from "./pagesParsing/searchComponents"
|
|
||||||
import { pipe } from "components/common/core"
|
|
||||||
import { filter, find, concat } from "lodash/fp"
|
|
||||||
|
|
||||||
const notSelectedComponent = { name: "(none selected)" }
|
|
||||||
|
|
||||||
$: page = $store.pages[$store.currentPageName]
|
|
||||||
$: title = page.index.title
|
|
||||||
$: components = pipe($store.components, [
|
|
||||||
filter(store => !isRootComponent($store)),
|
|
||||||
concat([notSelectedComponent]),
|
|
||||||
])
|
|
||||||
$: entryComponent = components[page.appBody] || notSelectedComponent
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
if (!title || !entryComponent || entryComponent === notSeletedComponent)
|
|
||||||
return
|
|
||||||
const page = {
|
|
||||||
index: {
|
|
||||||
title,
|
|
||||||
},
|
|
||||||
appBody: entryComponent.name,
|
|
||||||
}
|
|
||||||
store.savePage(page)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
|
|
||||||
<h3>{$store.currentPageName}</h3>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault class="uk-form-horizontal">
|
|
||||||
<Textbox bind:text={title} label="Title" hasError={!title} />
|
|
||||||
<div class="help-text">
|
|
||||||
The title of your page, displayed in the bowser tab
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
label="App Entry Component"
|
|
||||||
options={components}
|
|
||||||
bind:selected={entryComponent}
|
|
||||||
textMember={v => v.name} />
|
|
||||||
|
|
||||||
<div class="help-text">
|
|
||||||
The component that will be loaded into the body of the page
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 20px" />
|
|
||||||
<Button on:click={save}>Save</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
.help-text {
|
|
||||||
color: var(--grey-2);
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import Checkbox from "../common/Checkbox.svelte"
|
|
||||||
import Textbox from "../common/Textbox.svelte"
|
|
||||||
import Dropdown from "../common/Dropdown.svelte"
|
|
||||||
import StateBindingControl from "./StateBindingControl.svelte"
|
|
||||||
|
|
||||||
export let index
|
|
||||||
export let prop_name
|
|
||||||
export let prop_value
|
|
||||||
export let prop_definition = {}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
{#if prop_definition.type !== 'event'}
|
|
||||||
<h5>{prop_name}</h5>
|
|
||||||
<StateBindingControl
|
|
||||||
value={prop_value}
|
|
||||||
type={prop_definition.type || prop_definition}
|
|
||||||
options={prop_definition.options}
|
|
||||||
styleBindingProperty={prop_definition.styleBindingProperty}
|
|
||||||
onChanged={v => store.setComponentProp(prop_name, v)} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
grid-template-columns: 70px 1fr;
|
|
||||||
grid-gap: 10px;
|
|
||||||
align-items: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--ink);
|
|
||||||
opacity: 0.8;
|
|
||||||
padding-top: 13px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import Input from "components/common/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
|
@ -69,17 +69,14 @@
|
||||||
getBindableProperties()
|
getBindableProperties()
|
||||||
let temp = value
|
let temp = value
|
||||||
const boundValues = (value && value.match(/{{([^}]+)}}/g)) || []
|
const boundValues = (value && value.match(/{{([^}]+)}}/g)) || []
|
||||||
console.log(boundValues)
|
|
||||||
|
|
||||||
// Replace with names:
|
// Replace with names:
|
||||||
boundValues.forEach(v => {
|
boundValues.forEach(v => {
|
||||||
const { readableBinding } = bindableProperties.find(
|
const binding = bindableProperties.find(({ runtimeBinding }) => {
|
||||||
({ runtimeBinding }) => {
|
|
||||||
return v === `{{ ${runtimeBinding} }}`
|
return v === `{{ ${runtimeBinding} }}`
|
||||||
}
|
})
|
||||||
)
|
if (binding) {
|
||||||
if (readableBinding) {
|
temp = temp.replace(v, `{{ ${binding.readableBinding} }}`)
|
||||||
temp = temp.replace(v, `{{ ${readableBinding} }}`)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// console.log(temp)
|
// console.log(temp)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
This file exists because of how we pass this control to the
|
||||||
|
properties panel - via a JS reference, not using svelte tags
|
||||||
|
... checkout the use of Input in propertyCategories to see what i mean
|
||||||
|
*/
|
||||||
|
import { Input } from "@budibase/bbui"
|
||||||
|
export let name, value, placeholder, type
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Input {name} {value} {placeholder} {type} thin on:change />
|
|
@ -1,54 +0,0 @@
|
||||||
<script>
|
|
||||||
import { some, includes, filter } from "lodash/fp"
|
|
||||||
import Textbox from "../common/Textbox.svelte"
|
|
||||||
import Dropdown from "../common/Dropdown.svelte"
|
|
||||||
import PropControl from "./PropControl.svelte"
|
|
||||||
import IconButton from "../common/IconButton.svelte"
|
|
||||||
|
|
||||||
export let component
|
|
||||||
export let components
|
|
||||||
|
|
||||||
let errors = []
|
|
||||||
const props_to_ignore = ["_component", "_children", "_styles", "_code", "_id"]
|
|
||||||
|
|
||||||
$: componentDef = components[component._component]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
|
|
||||||
<form on:submit|preventDefault class="uk-form-stacked form-root">
|
|
||||||
{#if componentDef}
|
|
||||||
{#each Object.entries(componentDef.props) as [prop_name, prop_def], index}
|
|
||||||
{#if prop_def !== 'event'}
|
|
||||||
<div class="prop-container">
|
|
||||||
<PropControl
|
|
||||||
{prop_name}
|
|
||||||
prop_value={component[prop_name]}
|
|
||||||
prop_definition={prop_def}
|
|
||||||
{index}
|
|
||||||
disabled={false} />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
font-size: 10pt;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-root {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prop-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
import InputGroup from "../common/Inputs/InputGroup.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import Input from "../common/Input.svelte"
|
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { excludeProps } from "./propertyCategories.js"
|
import { excludeProps } from "./propertyCategories.js"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
<script>
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import IconButton from "../common/IconButton.svelte"
|
|
||||||
import Input from "../common/Input.svelte"
|
|
||||||
|
|
||||||
export let value = ""
|
|
||||||
export let onChanged = () => {}
|
|
||||||
export let type = ""
|
|
||||||
export let options = []
|
|
||||||
export let styleBindingProperty = ""
|
|
||||||
|
|
||||||
$: bindOptionToStyle = !!styleBindingProperty
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="unbound-container">
|
|
||||||
{#if type === 'bool'}
|
|
||||||
<div>
|
|
||||||
<IconButton
|
|
||||||
icon={value == true ? 'check-square' : 'square'}
|
|
||||||
size="19"
|
|
||||||
on:click={() => onChanged(!value)} />
|
|
||||||
</div>
|
|
||||||
{:else if type === 'models'}
|
|
||||||
<select
|
|
||||||
class="uk-select uk-form-small"
|
|
||||||
bind:value
|
|
||||||
on:change={() => {
|
|
||||||
onChanged(value)
|
|
||||||
}}>
|
|
||||||
{#each $backendUiStore.models || [] as option}
|
|
||||||
<option value={option}>{option.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{:else if type === 'options' || type === 'models'}
|
|
||||||
<select
|
|
||||||
class="uk-select uk-form-small"
|
|
||||||
{value}
|
|
||||||
on:change={ev => onChanged(ev.target.value)}>
|
|
||||||
{#each options || [] as option}
|
|
||||||
{#if bindOptionToStyle}
|
|
||||||
<option style={`${styleBindingProperty}: ${option};`} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
{:else}
|
|
||||||
<option value={option}>{option}</option>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.unbound-container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bound-header > div:nth-child(1) {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
width: 30px;
|
|
||||||
color: var(--secondary50);
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -8,7 +8,6 @@
|
||||||
import NewScreen from "./NewScreen.svelte"
|
import NewScreen from "./NewScreen.svelte"
|
||||||
import CurrentItemPreview from "./CurrentItemPreview.svelte"
|
import CurrentItemPreview from "./CurrentItemPreview.svelte"
|
||||||
import SettingsView from "./SettingsView.svelte"
|
import SettingsView from "./SettingsView.svelte"
|
||||||
import PageView from "./PageView.svelte"
|
|
||||||
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { last } from "lodash/fp"
|
import { last } from "lodash/fp"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Input from "../common/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import OptionSelect from "./OptionSelect.svelte"
|
import OptionSelect from "./OptionSelect.svelte"
|
||||||
import FlatButtonGroup from "./FlatButtonGroup.svelte"
|
import FlatButtonGroup from "./FlatButtonGroup.svelte"
|
||||||
import Colorpicker from "@budibase/colorpicker"
|
import Colorpicker from "@budibase/colorpicker"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Input from "../common/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import OptionSelect from "./OptionSelect.svelte"
|
import OptionSelect from "./OptionSelect.svelte"
|
||||||
import Checkbox from "../common/Checkbox.svelte"
|
import Checkbox from "../common/Checkbox.svelte"
|
||||||
import ModelSelect from "components/userInterface/ModelSelect.svelte"
|
import ModelSelect from "components/userInterface/ModelSelect.svelte"
|
||||||
|
@ -308,7 +308,7 @@ export default {
|
||||||
label: "destinationUrl",
|
label: "destinationUrl",
|
||||||
key: "destinationUrl",
|
key: "destinationUrl",
|
||||||
control: Input,
|
control: Input,
|
||||||
placeholder: "/table/{{context._id}}",
|
placeholder: "/table/_id",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -480,44 +480,6 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
_component: "@budibase/materialdesign-components/BasicCard",
|
|
||||||
name: "Card",
|
|
||||||
description:
|
|
||||||
"A basic card component that can contain content and actions.",
|
|
||||||
icon: "ri-layout-bottom-line",
|
|
||||||
children: [],
|
|
||||||
properties: {
|
|
||||||
design: { ...all },
|
|
||||||
settings: [
|
|
||||||
{
|
|
||||||
label: "Heading",
|
|
||||||
key: "heading",
|
|
||||||
control: Input,
|
|
||||||
placeholder: "text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Subheading",
|
|
||||||
key: "subheading",
|
|
||||||
control: Input,
|
|
||||||
placeholder: "text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Content",
|
|
||||||
key: "content",
|
|
||||||
control: Input,
|
|
||||||
placeholder: "text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Image",
|
|
||||||
key: "imageUrl",
|
|
||||||
control: Input,
|
|
||||||
placeholder: "src",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Table",
|
name: "Table",
|
||||||
_component: "@budibase/standard-components/datatable",
|
_component: "@budibase/standard-components/datatable",
|
||||||
|
@ -566,11 +528,6 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
template: {
|
|
||||||
component: "@budibase/materialdesign-components/Form",
|
|
||||||
description: "Form for saving a record",
|
|
||||||
name: "@budibase/materialdesign-components/recordForm",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/dataformwide",
|
_component: "@budibase/standard-components/dataformwide",
|
||||||
|
@ -600,7 +557,12 @@ export default {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Donut Chart",
|
name: "Chart",
|
||||||
|
description: "Shiny chart",
|
||||||
|
icon: "ri-bar-chart-fill",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: "Donut",
|
||||||
_component: "@budibase/standard-components/donut",
|
_component: "@budibase/standard-components/donut",
|
||||||
description: "Donut chart",
|
description: "Donut chart",
|
||||||
icon: "ri-pie-chart-fill",
|
icon: "ri-pie-chart-fill",
|
||||||
|
@ -688,41 +650,368 @@ export default {
|
||||||
key: "legendWidth",
|
key: "legendWidth",
|
||||||
control: Input,
|
control: Input,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Legend Height",
|
name: "Bar",
|
||||||
key: "legendHeight",
|
_component: "@budibase/standard-components/bar",
|
||||||
|
description: "Bar chart",
|
||||||
|
icon: "ri-bar-chart-fill",
|
||||||
|
properties: {
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Table",
|
||||||
|
key: "model",
|
||||||
|
control: ModelSelect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Name Label",
|
||||||
|
key: "nameLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value Label",
|
||||||
|
key: "valueLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Y Axis Label",
|
||||||
|
key: "yAxisLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Label",
|
||||||
|
key: "xAxisLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Label Offset",
|
||||||
|
key: "xAxisLabelOffset",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Y Axis Label Offset",
|
||||||
|
key: "yAxisLabelOffset",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Enable Labels",
|
||||||
|
key: "enableLabels",
|
||||||
|
control: Checkbox,
|
||||||
|
valueKey: "checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Colors",
|
||||||
|
key: "color",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
{ label: "Normal", value: "britecharts" },
|
||||||
|
{ label: "Blue Green", value: "blueGreen" },
|
||||||
|
{ label: "Green", value: "green" },
|
||||||
|
{ label: "Grey", value: "grey" },
|
||||||
|
{ label: "Orange", value: "orange" },
|
||||||
|
{ label: "Pink", value: "pink" },
|
||||||
|
{ label: "Purple", value: "purple" },
|
||||||
|
{ label: "Red", value: "red" },
|
||||||
|
{ label: "Teal", value: "teal" },
|
||||||
|
{ label: "Yellow", value: "yellow" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Gradients",
|
||||||
|
key: "gradient",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "None" },
|
||||||
|
{ value: "bluePurple", label: "Blue Purple" },
|
||||||
|
{ value: "greenBlue", label: "Green Blue" },
|
||||||
|
{ value: "orangePink", label: "Orange Pink" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Highlight Single Bar",
|
||||||
|
key: "hasSingleBarHighlight",
|
||||||
|
control: Checkbox,
|
||||||
|
valueKey: "checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Width",
|
||||||
|
key: "width",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Height",
|
||||||
|
key: "height",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Animate",
|
||||||
|
key: "isAnimate",
|
||||||
|
control: Checkbox,
|
||||||
|
valueKey: "checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Horizontal",
|
||||||
|
key: "isHorizontal",
|
||||||
|
control: Checkbox,
|
||||||
|
valueKey: "checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Label Number Format",
|
||||||
|
key: "labelsNumberFormat",
|
||||||
control: Input,
|
control: Input,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Data List",
|
name: "Groupedbar",
|
||||||
_component: "@budibase/standard-components/datalist",
|
_component: "@budibase/standard-components/groupedbar",
|
||||||
description: "Shiny list",
|
description: "Groupedbar chart",
|
||||||
icon: "ri-file-list-fill",
|
icon: "ri-bar-chart-grouped-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
settings: [
|
||||||
settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
{
|
||||||
},
|
label: "Table",
|
||||||
children: [],
|
key: "model",
|
||||||
|
control: ModelSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "List",
|
label: "Color",
|
||||||
_component: "@budibase/standard-components/list",
|
key: "color",
|
||||||
description: "Renders all children once per record, of a given table",
|
control: OptionSelect,
|
||||||
icon: "ri-file-list-fill",
|
options: [
|
||||||
|
"britecharts",
|
||||||
|
"blueGreen",
|
||||||
|
"green",
|
||||||
|
"grey",
|
||||||
|
"orange",
|
||||||
|
"pink",
|
||||||
|
"purple",
|
||||||
|
"red",
|
||||||
|
"teal",
|
||||||
|
"yellow",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Height",
|
||||||
|
key: "height",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Width",
|
||||||
|
key: "width",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Aspect Ratio",
|
||||||
|
key: "aspectRatio",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grid",
|
||||||
|
key: "grid",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["vertical", "horizontal", "full"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Group Label",
|
||||||
|
key: "groupLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Name Label",
|
||||||
|
key: "nameLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value Label",
|
||||||
|
key: "valueLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Y Ticks",
|
||||||
|
key: "yTicks",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Y Tick Text Offset",
|
||||||
|
key: "yTickTextOffset",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Is Animated",
|
||||||
|
key: "isAnimated",
|
||||||
|
valueKey: "checked",
|
||||||
|
control: Checkbox,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Is Horizontal",
|
||||||
|
key: "isHorizontal",
|
||||||
|
valueKey: "checked",
|
||||||
|
control: Checkbox,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Tooltip Title",
|
||||||
|
key: "tooltipTitle",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line",
|
||||||
|
_component: "@budibase/standard-components/line",
|
||||||
|
description: "Line chart",
|
||||||
|
icon: "ri-line-chart-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
settings: [
|
||||||
settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/heading",
|
label: "Table",
|
||||||
name: "Headline",
|
key: "model",
|
||||||
description: "A component for displaying heading text",
|
control: ModelSelect,
|
||||||
icon: "ri-heading",
|
},
|
||||||
|
{
|
||||||
|
label: "Colors",
|
||||||
|
key: "color",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
"britecharts",
|
||||||
|
"blueGreen",
|
||||||
|
"green",
|
||||||
|
"grey",
|
||||||
|
"orange",
|
||||||
|
"pink",
|
||||||
|
"purple",
|
||||||
|
"red",
|
||||||
|
"teal",
|
||||||
|
"yellow",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Gradients",
|
||||||
|
key: "lineGradient",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "None" },
|
||||||
|
{ value: "bluePurple", label: "Blue Purple" },
|
||||||
|
{ value: "greenBlue", label: "Green Blue" },
|
||||||
|
{ value: "orangePink", label: "Orange Pink" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Line Curve",
|
||||||
|
key: "lineCurve",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
"linear",
|
||||||
|
"basis",
|
||||||
|
"natural",
|
||||||
|
"monotoneX",
|
||||||
|
"monotoneY",
|
||||||
|
"step",
|
||||||
|
"stepAfter",
|
||||||
|
"stepBefore",
|
||||||
|
"cardinal",
|
||||||
|
"catmullRom",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Value Type",
|
||||||
|
key: "xAxisValueType",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["date", "number"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grid",
|
||||||
|
key: "grid",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["vertical", "horizontal", "full"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Date Label",
|
||||||
|
key: "dateLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Topic Label",
|
||||||
|
key: "topicLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value Label",
|
||||||
|
key: "valueLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Label",
|
||||||
|
key: "xAxisLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Y Axis Label",
|
||||||
|
key: "yAxisLabel",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Show All Datapoints",
|
||||||
|
key: "shouldShowAllDataPoints",
|
||||||
|
valueKey: "checked",
|
||||||
|
control: Checkbox,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Width",
|
||||||
|
key: "width",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Height",
|
||||||
|
key: "height",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Is Animated",
|
||||||
|
key: "isAnimated",
|
||||||
|
control: Checkbox,
|
||||||
|
valueKey: "checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Locale",
|
||||||
|
key: "locale",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["en-GB", "en-US"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Value Type",
|
||||||
|
key: "xAxisValueType",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["date", "numeric"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Format",
|
||||||
|
key: "xAxisFormat",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
"day-month",
|
||||||
|
"minute-hour",
|
||||||
|
"hour-daymonth",
|
||||||
|
"month-year",
|
||||||
|
"custom",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X Axis Custom Format",
|
||||||
|
key: "xAxisCustomFormat",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Tooltip Title",
|
||||||
|
key: "tooltipTitle",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin block-field">
|
<div class="bb-margin-xl block-field">
|
||||||
<label class="uk-form-label">Page</label>
|
<label class="uk-form-label">Page</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<select class="budibase__input" bind:value={pageName}>
|
<select class="budibase__input" bind:value={pageName}>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
export let value
|
export let value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin block-field">
|
<div class="bb-margin-xl block-field">
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<select class="budibase__input" bind:value>
|
<select class="budibase__input" bind:value>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { Input } from "@budibase/bbui"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin block-field">
|
<div class="bb-margin-xl block-field">
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<select class="budibase__input" bind:value={value.model}>
|
<select class="budibase__input" bind:value={value.model}>
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
|
@ -15,12 +16,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if value.model}
|
{#if value.model}
|
||||||
<div class="uk-margin block-field">
|
<div class="bb-margin-xl block-field">
|
||||||
<label class="uk-form-label fields">Fields</label>
|
<label class="uk-form-label fields">Fields</label>
|
||||||
{#each Object.keys(value.model.schema) as field}
|
{#each Object.keys(value.model.schema) as field}
|
||||||
<div class="uk-form-controls uk-margin">
|
<div class="uk-form-controls bb-margin-xl">
|
||||||
<label class="uk-form-label">{field}</label>
|
<Input bind:value={value[field]} label={field} />
|
||||||
<input type="text" class="budibase__input" bind:value={value[field]} />
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
||||||
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button, Input } from "@budibase/bbui"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
{#if selectedTab === 'TEST'}
|
{#if selectedTab === 'TEST'}
|
||||||
<div class="uk-margin config-item">
|
<div class="bb-margin-m">
|
||||||
{#if testResult}
|
{#if testResult}
|
||||||
<button
|
<button
|
||||||
transition:fade
|
transition:fade
|
||||||
|
@ -111,18 +111,10 @@
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="block-label">Workflow: {workflow.name}</div>
|
<div class="block-label">Workflow: {workflow.name}</div>
|
||||||
<div class="config-item">
|
|
||||||
<label>Name</label>
|
|
||||||
<div class="form">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="budibase_input"
|
|
||||||
bind:value={workflow.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
<label class="uk-form-label">User Access</label>
|
<label class="uk-form-label">User Access</label>
|
||||||
<div class="access-levels">
|
<div class="access-levels">
|
||||||
|
|
||||||
{#each ACCESS_LEVELS as level}
|
{#each ACCESS_LEVELS as level}
|
||||||
<span class="access-level">
|
<span class="access-level">
|
||||||
<label>{level.name}</label>
|
<label>{level.name}</label>
|
||||||
|
@ -194,28 +186,12 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase_input {
|
|
||||||
height: 36px;
|
|
||||||
width: 244px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
border: 1px solid var(--grey-2);
|
|
||||||
text-align: left;
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header > span {
|
header > span {
|
||||||
color: var(--grey-5);
|
color: var(--grey-5);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -226,7 +202,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 260px;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
|
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
|
||||||
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
||||||
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
||||||
|
import { Input, TextArea, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
export let workflowBlock
|
export let workflowBlock
|
||||||
|
|
||||||
|
@ -18,42 +19,34 @@
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<label class="label">{parameter}</label>
|
<label class="label">{parameter}</label>
|
||||||
{#if Array.isArray(type)}
|
{#if Array.isArray(type)}
|
||||||
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
|
<Select bind:value={workflowBlock.args[parameter]} thin>
|
||||||
{#each type as option}
|
{#each type as option}
|
||||||
<option value={option}>{option}</option>
|
<option value={option}>{option}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
{:else if type === 'component'}
|
{:else if type === 'component'}
|
||||||
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
|
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'accessLevel'}
|
{:else if type === 'accessLevel'}
|
||||||
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
|
<Select bind:value={workflowBlock.args[parameter]} thin>
|
||||||
<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>
|
||||||
{:else if type === 'password'}
|
{:else if type === 'password'}
|
||||||
<input
|
<Input type="password" thin bind:value={workflowBlock.args[parameter]} />
|
||||||
type="password"
|
|
||||||
class="budibase_input"
|
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
|
||||||
{:else if type === 'number'}
|
{:else if type === 'number'}
|
||||||
<input
|
<Input type="number" thin bind:value={workflowBlock.args[parameter]} />
|
||||||
type="number"
|
|
||||||
class="budibase_input"
|
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
|
||||||
{:else if type === 'longText'}
|
{:else if type === 'longText'}
|
||||||
<textarea
|
<TextArea
|
||||||
type="text"
|
type="text"
|
||||||
class="budibase_input"
|
thin
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
bind:value={workflowBlock.args[parameter]}
|
||||||
|
label="" />
|
||||||
{:else if type === 'model'}
|
{:else if type === 'model'}
|
||||||
<ModelSelector bind:value={workflowBlock.args[parameter]} />
|
<ModelSelector bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'record'}
|
{:else if type === 'record'}
|
||||||
<RecordSelector bind:value={workflowBlock.args[parameter]} />
|
<RecordSelector bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'string'}
|
{:else if type === 'string'}
|
||||||
<input
|
<Input type="text" thin bind:value={workflowBlock.args[parameter]} />
|
||||||
type="text"
|
|
||||||
class="budibase_input"
|
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -62,17 +55,6 @@
|
||||||
.block-field {
|
.block-field {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
.budibase_input {
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
border: 1px solid var(--grey-2);
|
|
||||||
text-align: left;
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
padding-left: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { store, backendUiStore, workflowStore } from "builderStore"
|
import { store, backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
import { Input } from "@budibase/bbui"
|
||||||
|
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
|
@ -26,8 +27,7 @@
|
||||||
Create Workflow
|
Create Workflow
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div>
|
||||||
<label class="uk-form-label" for="form-stacked-text">Name</label>
|
<Input bind:value={name} label="Name" />
|
||||||
<input class="uk-input" type="text" bind:value={name} />
|
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<a href="https://docs.budibase.com">
|
<a href="https://docs.budibase.com">
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const FIELDS = {
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "number",
|
type: "number",
|
||||||
presence: { allowEmpty: true },
|
presence: { allowEmpty: true },
|
||||||
numericality: {},
|
numericality: { greaterThanOrEqualTo: "", lessThanOrEqualTo: "" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BOOLEAN: {
|
BOOLEAN: {
|
||||||
|
@ -37,16 +37,21 @@ export const FIELDS = {
|
||||||
// presence: { allowEmpty: true },
|
// presence: { allowEmpty: true },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
DATETIME: {
|
// DATETIME: {
|
||||||
name: "Date/Time",
|
// name: "Date/Time",
|
||||||
icon: "ri-calendar-event-fill",
|
// icon: "ri-calendar-event-fill",
|
||||||
type: "string",
|
// type: "string",
|
||||||
constraints: {
|
// value: "datetime",
|
||||||
type: "string",
|
// constraints: {
|
||||||
length: {},
|
// type: "string",
|
||||||
presence: { allowEmpty: true },
|
// length: {},
|
||||||
},
|
// presence: { allowEmpty: true },
|
||||||
},
|
// datetime: {
|
||||||
|
// latest: "",
|
||||||
|
// earliest: "",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
// IMAGE: {
|
// IMAGE: {
|
||||||
// name: "File",
|
// name: "File",
|
||||||
// icon: "ri-image-line",
|
// icon: "ri-image-line",
|
||||||
|
@ -75,123 +80,3 @@ export const FIELDS = {
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BLOCKS = {
|
|
||||||
NAME: {
|
|
||||||
name: "Name",
|
|
||||||
icon: "ri-text",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
COMPANY: {
|
|
||||||
name: "Company",
|
|
||||||
icon: "ri-store-line",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EMAIL: {
|
|
||||||
name: "Email",
|
|
||||||
icon: "ri-mail-line",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PHONE_NUMBER: {
|
|
||||||
name: "Phone No.",
|
|
||||||
icon: "ri-phone-line",
|
|
||||||
type: "number",
|
|
||||||
constraints: {
|
|
||||||
type: "number",
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
numericality: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VALUE: {
|
|
||||||
name: "Value",
|
|
||||||
icon: "ri-number-5",
|
|
||||||
type: "number",
|
|
||||||
constraints: {
|
|
||||||
type: "number",
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
numericality: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ACTIVE: {
|
|
||||||
name: "Active",
|
|
||||||
icon: "ri-toggle-line",
|
|
||||||
type: "boolean",
|
|
||||||
constraints: {
|
|
||||||
type: "boolean",
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
URL: {
|
|
||||||
name: "URL",
|
|
||||||
icon: "ri-link",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IMAGE: {
|
|
||||||
name: "Image URL",
|
|
||||||
icon: "ri-image-line",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// PRIORITY: {
|
|
||||||
// name: "Options",
|
|
||||||
// icon: "ri-list-check-2",
|
|
||||||
// type: "options",
|
|
||||||
// constraints: {
|
|
||||||
// type: "string",
|
|
||||||
// presence: { allowEmpty: true },
|
|
||||||
// inclusion: ["low", "medium", "high"],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
END_DATE: {
|
|
||||||
name: "End Date",
|
|
||||||
icon: "ri-calendar-event-fill",
|
|
||||||
type: "string",
|
|
||||||
constraints: {
|
|
||||||
type: "string",
|
|
||||||
length: {},
|
|
||||||
presence: { allowEmpty: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// AVATAR: {
|
|
||||||
// name: "Avatar",
|
|
||||||
// icon: "ri-image-line",
|
|
||||||
// type: "image",
|
|
||||||
// constraints: {
|
|
||||||
// type: "string",
|
|
||||||
// presence: { allowEmpty: true },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// PDF: {
|
|
||||||
// name: "PDF",
|
|
||||||
// icon: "ri-file-line",
|
|
||||||
// type: "file",
|
|
||||||
// constraints: {
|
|
||||||
// type: "string",
|
|
||||||
// presence: { allowEmpty: true },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
}
|
|
||||||
|
|
|
@ -95,3 +95,18 @@ textarea {
|
||||||
.hoverable:hover {
|
.hoverable:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bb-margin-m {
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
}
|
||||||
|
* + .bb-margin-m {
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bb-margin-xl {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
* + .bb-margin-xl {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
|
@ -11,17 +11,18 @@
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
|
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
|
||||||
<link rel='stylesheet' href='/_builder/global.css'>
|
<link rel='stylesheet' href='/_builder/global.css'>
|
||||||
<link rel='stylesheet' href='/_builder/codemirror.css'>
|
|
||||||
<link rel='stylesheet' href='/_builder/budibase.css'>
|
<link rel='stylesheet' href='/_builder/budibase.css'>
|
||||||
<link rel='stylesheet' href='/_builder/monokai.css'>
|
|
||||||
<link rel='stylesheet' href='/_builder/bundle.css'>
|
<link rel='stylesheet' href='/_builder/bundle.css'>
|
||||||
<link rel='stylesheet' href='/_builder/bbui.css'>
|
<link rel='stylesheet' href='/_builder/bbui.css'>
|
||||||
<link rel='stylesheet' href='/_builder/fonts.css'>
|
<link rel='stylesheet' href='/_builder/fonts.css'>
|
||||||
<link rel='stylesheet' href="/_builder/uikit.min.css">
|
<link rel='stylesheet' href="/_builder/uikit.min.css">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="app">
|
<body id="app">
|
||||||
|
|
||||||
<script src='/_builder/bundle.js'></script>
|
<script src='/_builder/bundle.js'></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,10 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import * as api from "components/database/ModelDataTable/api"
|
import * as api from "components/database/DataTable/api"
|
||||||
|
|
||||||
import ModelNavigator from "components/nav/ModelNavigator/ModelNavigator.svelte"
|
import ModelNavigator from "components/nav/ModelNavigator/ModelNavigator.svelte"
|
||||||
import { ModelSetupNav } from "components/nav/ModelSetupNav"
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
@ -24,12 +22,10 @@
|
||||||
background: var(--grey-1);
|
background: var(--grey-1);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin: 20px 40px;
|
margin: 20px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button } from "@budibase/bbui"
|
||||||
import ModelDataTable from "components/database/ModelDataTable"
|
import ModelDataTable from "components/database/DataTable"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import * as api from "components/database/ModelDataTable/api"
|
import * as api from "components/database/DataTable/api"
|
||||||
import { CreateEditRecordModal } from "components/database/ModelDataTable/modals"
|
import { CreateEditRecordModal } from "components/database/DataTable/modals"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
if ($params.selectedView) {
|
||||||
|
let view
|
||||||
|
const viewName = decodeURI($params.selectedView)
|
||||||
|
for (let model of $backendUiStore.models) {
|
||||||
|
if (model.views && model.views[viewName]) {
|
||||||
|
view = model.views[viewName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (view) {
|
||||||
|
backendUiStore.actions.views.select({
|
||||||
|
name: viewName,
|
||||||
|
...view,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue