Merge branch 'develop' of github.com:Budibase/budibase into cheeks-fixes

This commit is contained in:
Andrew Kingston 2023-07-11 13:59:00 +01:00
commit 2dafdc1fd7
32 changed files with 393 additions and 407 deletions

View File

@ -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;
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "2.8.3-alpha.3", "version": "2.8.6-alpha.5",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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">

View File

@ -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;
} }

View File

@ -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>

View File

@ -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"

View File

@ -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} />

View File

@ -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>

View File

@ -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

View File

@ -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">LETS 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>

View File

@ -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

View File

@ -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
} }

View File

@ -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
}
} }
] ]
} }

View File

@ -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>

View File

@ -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 () => {

View File

@ -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,
}, },
}, },
{ {

View File

@ -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">

View File

@ -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>