Merge branch 'develop' of github.com:Budibase/budibase into cheeks-fixes
This commit is contained in:
commit
2dafdc1fd7
|
@ -22,6 +22,16 @@ server {
|
||||||
proxy_pass http://127.0.0.1:4001;
|
proxy_pass http://127.0.0.1:4001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /embed {
|
||||||
|
rewrite /embed/(.*) /app/$1 break;
|
||||||
|
proxy_pass http://127.0.0.1:4001;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header x-budibase-embed "true";
|
||||||
|
add_header x-budibase-embed "true";
|
||||||
|
add_header Content-Security-Policy "frame-ancestors *";
|
||||||
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
proxy_pass http://127.0.0.1:4001;
|
proxy_pass http://127.0.0.1:4001;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.3-alpha.3",
|
"version": "2.8.6-alpha.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -2,21 +2,13 @@
|
||||||
import { goto, url } from "@roxi/routify"
|
import { goto, url } from "@roxi/routify"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import {
|
import { Input, Label, ModalContent, Layout } from "@budibase/bbui"
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
ModalContent,
|
|
||||||
Toggle,
|
|
||||||
Divider,
|
|
||||||
Layout,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { datasources } from "stores/backend"
|
import { datasources } from "stores/backend"
|
||||||
import TableDataImport from "../TableDataImport.svelte"
|
import TableDataImport from "../TableDataImport.svelte"
|
||||||
import {
|
import {
|
||||||
BUDIBASE_INTERNAL_DB_ID,
|
BUDIBASE_INTERNAL_DB_ID,
|
||||||
BUDIBASE_DATASOURCE_TYPE,
|
BUDIBASE_DATASOURCE_TYPE,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { buildAutoColumn, getAutoColumnInformation } from "builderStore/utils"
|
|
||||||
|
|
||||||
$: tableNames = $tables.list.map(table => table.name)
|
$: tableNames = $tables.list.map(table => table.name)
|
||||||
$: selectedSource = $datasources.list.find(
|
$: selectedSource = $datasources.list.find(
|
||||||
|
@ -43,28 +35,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = ""
|
let error = ""
|
||||||
let autoColumns = getAutoColumnInformation()
|
|
||||||
let schema = {}
|
let schema = {}
|
||||||
let rows = []
|
let rows = []
|
||||||
let allValid = true
|
let allValid = true
|
||||||
let displayColumn = null
|
let displayColumn = null
|
||||||
|
|
||||||
function getAutoColumns() {
|
|
||||||
const selectedAutoColumns = {}
|
|
||||||
|
|
||||||
Object.entries(autoColumns).forEach(([subtype, column]) => {
|
|
||||||
if (column.enabled) {
|
|
||||||
selectedAutoColumns[column.name] = buildAutoColumn(
|
|
||||||
name,
|
|
||||||
column.name,
|
|
||||||
subtype
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return selectedAutoColumns
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValid(evt) {
|
function checkValid(evt) {
|
||||||
const tableName = evt.target.value
|
const tableName = evt.target.value
|
||||||
if (tableNames.includes(tableName)) {
|
if (tableNames.includes(tableName)) {
|
||||||
|
@ -77,7 +53,7 @@
|
||||||
async function saveTable() {
|
async function saveTable() {
|
||||||
let newTable = {
|
let newTable = {
|
||||||
name,
|
name,
|
||||||
schema: { ...schema, ...getAutoColumns() },
|
schema: { ...schema },
|
||||||
rows,
|
rows,
|
||||||
type: "internal",
|
type: "internal",
|
||||||
sourceId: targetDatasourceId,
|
sourceId: targetDatasourceId,
|
||||||
|
@ -118,21 +94,6 @@
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
{error}
|
{error}
|
||||||
/>
|
/>
|
||||||
<div class="autocolumns">
|
|
||||||
<Label extraSmall grey>Auto Columns</Label>
|
|
||||||
<div class="toggles">
|
|
||||||
<div class="toggle-1">
|
|
||||||
<Toggle text="Created by" bind:value={autoColumns.createdBy.enabled} />
|
|
||||||
<Toggle text="Created at" bind:value={autoColumns.createdAt.enabled} />
|
|
||||||
<Toggle text="Auto ID" bind:value={autoColumns.autoID.enabled} />
|
|
||||||
</div>
|
|
||||||
<div class="toggle-2">
|
|
||||||
<Toggle text="Updated by" bind:value={autoColumns.updatedBy.enabled} />
|
|
||||||
<Toggle text="Updated at" bind:value={autoColumns.updatedAt.enabled} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Label grey extraSmall
|
<Label grey extraSmall
|
||||||
|
@ -148,24 +109,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
|
||||||
.autocolumns {
|
|
||||||
margin-bottom: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggles {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-1 :global(> *) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-2 :global(> *) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script>
|
||||||
|
import { Icon, Heading } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let showClose = false
|
||||||
|
export let onClose = () => {}
|
||||||
|
export let heading = ""
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="page">
|
||||||
|
<div class="closeButton">
|
||||||
|
{#if showClose}
|
||||||
|
<Icon hoverable name="Close" on:click={onClose} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="heading">
|
||||||
|
<Heading weight="light">{heading}</Heading>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
height: 38px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -208,9 +208,9 @@
|
||||||
<div class="syntax-error">
|
<div class="syntax-error">
|
||||||
Current Handlebars syntax is invalid, please check the
|
Current Handlebars syntax is invalid, please check the
|
||||||
guide
|
guide
|
||||||
<a href="https://handlebarsjs.com/guide/" target="_blank">
|
<a href="https://handlebarsjs.com/guide/" target="_blank"
|
||||||
here
|
>here</a
|
||||||
</a>
|
>
|
||||||
for more details.
|
for more details.
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
makeStateBinding,
|
makeStateBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { currentAsset, store } from "builderStore"
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
const flipDurationMs = 150
|
const flipDurationMs = 150
|
||||||
const EVENT_TYPE_KEY = "##eventHandlerType"
|
const EVENT_TYPE_KEY = "##eventHandlerType"
|
||||||
|
@ -29,6 +30,26 @@
|
||||||
let actionQuery
|
let actionQuery
|
||||||
let selectedAction = actions?.length ? actions[0] : null
|
let selectedAction = actions?.length ? actions[0] : null
|
||||||
|
|
||||||
|
const setUpdateActions = actions => {
|
||||||
|
return actions
|
||||||
|
? cloneDeep(actions)
|
||||||
|
.filter(action => {
|
||||||
|
return (
|
||||||
|
action[EVENT_TYPE_KEY] === "Update State" &&
|
||||||
|
action.parameters?.type === "set" &&
|
||||||
|
action.parameters.key
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.reduce((acc, action) => {
|
||||||
|
acc[action.id] = action
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot original action state
|
||||||
|
let updateStateActions = setUpdateActions(actions)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// Ensure parameters object is never null
|
// Ensure parameters object is never null
|
||||||
if (selectedAction && !selectedAction.parameters) {
|
if (selectedAction && !selectedAction.parameters) {
|
||||||
|
@ -125,8 +146,9 @@
|
||||||
actions = e.detail.items
|
actions = e.detail.items
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllBindings = (bindings, eventContextBindings, actions) => {
|
const getAllBindings = (actionBindings, eventContextBindings, actions) => {
|
||||||
let allBindings = []
|
let allBindings = []
|
||||||
|
let cloneActionBindings = cloneDeep(actionBindings)
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -144,11 +166,19 @@
|
||||||
.forEach(action => {
|
.forEach(action => {
|
||||||
// Check we have a binding for this action, and generate one if not
|
// Check we have a binding for this action, and generate one if not
|
||||||
const stateBinding = makeStateBinding(action.parameters.key)
|
const stateBinding = makeStateBinding(action.parameters.key)
|
||||||
const hasKey = bindings.some(binding => {
|
const hasKey = actionBindings.some(binding => {
|
||||||
return binding.runtimeBinding === stateBinding.runtimeBinding
|
return binding.runtimeBinding === stateBinding.runtimeBinding
|
||||||
})
|
})
|
||||||
if (!hasKey) {
|
if (!hasKey) {
|
||||||
bindings.push(stateBinding)
|
let existing = updateStateActions[action.id]
|
||||||
|
if (existing) {
|
||||||
|
const existingBinding = makeStateBinding(existing.parameters.key)
|
||||||
|
cloneActionBindings = cloneActionBindings.filter(
|
||||||
|
binding =>
|
||||||
|
binding.runtimeBinding !== existingBinding.runtimeBinding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
allBindings.push(stateBinding)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Get which indexes are asynchronous automations as we want to filter them out from the bindings
|
// Get which indexes are asynchronous automations as we want to filter them out from the bindings
|
||||||
|
@ -164,15 +194,16 @@
|
||||||
.filter(index => index !== undefined)
|
.filter(index => index !== undefined)
|
||||||
|
|
||||||
// Based on the above, filter out the asynchronous automations from the bindings
|
// Based on the above, filter out the asynchronous automations from the bindings
|
||||||
if (asynchronousAutomationIndexes) {
|
let contextBindings = asynchronousAutomationIndexes
|
||||||
allBindings = eventContextBindings
|
? eventContextBindings.filter((binding, index) => {
|
||||||
.filter((binding, index) => {
|
|
||||||
return !asynchronousAutomationIndexes.includes(index)
|
return !asynchronousAutomationIndexes.includes(index)
|
||||||
})
|
})
|
||||||
.concat(bindings)
|
: eventContextBindings
|
||||||
} else {
|
|
||||||
allBindings = eventContextBindings.concat(bindings)
|
allBindings = contextBindings
|
||||||
}
|
.concat(cloneActionBindings)
|
||||||
|
.concat(allBindings)
|
||||||
|
|
||||||
return allBindings
|
return allBindings
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -70,8 +70,9 @@
|
||||||
} set`
|
} set`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="action-count">{actionText}</div>
|
<div class="action-editor">
|
||||||
<ActionButton on:click={openDrawer}>Define actions</ActionButton>
|
<ActionButton on:click={openDrawer}>{actionText}</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={drawer} title={"Actions"}>
|
<Drawer bind:this={drawer} title={"Actions"}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
|
@ -89,9 +90,7 @@
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.action-count {
|
.action-editor :global(.spectrum-ActionButton) {
|
||||||
padding-top: 6px;
|
width: 100%;
|
||||||
padding-bottom: var(--spacing-s);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
let drawer
|
let drawer
|
||||||
let boundValue
|
let boundValue
|
||||||
|
|
||||||
|
$: text = getText(value)
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchema($currentAsset, datasource)
|
$: schema = getSchema($currentAsset, datasource)
|
||||||
$: options = allowCellEditing
|
$: options = allowCellEditing
|
||||||
|
@ -31,6 +32,17 @@
|
||||||
allowLinks: true,
|
allowLinks: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getText = value => {
|
||||||
|
if (!value?.length) {
|
||||||
|
return "All columns"
|
||||||
|
}
|
||||||
|
let text = `${value.length} column`
|
||||||
|
if (value.length !== 1) {
|
||||||
|
text += "s"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
const getSchema = (asset, datasource) => {
|
const getSchema = (asset, datasource) => {
|
||||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||||
|
|
||||||
|
@ -76,7 +88,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column-editor">
|
<div class="column-editor">
|
||||||
<ActionButton on:click={open}>Configure columns</ActionButton>
|
<ActionButton on:click={open}>{text}</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<Drawer bind:this={drawer} title="Columns">
|
<Drawer bind:this={drawer} title="Columns">
|
||||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
|
|
|
@ -12,25 +12,36 @@
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let value = []
|
export let value = []
|
||||||
|
|
||||||
const convertOldColumnFormat = oldColumns => {
|
|
||||||
if (typeof oldColumns?.[0] === "string") {
|
|
||||||
value = oldColumns.map(field => ({ name: field, displayName: field }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: convertOldColumnFormat(value)
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
let boundValue
|
let boundValue
|
||||||
|
|
||||||
|
$: text = getText(value)
|
||||||
|
$: convertOldColumnFormat(value)
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchema($currentAsset, datasource)
|
$: schema = getSchema($currentAsset, datasource)
|
||||||
$: options = Object.keys(schema || {})
|
$: options = Object.keys(schema || {})
|
||||||
$: sanitisedValue = getValidColumns(value, options)
|
$: sanitisedValue = getValidColumns(value, options)
|
||||||
$: updateBoundValue(sanitisedValue)
|
$: updateBoundValue(sanitisedValue)
|
||||||
|
|
||||||
|
const getText = value => {
|
||||||
|
if (!value?.length) {
|
||||||
|
return "All fields"
|
||||||
|
}
|
||||||
|
let text = `${value.length} field`
|
||||||
|
if (value.length !== 1) {
|
||||||
|
text += "s"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertOldColumnFormat = oldColumns => {
|
||||||
|
if (typeof oldColumns?.[0] === "string") {
|
||||||
|
value = oldColumns.map(field => ({ name: field, displayName: field }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getSchema = (asset, datasource) => {
|
const getSchema = (asset, datasource) => {
|
||||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||||
|
|
||||||
|
@ -75,7 +86,10 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton on:click={open}>Configure fields</ActionButton>
|
<div class="field-configuration">
|
||||||
|
<ActionButton on:click={open}>{text}</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={drawer} title="Form Fields">
|
<Drawer bind:this={drawer} title="Form Fields">
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Configure the fields in your form.
|
Configure the fields in your form.
|
||||||
|
@ -83,3 +97,9 @@
|
||||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
|
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field-configuration :global(.spectrum-ActionButton) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
import { createValidationStore } from "helpers/validation/yup"
|
import { createValidationStore } from "helpers/validation/yup"
|
||||||
import * as appValidation from "helpers/validation/yup/app"
|
import * as appValidation from "helpers/validation/yup/app"
|
||||||
import TemplateCard from "components/common/TemplateCard.svelte"
|
import TemplateCard from "components/common/TemplateCard.svelte"
|
||||||
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
|
|
||||||
import { Roles } from "constants/backend"
|
|
||||||
import { lowercase } from "helpers"
|
import { lowercase } from "helpers"
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
|
@ -142,21 +140,6 @@
|
||||||
// Create user
|
// Create user
|
||||||
await auth.setInitInfo({})
|
await auth.setInitInfo({})
|
||||||
|
|
||||||
// Create a default home screen if no template was selected
|
|
||||||
if (template == null) {
|
|
||||||
let defaultScreenTemplate = createFromScratchScreen.create()
|
|
||||||
defaultScreenTemplate.routing.route = "/home"
|
|
||||||
defaultScreenTemplate.routing.roldId = Roles.BASIC
|
|
||||||
try {
|
|
||||||
await store.actions.screens.save(defaultScreenTemplate)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Could not create a default application screen", err)
|
|
||||||
notifications.warning(
|
|
||||||
"Encountered an issue creating the default screen."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$goto(`/builder/app/${createdApp.instance._id}`)
|
$goto(`/builder/app/${createdApp.instance._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
creating = false
|
creating = false
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
import ImportQueriesModal from "./RestImportQueriesModal.svelte"
|
import ImportQueriesModal from "./RestImportQueriesModal.svelte"
|
||||||
|
|
||||||
|
export let datasourceId
|
||||||
let importQueriesModal
|
let importQueriesModal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={importQueriesModal}>
|
<Modal bind:this={importQueriesModal}>
|
||||||
<ImportQueriesModal createDatasource={false} datasourceId={"todo"} />
|
<ImportQueriesModal createDatasource={false} {datasourceId} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<div class="button">
|
<div class="button">
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
} from "stores/backend"
|
} from "stores/backend"
|
||||||
|
|
||||||
import { hasData } from "stores/selectors"
|
import { hasData } from "stores/selectors"
|
||||||
import { Icon, notifications, Heading, Body } from "@budibase/bbui"
|
import { notifications, Body } from "@budibase/bbui"
|
||||||
import { params, goto } from "@roxi/routify"
|
import { params, goto } from "@roxi/routify"
|
||||||
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
||||||
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
||||||
import DatasourceOption from "./_components/DatasourceOption.svelte"
|
import DatasourceOption from "./_components/DatasourceOption.svelte"
|
||||||
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||||
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
||||||
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
|
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
|
||||||
|
|
||||||
|
@ -46,16 +47,11 @@
|
||||||
bind:this={externalDatasourceModal}
|
bind:this={externalDatasourceModal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="page">
|
<CreationPage
|
||||||
<div class="closeButton">
|
showClose={hasData($datasources, $tables)}
|
||||||
{#if hasData($datasources, $tables)}
|
onClose={() => $goto("./table")}
|
||||||
<Icon hoverable name="Close" on:click={$goto("./table")} />
|
heading="Add new data source"
|
||||||
{/if}
|
>
|
||||||
</div>
|
|
||||||
<div class="heading">
|
|
||||||
<Heading weight="light">Add new data source</Heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="subHeading">
|
<div class="subHeading">
|
||||||
<Body>Get started with our Budibase DB</Body>
|
<Body>Get started with our Budibase DB</Body>
|
||||||
<div
|
<div
|
||||||
|
@ -113,30 +109,13 @@
|
||||||
</DatasourceOption>
|
</DatasourceOption>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CreationPage>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.page {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.closeButton {
|
|
||||||
height: 38px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subHeading {
|
.subHeading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 12px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
<script>
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
import { ModalContent, Body, Layout, Icon, Heading } from "@budibase/bbui"
|
|
||||||
import blankScreenPreview from "./blankScreenPreview.png"
|
|
||||||
import listScreenPreview from "./listScreenPreview.png"
|
|
||||||
|
|
||||||
export let onConfirm
|
|
||||||
export let onCancel
|
|
||||||
|
|
||||||
let listScreenModeKey = "autoCreate"
|
|
||||||
let blankScreenModeKey = "blankScreen"
|
|
||||||
|
|
||||||
let selectedScreenMode
|
|
||||||
|
|
||||||
const confirmScreenSelection = async () => {
|
|
||||||
await onConfirm(selectedScreenMode)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<ModalContent
|
|
||||||
title="Add screens"
|
|
||||||
confirmText="Continue"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onConfirm={confirmScreenSelection}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!selectedScreenMode}
|
|
||||||
size="M"
|
|
||||||
>
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
<div
|
|
||||||
class="screen-type item blankView"
|
|
||||||
class:selected={selectedScreenMode == blankScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = blankScreenModeKey
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="blank screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={blankScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">Blank screen</Heading>
|
|
||||||
<Body size="S">Add an empty blank screen</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == blankScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="listViewTitle">
|
|
||||||
<Heading size="XS">Quickly create a screen from your data</Heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="screen-type item"
|
|
||||||
class:selected={selectedScreenMode == listScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = listScreenModeKey
|
|
||||||
}}
|
|
||||||
class:disabled={!$tables.list.filter(table => table._id !== "ta_users")
|
|
||||||
.length}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="list screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={listScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">List view</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Create, edit and view your data in a list view screen with side
|
|
||||||
panel
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == listScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.screen-type-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.checkmark-spacing {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
letter-spacing: 0px;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
cursor: pointer;
|
|
||||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
|
||||||
background: var(--spectrum-alias-background-color-secondary);
|
|
||||||
transition: 0.3s all;
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
border-width: 1px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.item:hover,
|
|
||||||
.selected {
|
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text {
|
|
||||||
padding-left: var(--spectrum-alias-item-padding-xl);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text :global(h1) {
|
|
||||||
padding-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Icon) {
|
|
||||||
min-width: var(--spectrum-icon-size-m);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Heading) {
|
|
||||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
|
||||||
}
|
|
||||||
.preview {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listViewTitle {
|
|
||||||
margin-top: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blankView {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visible {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -9,7 +9,7 @@
|
||||||
Helpers,
|
Helpers,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
import { makeComponentUnique } from "builderStore/componentUtils"
|
import { makeComponentUnique } from "builderStore/componentUtils"
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
|
import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
import ScreenWizard from "./ScreenWizard.svelte"
|
|
||||||
import RoleIndicator from "./RoleIndicator.svelte"
|
import RoleIndicator from "./RoleIndicator.svelte"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let searchString
|
let searchString
|
||||||
let accessRole = "all"
|
let accessRole = "all"
|
||||||
let showNewScreenModal
|
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens(
|
$: filteredScreens = getFilteredScreens(
|
||||||
$sortedScreens,
|
$sortedScreens,
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
|
|
||||||
<Panel title="Screens" borderRight>
|
<Panel title="Screens" borderRight>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Button on:click={showNewScreenModal} cta>Add screen</Button>
|
<Button on:click={() => $goto("../../new")} cta>Add screen</Button>
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchString}
|
value={searchString}
|
||||||
|
@ -74,5 +73,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<ScreenWizard bind:showModal={showNewScreenModal} />
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
Before Width: | Height: | Size: 106 KiB |
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import NewScreenModal from "./NewScreenModal.svelte"
|
|
||||||
import DatasourceModal from "./DatasourceModal.svelte"
|
import DatasourceModal from "./DatasourceModal.svelte"
|
||||||
import ScreenRoleModal from "./ScreenRoleModal.svelte"
|
import ScreenRoleModal from "./ScreenRoleModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
|
@ -11,11 +10,11 @@
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { Roles } from "constants/backend"
|
import { Roles } from "constants/backend"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
let pendingScreen
|
let pendingScreen
|
||||||
|
|
||||||
// Modal refs
|
// Modal refs
|
||||||
let newScreenModal
|
|
||||||
let screenDetailsModal
|
let screenDetailsModal
|
||||||
let datasourceModal
|
let datasourceModal
|
||||||
let screenAccessRoleModal
|
let screenAccessRoleModal
|
||||||
|
@ -26,16 +25,6 @@
|
||||||
let blankScreenUrl = null
|
let blankScreenUrl = null
|
||||||
let screenMode = null
|
let screenMode = null
|
||||||
|
|
||||||
// External handler to show the screen wizard
|
|
||||||
export const showModal = () => {
|
|
||||||
selectedTemplates = null
|
|
||||||
blankScreenUrl = null
|
|
||||||
screenMode = null
|
|
||||||
pendingScreen = null
|
|
||||||
screenAccessRole = Roles.BASIC
|
|
||||||
newScreenModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an array of screens, checking and sanitising their URLs
|
// Creates an array of screens, checking and sanitising their URLs
|
||||||
const createScreens = async ({ screens, screenAccessRole }) => {
|
const createScreens = async ({ screens, screenAccessRole }) => {
|
||||||
if (!screens?.length) {
|
if (!screens?.length) {
|
||||||
|
@ -43,6 +32,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let screenId
|
||||||
|
|
||||||
for (let screen of screens) {
|
for (let screen of screens) {
|
||||||
// Check we aren't clashing with an existing URL
|
// Check we aren't clashing with an existing URL
|
||||||
if (hasExistingUrl(screen.routing.route)) {
|
if (hasExistingUrl(screen.routing.route)) {
|
||||||
|
@ -64,7 +55,8 @@
|
||||||
screen.routing.roleId = screenAccessRole
|
screen.routing.roleId = screenAccessRole
|
||||||
|
|
||||||
// Create the screen
|
// Create the screen
|
||||||
await store.actions.screens.save(screen)
|
const response = await store.actions.screens.save(screen)
|
||||||
|
screenId = response._id
|
||||||
|
|
||||||
// Add link in layout for list screens
|
// Add link in layout for list screens
|
||||||
if (screen.props._instanceName.endsWith("List")) {
|
if (screen.props._instanceName.endsWith("List")) {
|
||||||
|
@ -74,7 +66,10 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$goto(`./${screenId}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
notifications.error("Error creating screens")
|
notifications.error("Error creating screens")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,18 +99,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for NewScreenModal
|
// Handler for NewScreenModal
|
||||||
const confirmScreenSelection = async mode => {
|
export const show = mode => {
|
||||||
|
selectedTemplates = null
|
||||||
|
blankScreenUrl = null
|
||||||
screenMode = mode
|
screenMode = mode
|
||||||
|
pendingScreen = null
|
||||||
|
screenAccessRole = Roles.BASIC
|
||||||
|
|
||||||
if (mode === "autoCreate") {
|
if (mode === "table") {
|
||||||
datasourceModal.show()
|
datasourceModal.show()
|
||||||
} else {
|
} else if (mode === "blank") {
|
||||||
let templates = getTemplates($store, $tables.list)
|
let templates = getTemplates($store, $tables.list)
|
||||||
const blankScreenTemplate = templates.find(
|
const blankScreenTemplate = templates.find(
|
||||||
t => t.id === "createFromScratch"
|
t => t.id === "createFromScratch"
|
||||||
)
|
)
|
||||||
pendingScreen = blankScreenTemplate.create()
|
pendingScreen = blankScreenTemplate.create()
|
||||||
screenDetailsModal.show()
|
screenDetailsModal.show()
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid mode provided")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@
|
||||||
|
|
||||||
// Submit screen config for creation.
|
// Submit screen config for creation.
|
||||||
const confirmScreenCreation = async () => {
|
const confirmScreenCreation = async () => {
|
||||||
if (screenMode === "blankScreen") {
|
if (screenMode === "blank") {
|
||||||
confirmBlankScreenCreation({
|
confirmBlankScreenCreation({
|
||||||
screenUrl: blankScreenUrl,
|
screenUrl: blankScreenUrl,
|
||||||
screenAccessRole,
|
screenAccessRole,
|
||||||
|
@ -166,7 +167,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleSelectBack = () => {
|
const roleSelectBack = () => {
|
||||||
if (screenMode === "blankScreen") {
|
if (screenMode === "blank") {
|
||||||
screenDetailsModal.show()
|
screenDetailsModal.show()
|
||||||
} else {
|
} else {
|
||||||
datasourceModal.show()
|
datasourceModal.show()
|
||||||
|
@ -174,14 +175,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={newScreenModal}>
|
|
||||||
<NewScreenModal onConfirm={confirmScreenSelection} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={datasourceModal}>
|
<Modal bind:this={datasourceModal}>
|
||||||
<DatasourceModal
|
<DatasourceModal
|
||||||
onConfirm={confirmScreenDatasources}
|
onConfirm={confirmScreenDatasources}
|
||||||
onCancel={() => newScreenModal.show()}
|
|
||||||
initalScreens={!selectedTemplates ? [] : [...selectedTemplates]}
|
initalScreens={!selectedTemplates ? [] : [...selectedTemplates]}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -198,7 +194,6 @@
|
||||||
<Modal bind:this={screenDetailsModal}>
|
<Modal bind:this={screenDetailsModal}>
|
||||||
<ScreenDetailsModal
|
<ScreenDetailsModal
|
||||||
onConfirm={confirmScreenBlank}
|
onConfirm={confirmScreenBlank}
|
||||||
onCancel={() => newScreenModal.show()}
|
|
||||||
initialUrl={blankScreenUrl}
|
initialUrl={blankScreenUrl}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
|
@ -40,14 +40,14 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Autogenerated screens"
|
title="Access"
|
||||||
confirmText="Done"
|
confirmText="Done"
|
||||||
cancelText="Back"
|
cancelText="Back"
|
||||||
{onConfirm}
|
{onConfirm}
|
||||||
{onCancel}
|
{onCancel}
|
||||||
disabled={!!error}
|
disabled={!!error}
|
||||||
>
|
>
|
||||||
Select which level of access you want your screens to have
|
Select the level of access required to see these screens
|
||||||
<Select
|
<Select
|
||||||
bind:value={screenAccessRole}
|
bind:value={screenAccessRole}
|
||||||
on:change={onChangeRole}
|
on:change={onChangeRole}
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -1,67 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, selectedScreen } from "builderStore"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { Layout, Button, Detail, notifications } from "@budibase/bbui"
|
import { store as frontendStore } from "builderStore"
|
||||||
import Logo from "assets/bb-space-man.svg"
|
|
||||||
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
|
|
||||||
import { Roles } from "constants/backend"
|
|
||||||
|
|
||||||
let loaded = false
|
$: {
|
||||||
|
if ($frontendStore.screens.length > 0) {
|
||||||
const createFirstScreen = async () => {
|
$redirect(`./${$frontendStore.screens[0]._id}`)
|
||||||
let screen = createFromScratchScreen.create()
|
|
||||||
screen.routing.route = "/home"
|
|
||||||
screen.routing.roldId = Roles.BASIC
|
|
||||||
screen.routing.homeScreen = true
|
|
||||||
try {
|
|
||||||
const savedScreen = await store.actions.screens.save(screen)
|
|
||||||
notifications.success("Screen created successfully")
|
|
||||||
$redirect(`./${savedScreen._id}`)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Could not create screen", err)
|
|
||||||
notifications.error("Error creating screen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if ($selectedScreen) {
|
|
||||||
$redirect(`./${$selectedScreen._id}`)
|
|
||||||
} else if ($store.screens?.length) {
|
|
||||||
$redirect(`./${$store.screens[0]._id}`)
|
|
||||||
} else {
|
} else {
|
||||||
loaded = true
|
$redirect("./new")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
<div class="centered">
|
|
||||||
<Layout gap="S" justifyItems="center">
|
|
||||||
<img class="img-size" alt="logo" src={Logo} />
|
|
||||||
<div class="new-screen-text">
|
|
||||||
<Detail size="L">LET’S BRING THIS APP TO LIFE</Detail>
|
|
||||||
</div>
|
|
||||||
<Button on:click={createFirstScreen} size="M" cta icon="Add">
|
|
||||||
Create first screen
|
|
||||||
</Button>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.centered {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
.new-screen-text {
|
|
||||||
width: 150px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.img-size {
|
|
||||||
width: 170px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script>
|
||||||
|
import { Body } from "@budibase/bbui"
|
||||||
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
|
import blankImage from "./blank.png"
|
||||||
|
import tableImage from "./table.png"
|
||||||
|
import CreateScreenModal from "./_components/CreateScreenModal.svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
let createScreenModal
|
||||||
|
|
||||||
|
$: hasScreens = $store.screens?.length
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<CreationPage
|
||||||
|
showClose={$store.screens.length > 0}
|
||||||
|
onClose={() => $goto(`./${$store.screens[0]._id}`)}
|
||||||
|
heading={hasScreens ? "Create new screen" : "Create your first screen"}
|
||||||
|
>
|
||||||
|
<div class="subHeading">
|
||||||
|
<Body size="L">Start from scratch or create screens from your data</Body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
||||||
|
<div class="image">
|
||||||
|
<img alt="" src={blankImage} />
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<Body size="S">Blank screen</Body>
|
||||||
|
<Body size="XS">Add an empty blank screen</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" on:click={() => createScreenModal.show("table")}>
|
||||||
|
<div class="image">
|
||||||
|
<img alt="" src={tableImage} />
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<Body size="S">Table</Body>
|
||||||
|
<Body size="XS">View, edit and delete rows on a table</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CreationPage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CreateScreenModal bind:this={createScreenModal} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 28px 40px 40px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subHeading :global(p) {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 12px;
|
||||||
|
max-width: 235px;
|
||||||
|
transition: filter 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 127px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
border: 1px solid var(--grey-4);
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
padding: 8px 16px 13px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text :global(p:nth-child(1)) {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text :global(p:nth-child(2)) {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
</style>
|
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -7,8 +7,6 @@
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { auth, admin } from "stores/portal"
|
import { auth, admin } from "stores/portal"
|
||||||
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
|
|
||||||
import { Roles } from "constants/backend"
|
|
||||||
|
|
||||||
let name = "My first app"
|
let name = "My first app"
|
||||||
let url = "my-first-app"
|
let url = "my-first-app"
|
||||||
|
@ -38,11 +36,6 @@
|
||||||
// Create user
|
// Create user
|
||||||
await auth.setInitInfo({})
|
await auth.setInitInfo({})
|
||||||
|
|
||||||
let defaultScreenTemplate = createFromScratchScreen.create()
|
|
||||||
defaultScreenTemplate.routing.route = "/home"
|
|
||||||
defaultScreenTemplate.routing.roldId = Roles.BASIC
|
|
||||||
await store.actions.screens.save(defaultScreenTemplate)
|
|
||||||
|
|
||||||
appId = createdApp.instance._id
|
appId = createdApp.instance._id
|
||||||
return createdApp
|
return createdApp
|
||||||
}
|
}
|
||||||
|
|
|
@ -3223,6 +3223,46 @@
|
||||||
"key": "allowManualEntry",
|
"key": "allowManualEntry",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Play sound on scan",
|
||||||
|
"key": "beepOnScan",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sound pitch",
|
||||||
|
"key": "beepFrequency",
|
||||||
|
"dependsOn": "beepOnScan",
|
||||||
|
"defaultValue": 2637,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Low",
|
||||||
|
"value": 2096
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Regular",
|
||||||
|
"value": 2637
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "High",
|
||||||
|
"value": 3136
|
||||||
|
},
|
||||||
|
{ "label": "Custom", "value": "custom" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Sound frequency (Hz)",
|
||||||
|
"key": "customFrequency",
|
||||||
|
"defaultValue": 1046,
|
||||||
|
"min": 20,
|
||||||
|
"max": 8000,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "beepFrequency",
|
||||||
|
"value": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
|
@ -4541,6 +4581,16 @@
|
||||||
"setting": "clickBehaviour",
|
"setting": "clickBehaviour",
|
||||||
"value": "details"
|
"value": "details"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Hide notifications",
|
||||||
|
"key": "notificationOverride",
|
||||||
|
"defaultValue": false,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "clickBehaviour",
|
||||||
|
"value": "details"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -5178,6 +5228,16 @@
|
||||||
"value": "View",
|
"value": "View",
|
||||||
"invert": true
|
"invert": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Hide notifications",
|
||||||
|
"key": "notificationOverride",
|
||||||
|
"defaultValue": false,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "showSaveButton",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
export let sidePanelShowDelete
|
export let sidePanelShowDelete
|
||||||
export let sidePanelSaveLabel
|
export let sidePanelSaveLabel
|
||||||
export let sidePanelDeleteLabel
|
export let sidePanelDeleteLabel
|
||||||
|
export let notificationOverride
|
||||||
|
|
||||||
const { fetchDatasourceSchema, API } = getContext("sdk")
|
const { fetchDatasourceSchema, API } = getContext("sdk")
|
||||||
const stateKey = `ID_${generate()}`
|
const stateKey = `ID_${generate()}`
|
||||||
|
@ -253,6 +254,7 @@
|
||||||
fields: sidePanelFields || normalFields,
|
fields: sidePanelFields || normalFields,
|
||||||
title: editTitle,
|
title: editTitle,
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
|
notificationOverride,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
@ -277,6 +279,7 @@
|
||||||
fields: sidePanelFields || normalFields,
|
fields: sidePanelFields || normalFields,
|
||||||
title: "Create Row",
|
title: "Create Row",
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
|
notificationOverride,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
export let rowId
|
export let rowId
|
||||||
export let actionUrl
|
export let actionUrl
|
||||||
export let noRowsMessage
|
export let noRowsMessage
|
||||||
|
export let notificationOverride
|
||||||
|
|
||||||
const { fetchDatasourceSchema } = getContext("sdk")
|
const { fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
|
||||||
|
@ -87,6 +88,7 @@
|
||||||
showDeleteButton,
|
showDeleteButton,
|
||||||
schema,
|
schema,
|
||||||
repeaterId,
|
repeaterId,
|
||||||
|
notificationOverride,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchSchema = async () => {
|
const fetchSchema = async () => {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let showDeleteButton
|
export let showDeleteButton
|
||||||
export let schema
|
export let schema
|
||||||
export let repeaterId
|
export let repeaterId
|
||||||
|
export let notificationOverride
|
||||||
|
|
||||||
const FieldTypeToComponentMap = {
|
const FieldTypeToComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
parameters: {
|
parameters: {
|
||||||
providerId: formId,
|
providerId: formId,
|
||||||
tableId: dataSource?.tableId,
|
tableId: dataSource?.tableId,
|
||||||
|
notificationOverride,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let allowManualEntry = false
|
export let allowManualEntry = false
|
||||||
export let scanButtonText = "Scan code"
|
export let scanButtonText = "Scan code"
|
||||||
|
export let beepOnScan = false
|
||||||
|
export let beepFrequency = 2637
|
||||||
|
export let customFrequency = 1046
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let videoEle
|
let videoEle
|
||||||
|
@ -21,8 +25,13 @@
|
||||||
fps: 25,
|
fps: 25,
|
||||||
qrbox: { width: 250, height: 250 },
|
qrbox: { width: 250, height: 250 },
|
||||||
}
|
}
|
||||||
|
const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
|
||||||
|
|
||||||
const onScanSuccess = decodedText => {
|
const onScanSuccess = decodedText => {
|
||||||
if (value != decodedText) {
|
if (value != decodedText) {
|
||||||
|
if (beepOnScan) {
|
||||||
|
beep()
|
||||||
|
}
|
||||||
dispatch("change", decodedText)
|
dispatch("change", decodedText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +93,27 @@
|
||||||
}
|
}
|
||||||
camModal.hide()
|
camModal.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const beep = () => {
|
||||||
|
const oscillator = audioCtx.createOscillator()
|
||||||
|
const gainNode = audioCtx.createGain()
|
||||||
|
|
||||||
|
oscillator.connect(gainNode)
|
||||||
|
gainNode.connect(audioCtx.destination)
|
||||||
|
|
||||||
|
const frequency =
|
||||||
|
beepFrequency === "custom" ? customFrequency : beepFrequency
|
||||||
|
oscillator.frequency.value = frequency
|
||||||
|
oscillator.type = "square"
|
||||||
|
|
||||||
|
const duration = 420
|
||||||
|
const endTime = audioCtx.currentTime + duration / 1000
|
||||||
|
gainNode.gain.setValueAtTime(1, audioCtx.currentTime)
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.001, endTime)
|
||||||
|
|
||||||
|
oscillator.start()
|
||||||
|
oscillator.stop(endTime)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="scanner-video-wrapper">
|
<div class="scanner-video-wrapper">
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let allowManualEntry
|
export let allowManualEntry
|
||||||
export let scanButtonText
|
export let scanButtonText
|
||||||
|
export let beepOnScan
|
||||||
|
export let beepFrequency
|
||||||
|
export let customFrequency
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -42,6 +45,9 @@
|
||||||
disabled={fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
{allowManualEntry}
|
{allowManualEntry}
|
||||||
scanButtonText={scanText}
|
scanButtonText={scanText}
|
||||||
|
{beepOnScan}
|
||||||
|
{beepFrequency}
|
||||||
|
{customFrequency}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
Loading…
Reference in New Issue