Merge branch 'master' of github.com:Budibase/budibase into feature/self-hosting

This commit is contained in:
mike12345567 2020-12-22 16:24:34 +00:00
commit 207415a932
49 changed files with 420 additions and 250 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "0.3.8", "version": "0.4.2",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -115,7 +115,7 @@ Cypress.Commands.add("createUser", (email, password, role) => {
// Create User // Create User
cy.contains("Users").click() cy.contains("Users").click()
cy.contains("Create New Row").click() cy.contains("Create New User").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {
cy.get("input") cy.get("input")

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.3.8", "version": "0.4.2",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -63,8 +63,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.52.2", "@budibase/bbui": "^1.52.4",
"@budibase/client": "^0.3.8", "@budibase/client": "^0.4.2",
"@budibase/colorpicker": "^1.0.1", "@budibase/colorpicker": "^1.0.1",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"@fortawesome/fontawesome-free": "^5.14.0", "@fortawesome/fontawesome-free": "^5.14.0",

View File

@ -39,19 +39,22 @@ function identify(id) {
async function identifyByApiKey(apiKey) { async function identifyByApiKey(apiKey) {
if (!analyticsEnabled) return true if (!analyticsEnabled) return true
const response = await fetch( try {
`https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}` const response = await fetch(
) `https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}`
)
if (response.status === 200) {
const id = await response.json()
if (response.status === 200) { await api.put("/api/keys/userId", { value: id })
const id = await response.json() identify(id)
return true
}
await api.put("/api/keys/userId", { value: id }) return false
identify(id) } catch (error) {
return true console.log(error)
} }
return false
} }
function captureException(err) { function captureException(err) {

View File

@ -4,9 +4,9 @@ import { getAutomationStore } from "./store/automation"
import { getHostingStore } from "./store/hosting" import { getHostingStore } from "./store/hosting"
import { getThemeStore } from "./store/theme" import { getThemeStore } from "./store/theme"
import { derived } from "svelte/store" import { derived, writable } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
import { LAYOUT_NAMES } from "../constants" import { FrontendTypes, LAYOUT_NAMES } from "../constants"
import { makePropsSafe } from "components/userInterface/assetParsing/createProps" import { makePropsSafe } from "components/userInterface/assetParsing/createProps"
export const store = getFrontendStore() export const store = getFrontendStore()
@ -16,18 +16,12 @@ export const themeStore = getThemeStore()
export const hostingStore = getHostingStore() export const hostingStore = getHostingStore()
export const currentAsset = derived(store, $store => { export const currentAsset = derived(store, $store => {
const layout = $store.layouts const type = $store.currentFrontEndType
? $store.layouts.find(layout => layout._id === $store.currentAssetId) if (type === FrontendTypes.SCREEN) {
: null return $store.screens.find(screen => screen._id === $store.selectedScreenId)
} else if (type === FrontendTypes.LAYOUT) {
if (layout) return layout return $store.layouts.find(layout => layout._id === $store.selectedLayoutId)
}
const screen = $store.screens
? $store.screens.find(screen => screen._id === $store.currentAssetId)
: null
if (screen) return screen
return null return null
}) })
@ -62,8 +56,14 @@ export const selectedComponent = derived(
} }
) )
export const currentAssetName = derived(store, () => { export const currentAssetId = derived(store, $store => {
return currentAsset.name return $store.currentFrontEndType === FrontendTypes.SCREEN
? $store.selectedScreenId
: $store.selectedLayoutId
})
export const currentAssetName = derived(currentAsset, $currentAsset => {
return $currentAsset?.name
}) })
// leave this as before for consistency // leave this as before for consistency
@ -77,6 +77,8 @@ export const mainLayout = derived(store, $store => {
) )
}) })
export const selectedAccessRole = writable("BASIC")
export const initialise = async () => { export const initialise = async () => {
try { try {
await analytics.activate() await analytics.activate()

View File

@ -6,6 +6,7 @@ const INITIAL_BACKEND_UI_STATE = {
tables: [], tables: [],
views: [], views: [],
users: [], users: [],
roles: [],
selectedDatabase: {}, selectedDatabase: {},
selectedTable: {}, selectedTable: {},
draftTable: {}, draftTable: {},
@ -177,6 +178,26 @@ export const getBackendUiStore = () => {
return state return state
}), }),
}, },
roles: {
fetch: async () => {
const response = await api.get("/api/roles")
const roles = await response.json()
store.update(state => {
state.roles = roles
return state
})
},
delete: async role => {
const response = await api.delete(`/api/roles/${role._id}/${role._rev}`)
await store.actions.roles.fetch()
return response
},
save: async role => {
const response = await api.post("/api/roles", role)
await store.actions.roles.fetch()
return response
},
},
} }
return store return store

View File

@ -3,7 +3,6 @@ import { cloneDeep } from "lodash/fp"
import { import {
createProps, createProps,
getBuiltin, getBuiltin,
makePropsSafe,
} from "components/userInterface/assetParsing/createProps" } from "components/userInterface/assetParsing/createProps"
import { import {
allScreens, allScreens,
@ -12,6 +11,7 @@ import {
currentAsset, currentAsset,
mainLayout, mainLayout,
selectedComponent, selectedComponent,
selectedAccessRole,
} from "builderStore" } from "builderStore"
import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import api from "../api" import api from "../api"
@ -33,7 +33,8 @@ const INITIAL_FRONTEND_STATE = {
screens: [], screens: [],
components: [], components: [],
currentFrontEndType: "none", currentFrontEndType: "none",
currentAssetId: "", selectedScreenId: "",
selectedLayoutId: "",
selectedComponentId: "", selectedComponentId: "",
errors: [], errors: [],
hasAppPackage: false, hasAppPackage: false,
@ -85,25 +86,31 @@ export const getFrontendStore = () => {
}, },
}, },
screens: { screens: {
select: async screenId => { select: screenId => {
store.update(state => { store.update(state => {
const screen = get(allScreens).find(screen => screen._id === screenId) let screens = get(allScreens)
let screen =
screens.find(screen => screen._id === screenId) || screens[0]
if (!screen) return state if (!screen) return state
// Update role to the screen's role setting so that it will always
// be visible
selectedAccessRole.set(screen.routing.roleId)
state.currentFrontEndType = FrontendTypes.SCREEN state.currentFrontEndType = FrontendTypes.SCREEN
state.currentAssetId = screenId state.selectedScreenId = screen._id
state.currentView = "detail" state.currentView = "detail"
state.selectedComponentId = screen.props?._id state.selectedComponentId = screen.props?._id
return state return state
}) })
}, },
create: async screen => { create: async screen => {
screen = await store.actions.screens.save(screen) screen = await store.actions.screens.save(screen)
store.update(state => { store.update(state => {
state.currentAssetId = screen._id state.selectedScreenId = screen._id
state.selectedComponentId = screen.props._id state.selectedComponentId = screen.props._id
state.currentFrontEndType = FrontendTypes.SCREEN state.currentFrontEndType = FrontendTypes.SCREEN
selectedAccessRole.set(screen.routing.roleId)
return state return state
}) })
return screen return screen
@ -112,6 +119,7 @@ export const getFrontendStore = () => {
const creatingNewScreen = screen._id === undefined const creatingNewScreen = screen._id === undefined
const response = await api.post(`/api/screens`, screen) const response = await api.post(`/api/screens`, screen)
screen = await response.json() screen = await response.json()
await store.actions.routing.fetch()
store.update(state => { store.update(state => {
const foundScreen = state.screens.findIndex( const foundScreen = state.screens.findIndex(
@ -121,17 +129,13 @@ export const getFrontendStore = () => {
state.screens.splice(foundScreen, 1) state.screens.splice(foundScreen, 1)
} }
state.screens.push(screen) state.screens.push(screen)
if (creatingNewScreen) {
const safeProps = makePropsSafe(
state.components[screen.props._component],
screen.props
)
state.selectedComponentId = safeProps._id
screen.props = safeProps
}
return state return state
}) })
if (creatingNewScreen) {
store.actions.screens.select(screen._id)
}
return screen return screen
}, },
delete: async screens => { delete: async screens => {
@ -148,8 +152,8 @@ export const getFrontendStore = () => {
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}` `/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
) )
) )
if (screenToDelete._id === state.currentAssetId) { if (screenToDelete._id === state.selectedScreenId) {
state.currentAssetId = "" state.selectedScreenId = null
} }
} }
return state return state
@ -172,39 +176,42 @@ export const getFrontendStore = () => {
layouts: { layouts: {
select: layoutId => { select: layoutId => {
store.update(state => { store.update(state => {
const layout = store.actions.layouts.find(layoutId) const layout =
store.actions.layouts.find(layoutId) || get(store).layouts[0]
if (!layout) return
state.currentFrontEndType = FrontendTypes.LAYOUT state.currentFrontEndType = FrontendTypes.LAYOUT
state.currentView = "detail" state.currentView = "detail"
state.selectedLayoutId = layout._id
state.currentAssetId = layout._id
state.selectedComponentId = layout.props?._id state.selectedComponentId = layout.props?._id
return state return state
}) })
}, },
save: async layout => { save: async layout => {
const layoutToSave = cloneDeep(layout) const layoutToSave = cloneDeep(layout)
const creatingNewLayout = layoutToSave._id === undefined
const response = await api.post(`/api/layouts`, layoutToSave) const response = await api.post(`/api/layouts`, layoutToSave)
const savedLayout = await response.json()
const json = await response.json()
store.update(state => { store.update(state => {
const layoutIdx = state.layouts.findIndex( const layoutIdx = state.layouts.findIndex(
stateLayout => stateLayout._id === json._id stateLayout => stateLayout._id === savedLayout._id
) )
if (layoutIdx >= 0) { if (layoutIdx >= 0) {
// update existing layout // update existing layout
state.layouts.splice(layoutIdx, 1, json) state.layouts.splice(layoutIdx, 1, savedLayout)
} else { } else {
// save new layout // save new layout
state.layouts.push(json) state.layouts.push(savedLayout)
} }
state.currentAssetId = json._id
return state return state
}) })
// Select layout if creating a new one
if (creatingNewLayout) {
store.actions.layouts.select(savedLayout._id)
}
return savedLayout
}, },
find: layoutId => { find: layoutId => {
if (!layoutId) { if (!layoutId) {
@ -217,16 +224,17 @@ export const getFrontendStore = () => {
const response = await api.delete( const response = await api.delete(
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}` `/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
) )
if (response.status !== 200) { if (response.status !== 200) {
const json = await response.json() const json = await response.json()
throw new Error(json.message) throw new Error(json.message)
} }
store.update(state => { store.update(state => {
state.layouts = state.layouts.filter( state.layouts = state.layouts.filter(
layout => layout._id !== layoutToDelete._id layout => layout._id !== layoutToDelete._id
) )
if (layoutToDelete._id === state.selectedLayoutId) {
state.selectedLayoutId = get(mainLayout)._id
}
return state return state
}) })
}, },
@ -455,7 +463,6 @@ export const getFrontendStore = () => {
// Save layout // Save layout
nav._children = [...nav._children, newLink] nav._children = [...nav._children, newLink]
state.currentAssetId = layout._id
promises.push(store.actions.layouts.save(layout)) promises.push(store.actions.layouts.save(layout))
} }
return state return state

View File

@ -44,6 +44,7 @@
<CreateColumnButton /> <CreateColumnButton />
{#if schema && Object.keys(schema).length > 0} {#if schema && Object.keys(schema).length > 0}
<CreateRowButton <CreateRowButton
title={isUsersTable ? 'Create New User' : 'Create New Row'}
modalContentComponent={isUsersTable ? CreateEditUser : CreateEditRow} /> modalContentComponent={isUsersTable ? CreateEditUser : CreateEditRow} />
<CreateViewButton /> <CreateViewButton />
<ExportButton view={tableView} /> <ExportButton view={tableView} />

View File

@ -82,7 +82,7 @@
if (!allowEditing) { if (!allowEditing) {
return false return false
} }
return !(isUsersTable && ["email", "roleId"].indexOf(key) !== -1) return !(isUsersTable && ["email", "roleId"].includes(key))
} }
Object.entries(schema || {}).forEach(([key, value]) => { Object.entries(schema || {}).forEach(([key, value]) => {

View File

@ -3,6 +3,7 @@
import CreateEditRow from "../modals/CreateEditRow.svelte" import CreateEditRow from "../modals/CreateEditRow.svelte"
export let modalContentComponent = CreateEditRow export let modalContentComponent = CreateEditRow
export let title = "Create New Row"
let modal let modal
</script> </script>
@ -10,7 +11,7 @@
<div> <div>
<Button text small on:click={modal.show}> <Button text small on:click={modal.show}>
<Icon name="addrow" /> <Icon name="addrow" />
Create New Row {title}
</Button> </Button>
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>

View File

@ -1,42 +1,10 @@
<!-- Module scoped cache of saved role options -->
<script context="module">
import builderApi from "builderStore/api"
let cachedRoles
async function getRoles(force = false) {
if (cachedRoles && !force) {
return await cachedRoles
}
cachedRoles = new Promise(resolve => {
builderApi
.get("/api/roles")
.then(response => response.json())
.then(resolve)
})
return await cachedRoles
}
</script>
<script> <script>
import { backendUiStore } from "builderStore"
export let roleId export let roleId
let roleName $: role = $backendUiStore.roles.find(role => role._id === roleId)
$: getRole() $: roleName = role?.name ?? "Unknown role"
async function getRole() {
// Try to find a matching role
let roles = await getRoles()
let role = roles.find(role => role._id === roleId)
// If we didn't find a matching role, try updating the cached results
if (!role) {
let roles = await getRoles(true)
let role = roles.find(role => role._id === roleId)
}
role = roles.find(role => role._id === roleId)
roleName = role?.name ?? "Unknown role"
}
</script> </script>
<div>{roleName}</div> <div>{roleName}</div>

View File

@ -45,3 +45,9 @@
</div> </div>
{/each} {/each}
</ModalContent> </ModalContent>
<style>
div {
min-width: 0;
}
</style>

View File

@ -1,18 +1,14 @@
<script> <script>
import { onMount } from "svelte"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import RowFieldControl from "../RowFieldControl.svelte" import RowFieldControl from "../RowFieldControl.svelte"
import * as backendApi from "../api" import * as backendApi from "../api"
import builderApi from "builderStore/api"
import { ModalContent, Select } from "@budibase/bbui" import { ModalContent, Select } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
export let row = {} export let row = {}
let errors = [] let errors = []
let roles = []
let rolesLoaded = false
$: creating = row?._id == null $: creating = row?._id == null
$: table = row.tableId $: table = row.tableId
@ -56,14 +52,6 @@
notifier.success("User saved successfully.") notifier.success("User saved successfully.")
backendUiStore.actions.rows.save(rowResponse) backendUiStore.actions.rows.save(rowResponse)
} }
const fetchRoles = async () => {
const rolesResponse = await builderApi.get("/api/roles")
roles = await rolesResponse.json()
rolesLoaded = true
}
onMount(fetchRoles)
</script> </script>
<ModalContent <ModalContent
@ -80,19 +68,17 @@
bind:value={row.password} /> bind:value={row.password} />
<!-- Defer rendering this select until roles load, otherwise the initial <!-- Defer rendering this select until roles load, otherwise the initial
selection is always undefined --> selection is always undefined -->
{#if rolesLoaded} <Select
<Select thin
thin secondary
secondary label="Role"
label="Role" data-cy="roleId-select"
data-cy="roleId-select" bind:value={row.roleId}>
bind:value={row.roleId}> <option value="">Choose an option</option>
<option value="">Choose an option</option> {#each $backendUiStore.roles as role}
{#each roles as role} <option value={role._id}>{role.name}</option>
<option value={role._id}>{role.name}</option> {/each}
{/each} </Select>
</Select>
{/if}
{#each customSchemaKeys as [key, meta]} {#each customSchemaKeys as [key, meta]}
<RowFieldControl {meta} bind:value={row[key]} {creating} /> <RowFieldControl {meta} bind:value={row[key]} {creating} />
{/each} {/each}

View File

@ -4,27 +4,26 @@
import api from "builderStore/api" import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
import { backendUiStore } from "builderStore"
let roles = []
let permissions = [] let permissions = []
let selectedRole = {} let selectedRole = {}
let errors = [] let errors = []
$: selectedRoleId = selectedRole._id $: selectedRoleId = selectedRole._id
$: otherRoles = roles.filter(role => role._id !== selectedRoleId) $: otherRoles = $backendUiStore.roles.filter(
role => role._id !== selectedRoleId
)
$: isCreating = selectedRoleId == null || selectedRoleId === "" $: isCreating = selectedRoleId == null || selectedRoleId === ""
// Loads available roles and permissions from the server const fetchPermissions = async () => {
const fetchRoles = async () => {
const rolesResponse = await api.get("/api/roles")
roles = await rolesResponse.json()
const permissionsResponse = await api.get("/api/permissions") const permissionsResponse = await api.get("/api/permissions")
permissions = await permissionsResponse.json() permissions = await permissionsResponse.json()
} }
// Changes the seleced role // Changes the selected role
const changeRole = event => { const changeRole = event => {
const id = event?.target?.value const id = event?.target?.value
const role = roles.find(role => role._id === id) const role = $backendUiStore.roles.find(role => role._id === id)
if (role) { if (role) {
selectedRole = { selectedRole = {
...role, ...role,
@ -61,7 +60,7 @@
} }
// Save/create the role // Save/create the role
const response = await api.post("/api/roles", selectedRole) const response = await backendUiStore.actions.roles.save(selectedRole)
if (response.status === 200) { if (response.status === 200) {
notifier.success("Role saved successfully.") notifier.success("Role saved successfully.")
} else { } else {
@ -72,11 +71,8 @@
// Deletes the selected role // Deletes the selected role
const deleteRole = async () => { const deleteRole = async () => {
const response = await api.delete( const response = await backendUiStore.actions.roles.delete(selectedRole)
`/api/roles/${selectedRole._id}/${selectedRole._rev}`
)
if (response.status === 200) { if (response.status === 200) {
await fetchRoles()
changeRole() changeRole()
notifier.success("Role deleted successfully.") notifier.success("Role deleted successfully.")
} else { } else {
@ -84,7 +80,7 @@
} }
} }
onMount(fetchRoles) onMount(fetchPermissions)
</script> </script>
<ModalContent <ModalContent
@ -101,7 +97,7 @@
value={selectedRoleId} value={selectedRoleId}
on:change={changeRole}> on:change={changeRole}>
<option value="">Create new role</option> <option value="">Create new role</option>
{#each roles as role} {#each $backendUiStore.roles as role}
<option value={role._id}>{role.name}</option> <option value={role._id}>{role.name}</option>
{/each} {/each}
</Select> </Select>

View File

@ -82,7 +82,7 @@
files = fileArray files = fileArray
validateCSV() validateCSV()
}) })
reader.readAsBinaryString(fileArray[0]) reader.readAsText(fileArray[0])
} }
async function omitColumn(columnName) { async function omitColumn(columnName) {

View File

@ -61,7 +61,7 @@
} }
.nav-item:hover, .nav-item:hover,
.nav-item.selected { .nav-item.selected {
border-radius: var(--border-radius-m); border-radius: var(--border-radius-s);
} }
.content { .content {

View File

@ -4,7 +4,7 @@
import api from "builderStore/api" import api from "builderStore/api"
async function updateApplication(data) { async function updateApplication(data) {
const response = await api.put(`/api/${$store.appId}`, data) const response = await api.put(`/api/applications/${$store.appId}`, data)
const app = await response.json() const app = await response.json()
store.update(state => { store.update(state => {
state = { state = {

View File

@ -84,6 +84,7 @@
overflow: hidden; overflow: hidden;
margin: auto; margin: auto;
height: 100%; height: 100%;
background-color: white;
} }
.component-container iframe { .component-container iframe {
border: 0; border: 0;

View File

@ -1,6 +1,6 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, currentAsset } from "builderStore" import { store, currentAssetId } from "builderStore"
import { getComponentDefinition } from "builderStore/storeUtils" import { getComponentDefinition } from "builderStore/storeUtils"
import { DropEffect, DropPosition } from "./dragDropStore" import { DropEffect, DropPosition } from "./dragDropStore"
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte" import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
@ -22,7 +22,7 @@
const path = store.actions.components.findRoute(component) const path = store.actions.components.findRoute(component)
// Go to correct URL // Go to correct URL
$goto(`./${$store.currentAssetId}/${path}`) $goto(`./${$currentAssetId}/${path}`)
} }
const dragstart = component => e => { const dragstart = component => e => {

View File

@ -1,5 +1,4 @@
<script> <script>
import { writable } from "svelte/store"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, selectedComponent, currentAsset } from "builderStore" import { store, selectedComponent, currentAsset } from "builderStore"
import instantiateStore from "./dragDropStore" import instantiateStore from "./dragDropStore"
@ -20,6 +19,7 @@
export let route export let route
export let path export let path
export let indent export let indent
export let border
$: selectedScreen = $currentAsset $: selectedScreen = $currentAsset
@ -34,6 +34,7 @@
icon="ri-folder-line" icon="ri-folder-line"
text={path} text={path}
opened={true} opened={true}
{border}
withArrow={route.subpaths} /> withArrow={route.subpaths} />
{#each Object.entries(route.subpaths) as [url, subpath]} {#each Object.entries(route.subpaths) as [url, subpath]}
@ -41,8 +42,8 @@
<NavItem <NavItem
icon="ri-artboard-2-line" icon="ri-artboard-2-line"
indentLevel={indent || 1} indentLevel={indent || 1}
selected={$store.currentAssetId === screenId} selected={$store.selectedScreenId === screenId}
opened={$store.currentAssetId === screenId} opened={$store.selectedScreenId === screenId}
text={ROUTE_NAME_MAP[url]?.[role] || url} text={ROUTE_NAME_MAP[url]?.[role] || url}
withArrow={route.subpaths} withArrow={route.subpaths}
on:click={() => changeScreen(screenId)}> on:click={() => changeScreen(screenId)}>

View File

@ -1,12 +1,76 @@
<script> <script>
import { store } from "builderStore" import { store, selectedAccessRole } from "builderStore"
import PathTree from "./PathTree.svelte" import PathTree from "./PathTree.svelte"
$: paths = Object.keys($store.routes || {}).sort() let routes = {}
$: paths = Object.keys(routes || {}).sort()
$: {
const allRoutes = $store.routes
const sortedPaths = Object.keys(allRoutes || {}).sort()
const selectedRoleId = $selectedAccessRole
const selectedScreenId = $store.selectedScreenId
let found = false
let firstValidScreenId
let filteredRoutes = {}
let screenRoleId
// Filter all routes down to only those which match the current role
sortedPaths.forEach(path => {
const config = allRoutes[path]
Object.entries(config.subpaths).forEach(([subpath, pathConfig]) => {
Object.entries(pathConfig.screens).forEach(([roleId, screenId]) => {
if (screenId === selectedScreenId) {
screenRoleId = roleId
found = roleId === selectedRoleId
}
if (roleId === selectedRoleId) {
if (!firstValidScreenId) {
firstValidScreenId = screenId
}
if (!filteredRoutes[path]) {
filteredRoutes[path] = { subpaths: {} }
}
filteredRoutes[path].subpaths[subpath] = {
screens: {
[selectedRoleId]: screenId,
},
}
}
})
})
})
routes = filteredRoutes
// Select the correct role for the current screen ID
if (!found && screenRoleId) {
selectedAccessRole.set(screenRoleId)
}
// If the selected screen isn't in this filtered list, select the first one
else if (!found && firstValidScreenId) {
store.actions.screens.select(firstValidScreenId)
}
}
</script> </script>
<div class="root"> <div class="root">
{#each paths as path} {#each paths as path, idx}
<PathTree {path} route={$store.routes[path]} /> <PathTree border={idx > 0} {path} route={routes[path]} />
{/each} {/each}
{#if !paths.length}
<div class="empty">
There aren't any screens configured with this access role.
</div>
{/if}
</div> </div>
<style>
div.empty {
font-size: var(--font-size-xs);
color: var(--grey-5);
padding-top: var(--spacing-xs);
}
</style>

View File

@ -1,6 +1,11 @@
<script> <script>
import { goto, url } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, currentAssetName, selectedComponent } from "builderStore" import {
store,
currentAssetName,
selectedComponent,
currentAssetId,
} from "builderStore"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -27,7 +32,7 @@
const onComponentChosen = component => { const onComponentChosen = component => {
store.actions.components.create(component._component, component.presetProps) store.actions.components.create(component._component, component.presetProps)
const path = store.actions.components.findRoute($selectedComponent) const path = store.actions.components.findRoute($selectedComponent)
$goto(`./${$store.currentAssetId}/${path}`) $goto(`./${$currentAssetId}/${path}`)
close() close()
} }
</script> </script>

View File

@ -1,13 +1,19 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { goto, params, url } from "@sveltech/routify" import { goto, params, url } from "@sveltech/routify"
import { store, currentAsset, selectedComponent } from "builderStore" import {
store,
allScreens,
currentAsset,
backendUiStore,
selectedAccessRole,
} from "builderStore"
import { FrontendTypes } from "constants" import { FrontendTypes } from "constants"
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte" import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
import Layout from "components/userInterface/Layout.svelte" import Layout from "components/userInterface/Layout.svelte"
import NewScreenModal from "components/userInterface/NewScreenModal.svelte" import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
import NewLayoutModal from "components/userInterface/NewLayoutModal.svelte" import NewLayoutModal from "components/userInterface/NewLayoutModal.svelte"
import { Modal, Switcher } from "@budibase/bbui" import { Modal, Switcher, Select } from "@budibase/bbui"
const tabs = [ const tabs = [
{ {
@ -24,11 +30,38 @@
let routes = {} let routes = {}
let tab = $params.assetType let tab = $params.assetType
function navigate({ detail }) { const navigate = ({ detail }) => {
if (!detail) return if (!detail) {
return
}
$goto(`../${detail.heading.key}`) $goto(`../${detail.heading.key}`)
} }
const updateAccessRole = event => {
const role = event.target.value
// Select a valid screen with this new role - otherwise we'll not be
// able to change role at all because ComponentNavigationTree will kick us
// back the current role again because the same screen ID is still selected
const firstValidScreenId = $allScreens.find(
screen => screen.routing.roleId === role
)?._id
if (firstValidScreenId) {
store.actions.screens.select(firstValidScreenId)
}
// Otherwise clear the selected screen ID so that the first new valid screen
// can be selected by ComponentNavigationTree
else {
store.update(state => {
state.selectedScreenId = null
return state
})
}
selectedAccessRole.set(role)
}
onMount(() => { onMount(() => {
store.actions.routing.fetch() store.actions.routing.fetch()
}) })
@ -41,11 +74,21 @@
on:click={modal.show} on:click={modal.show}
data-cy="new-screen" data-cy="new-screen"
class="ri-add-circle-fill" /> class="ri-add-circle-fill" />
{#if $currentAsset} <div class="role-select">
<div class="nav-items-container"> <Select
<ComponentNavigationTree /> extraThin
</div> secondary
{/if} on:change={updateAccessRole}
value={$selectedAccessRole}
label="Filter by Access">
{#each $backendUiStore.roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>
</div>
<div class="nav-items-container">
<ComponentNavigationTree />
</div>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<NewScreenModal /> <NewScreenModal />
</Modal> </Modal>
@ -54,8 +97,8 @@
on:click={modal.show} on:click={modal.show}
data-cy="new-layout" data-cy="new-layout"
class="ri-add-circle-fill" /> class="ri-add-circle-fill" />
{#each $store.layouts as layout (layout._id)} {#each $store.layouts as layout, idx (layout._id)}
<Layout {layout} /> <Layout {layout} border={idx > 0} />
{/each} {/each}
<Modal bind:this={modal}> <Modal bind:this={modal}>
<NewLayoutModal /> <NewLayoutModal />
@ -82,4 +125,8 @@
cursor: pointer; cursor: pointer;
color: var(--blue); color: var(--blue);
} }
.role-select {
margin-bottom: var(--spacing-m);
}
</style> </style>

View File

@ -10,6 +10,7 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
export let layout export let layout
export let border
let confirmDeleteDialog let confirmDeleteDialog
let componentToDelete = "" let componentToDelete = ""
@ -23,17 +24,17 @@
</script> </script>
<NavItem <NavItem
border={false} {border}
icon="ri-layout-3-line" icon="ri-layout-3-line"
text={layout.name} text={layout.name}
withArrow withArrow
selected={$store.currentAssetId === layout._id} selected={$store.selectedLayoutId === layout._id}
opened={$store.currentAssetId === layout._id} opened={$store.selectedLayoutId === layout._id}
on:click={selectLayout}> on:click={selectLayout}>
<LayoutDropdownMenu {layout} /> <LayoutDropdownMenu {layout} />
</NavItem> </NavItem>
{#if $store.currentAssetId === layout._id && layout.props?._children} {#if $store.selectedLayoutId === layout._id && layout.props?._children}
<ComponentTree <ComponentTree
components={layout.props._children} components={layout.props._children}
currentComponent={$selectedComponent} currentComponent={$selectedComponent}

View File

@ -1,19 +1,15 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { store, backendUiStore, allScreens } from "builderStore" import { store } from "builderStore"
import { Input, ModalContent } from "@budibase/bbui" import { Input, ModalContent } from "@budibase/bbui"
import analytics from "analytics"
const CONTAINER = "@budibase/standard-components/container"
let name = "" let name = ""
async function save() { async function save() {
try { try {
await store.actions.layouts.save({ name }) const layout = await store.actions.layouts.save({ name })
$goto(`./${$store.currentAssetId}`) $goto(`./${layout._id}`)
notifier.success(`Layout ${name} created successfully`) notifier.success(`Layout ${name} created successfully`)
} catch (err) { } catch (err) {
notifier.danger(`Error creating layout ${name}.`) notifier.danger(`Error creating layout ${name}.`)

View File

@ -1,17 +1,11 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, backendUiStore, allScreens } from "builderStore" import { store, backendUiStore, allScreens } from "builderStore"
import { import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
Input,
Button,
Spacer,
Select,
ModalContent,
Toggle,
} from "@budibase/bbui"
import getTemplates from "builderStore/store/screenTemplates" import getTemplates from "builderStore/store/screenTemplates"
import { some } from "lodash/fp"
import analytics from "analytics" import analytics from "analytics"
import { onMount } from "svelte"
import api from "builderStore/api"
const CONTAINER = "@budibase/standard-components/container" const CONTAINER = "@budibase/standard-components/container"
@ -21,15 +15,13 @@
let templateIndex let templateIndex
let draftScreen let draftScreen
let createLink = true let createLink = true
let roleId = "BASIC"
$: templates = getTemplates($store, $backendUiStore.tables) $: templates = getTemplates($store, $backendUiStore.tables)
$: route = !route && $allScreens.length === 0 ? "*" : route $: route = !route && $allScreens.length === 0 ? "*" : route
$: baseComponents = Object.values($store.components) $: baseComponents = Object.values($store.components)
.filter(componentDefinition => componentDefinition.baseComponent) .filter(componentDefinition => componentDefinition.baseComponent)
.map(c => c._component) .map(c => c._component)
$: { $: {
if (templates && templateIndex === undefined) { if (templates && templateIndex === undefined) {
templateIndex = 0 templateIndex = 0
@ -56,10 +48,10 @@
const save = async () => { const save = async () => {
if (!route) { if (!route) {
routeError = "Url is required" routeError = "URL is required"
} else { } else {
if (routeNameExists(route)) { if (routeExists(route, roleId)) {
routeError = "This url is already taken" routeError = "This URL is already taken for this access role"
} else { } else {
routeError = "" routeError = ""
} }
@ -69,8 +61,7 @@
draftScreen.props._instanceName = name draftScreen.props._instanceName = name
draftScreen.props._component = baseComponent draftScreen.props._component = baseComponent
// TODO: need to fix this up correctly draftScreen.routing = { route, roleId }
draftScreen.routing = { route, roleId: "ADMIN" }
const createdScreen = await store.actions.screens.create(draftScreen) const createdScreen = await store.actions.screens.create(draftScreen)
if (createLink) { if (createLink) {
@ -85,12 +76,14 @@
}) })
} }
$goto(`./screen/${createdScreen._id}`) $goto(`./${createdScreen._id}`)
} }
const routeNameExists = route => { const routeExists = (route, roleId) => {
return $allScreens.some( return $allScreens.some(
screen => screen.routing.route.toLowerCase() === route.toLowerCase() screen =>
screen.routing.route.toLowerCase() === route.toLowerCase() &&
screen.routing.roleId === roleId
) )
} }
@ -113,14 +106,16 @@
{/each} {/each}
{/if} {/if}
</Select> </Select>
<Input label="Name" bind:value={name} /> <Input label="Name" bind:value={name} />
<Input <Input
label="Url" label="Url"
error={routeError} error={routeError}
bind:value={route} bind:value={route}
on:change={routeChanged} /> on:change={routeChanged} />
<Select label="Access" bind:value={roleId} secondary>
{#each $backendUiStore.roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>
<Toggle text="Create link in navigation bar" bind:checked={createLink} /> <Toggle text="Create link in navigation bar" bind:checked={createLink} />
</ModalContent> </ModalContent>

View File

@ -0,0 +1,15 @@
<script>
import { Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
export let value
let roles = []
</script>
<Select bind:value extraThin secondary on:change>
<option value="">Choose an option</option>
{#each $backendUiStore.roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -4,6 +4,7 @@
import { FrontendTypes } from "constants" import { FrontendTypes } from "constants"
import PropertyControl from "./PropertyControl.svelte" import PropertyControl from "./PropertyControl.svelte"
import LayoutSelect from "./LayoutSelect.svelte" import LayoutSelect from "./LayoutSelect.svelte"
import RoleSelect from "./RoleSelect.svelte"
import Input from "./PropertyPanelControls/Input.svelte" import Input from "./PropertyPanelControls/Input.svelte"
import { excludeProps } from "./propertyCategories.js" import { excludeProps } from "./propertyCategories.js"
import { store, allScreens, currentAsset } from "builderStore" import { store, allScreens, currentAsset } from "builderStore"
@ -36,8 +37,8 @@
const screenDefinition = [ const screenDefinition = [
{ key: "description", label: "Description", control: Input }, { key: "description", label: "Description", control: Input },
{ key: "routing.route", label: "Route", control: Input }, { key: "routing.route", label: "Route", control: Input },
{ key: "routing.roleId", label: "Access", control: RoleSelect },
{ key: "layoutId", label: "Layout", control: LayoutSelect }, { key: "layoutId", label: "Layout", control: LayoutSelect },
{ key: "routing.roleId", label: "Role", control: Input },
] ]
const layoutDefinition = [{ key: "title", label: "Title", control: Input }] const layoutDefinition = [{ key: "title", label: "Title", control: Input }]

View File

@ -20,6 +20,7 @@
backendUiStore.actions.reset() backendUiStore.actions.reset()
await store.actions.initialise(pkg) await store.actions.initialise(pkg)
await automationStore.actions.fetch() await automationStore.actions.fetch()
await backendUiStore.actions.roles.fetch()
return pkg return pkg
} else { } else {
throw new Error(pkg) throw new Error(pkg)
@ -217,5 +218,6 @@
position: absolute; position: absolute;
bottom: var(--spacing-m); bottom: var(--spacing-m);
left: var(--spacing-m); left: var(--spacing-m);
z-index: 1;
} }
</style> </style>

View File

@ -26,11 +26,12 @@
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it. // There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
if ($leftover) { if ($leftover) {
// Get the correct screen children. // Get the correct screen children.
const assetChildren = assetList.find( const assetChildren =
asset => assetList.find(
asset._id === $params.asset || asset =>
asset._id === decodeURIComponent($params.asset) asset._id === $params.asset ||
).props._children asset._id === decodeURIComponent($params.asset)
)?.props._children ?? []
findComponent(componentIds, assetChildren) findComponent(componentIds, assetChildren)
} }
// } // }

View File

@ -1,11 +1,10 @@
<script> <script>
import { store, backendUiStore } from "builderStore" import { store, backendUiStore, currentAsset } from "builderStore"
import { onMount } from "svelte" import { onMount } from "svelte"
import { FrontendTypes } from "constants" import { FrontendTypes } from "constants"
import CurrentItemPreview from "components/userInterface/AppPreview" import CurrentItemPreview from "components/userInterface/AppPreview"
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte" import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte" import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
import { last } from "lodash/fp"
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte" import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
$: instance = $store.appInstance $: instance = $store.appInstance
@ -36,7 +35,7 @@
</div> </div>
<div class="preview-pane"> <div class="preview-pane">
{#if $store.currentAssetId && $store.currentAssetId.length > 0} {#if $currentAsset}
<ComponentSelectionList /> <ComponentSelectionList />
<div class="preview-content"> <div class="preview-content">
<CurrentItemPreview /> <CurrentItemPreview />

View File

@ -5,12 +5,32 @@
// Go to first layout // Go to first layout
if ($params.assetType === FrontendTypes.LAYOUT) { if ($params.assetType === FrontendTypes.LAYOUT) {
$goto(`../${$store.layouts[0]?._id}`) // Try to use previously selected layout first
let id
if (
$store.selectedLayoutId &&
$store.layouts.find(layout => layout._id === $store.selectedLayoutId)
) {
id = $store.selectedLayoutId
} else {
id = $store.layouts[0]?._id
}
$goto(`../${id}`)
} }
// Go to first screen // Go to first screen
if ($params.assetType === FrontendTypes.SCREEN) { if ($params.assetType === FrontendTypes.SCREEN) {
$goto(`../${$allScreens[0]?._id}`) // Try to use previously selected layout first
let id
if (
$store.selectedScreenId &&
$allScreens.find(screen => screen._id === $store.selectedScreenId)
) {
id = $store.selectedScreenId
} else {
id = $allScreens[0]?._id
}
$goto(`../${id}`)
} }
</script> </script>

View File

@ -842,10 +842,10 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/bbui@^1.52.2": "@budibase/bbui@^1.52.4":
version "1.52.2" version "1.52.4"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.2.tgz#a0774880fb755eb81c762bc355550af7f4562b09" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.4.tgz#ae3c17e1f49f14e65831703958bcddc6e64afd24"
integrity sha512-PxiN5xvr+Z/RpypMDYh3lNhCUnejH1moMoWW7PiuCiho5VXGauR+M8T49p5eTKoFSqRMC7BUdFJJ9ye/cnQxNA== integrity sha512-/wiv5dSyvXLgy2/zGEslnCsjwE8qqng1D8k5ScSOPEyMab8tzzd1XxfZAN9rp84zIMgAXeH6s5a4j4riR+jVkg==
dependencies: dependencies:
markdown-it "^12.0.2" markdown-it "^12.0.2"
quill "^1.3.7" quill "^1.3.7"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.3.8", "version": "0.4.2",
"license": "MPL-2.0", "license": "MPL-2.0",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
@ -15,7 +15,7 @@
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@budibase/standard-components": "^0.3.8", "@budibase/standard-components": "^0.4.2",
"@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-node-resolve": "^10.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
@ -23,8 +23,8 @@
"rollup": "^2.33.2", "rollup": "^2.33.2",
"rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-terser": "^4.0.4", "rollup-plugin-terser": "^4.0.4",
"svelte": "^3.30.0", "svelte": "^3.30.0",
"svelte-jester": "^1.0.6" "svelte-jester": "^1.0.6"

View File

@ -3,7 +3,7 @@
import { setContext, onMount } from "svelte" import { setContext, onMount } from "svelte"
import Component from "./Component.svelte" import Component from "./Component.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { createDataStore, routeStore, screenStore } from "../store" import { createDataStore, initialise, screenStore } from "../store"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -14,13 +14,11 @@
// Load app config // Load app config
onMount(async () => { onMount(async () => {
await routeStore.actions.fetchRoutes() await initialise()
await screenStore.actions.fetchScreens()
loaded = true loaded = true
}) })
</script> </script>
{#if loaded && $screenStore.activeLayout} {#if loaded && $screenStore.activeLayout}
<!-- // TODO: need to get the active screen as well -->
<Component definition={$screenStore.activeLayout.props} /> <Component definition={$screenStore.activeLayout.props} />
{/if} {/if}

View File

@ -7,7 +7,15 @@
const { styleable } = getContext("sdk") const { styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
$: routerConfig = getRouterConfig($routeStore.routes) // Only wrap this as an array to take advantage of svelte keying,
// to ensure the svelte-spa-router is fully remounted when route config
// changes
$: configs = [
{
routes: getRouterConfig($routeStore.routes),
id: $routeStore.routeSessionId,
},
]
const getRouterConfig = routes => { const getRouterConfig = routes => {
let config = {} let config = {}
@ -25,11 +33,11 @@
} }
</script> </script>
{#if routerConfig} {#each configs as config (config.id)}
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>
<Router on:routeLoading={onRouteLoading} routes={routerConfig} /> <Router on:routeLoading={onRouteLoading} routes={config.routes} />
</div> </div>
{/if} {/each}
<style> <style>
div { div {

View File

@ -1,18 +1,29 @@
import * as API from "../api" import * as API from "../api"
import { getAppId } from "../utils/getAppId" import { getAppId } from "../utils/getAppId"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { initialise } from "./initialise"
import { routeStore } from "./routes"
const createAuthStore = () => { const createAuthStore = () => {
const store = writable("") const store = writable("")
const goToDefaultRoute = () => {
// Setting the active route forces an update of the active screen ID,
// even if we're on the same URL
routeStore.actions.setActiveRoute("/")
// Navigating updates the URL to reflect this route
routeStore.actions.navigate("/")
}
const logIn = async ({ email, password }) => { const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password }) const user = await API.logIn({ email, password })
if (!user.error) { if (!user.error) {
store.set(user.token) store.set(user.token)
location.reload() await initialise()
goToDefaultRoute()
} }
} }
const logOut = () => { const logOut = async () => {
store.set("") store.set("")
const appId = getAppId() const appId = getAppId()
if (appId) { if (appId) {
@ -20,7 +31,8 @@ const createAuthStore = () => {
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;` window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
} }
} }
location.reload() await initialise()
goToDefaultRoute()
} }
return { return {

View File

@ -6,3 +6,6 @@ export { bindingStore } from "./binding"
// Data stores are layered and duplicated, so it is not a singleton // Data stores are layered and duplicated, so it is not a singleton
export { createDataStore, dataStore } from "./data" export { createDataStore, dataStore } from "./data"
// Initialises an app by loading screens and routes
export { initialise } from "./initialise"

View File

@ -0,0 +1,7 @@
import { routeStore } from "./routes"
import { screenStore } from "./screens"
export async function initialise() {
await routeStore.actions.fetchRoutes()
await screenStore.actions.fetchScreens()
}

View File

@ -7,6 +7,7 @@ const createRouteStore = () => {
routes: [], routes: [],
routeParams: {}, routeParams: {},
activeRoute: null, activeRoute: null,
routeSessionId: Math.random(),
} }
const store = writable(initialState) const store = writable(initialState)
@ -29,6 +30,7 @@ const createRouteStore = () => {
store.update(state => { store.update(state => {
state.routes = routes state.routes = routes
state.routeSessionId = Math.random()
return state return state
}) })
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.3.8", "version": "0.4.2",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/electron.js", "main": "src/electron.js",
"repository": { "repository": {
@ -21,7 +21,6 @@
"maintainer": "Budibase", "maintainer": "Budibase",
"icon": "./build/icons/", "icon": "./build/icons/",
"target": [ "target": [
"AppImage",
"deb" "deb"
], ],
"category": "Development" "category": "Development"
@ -49,7 +48,7 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/client": "^0.3.8", "@budibase/client": "^0.4.2",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sendgrid/mail": "^7.1.1", "@sendgrid/mail": "^7.1.1",
"@sentry/node": "^5.19.2", "@sentry/node": "^5.19.2",

View File

@ -5,7 +5,7 @@ Array [
Object { Object {
"Address": "5 Sesame Street", "Address": "5 Sesame Street",
"Age": 4324, "Age": 4324,
"Name": "Bert", "Name": "Bertå",
}, },
Object { Object {
"Address": "1 World Trade Center", "Address": "1 World Trade Center",

View File

@ -89,7 +89,7 @@ describe("CSV Parser", () => {
}) })
).toEqual([ ).toEqual([
{ {
Name: "Bert", Name: "Bertå",
}, },
{ {
Name: "Ernie", Name: "Ernie",

View File

@ -1,4 +1,4 @@
"Name","Age","Address" "Name","Age","Address"
"Bert","4324","5 Sesame Street" "Bertå","4324","5 Sesame Street"
"Ernie","34","1 World Trade Center" "Ernie","34","1 World Trade Center"
"Big Bird","23423","44 Second Avenue" "Big Bird","23423","44 Second Avenue"
1 Name Age Address
2 Bert Bertå 4324 5 Sesame Street
3 Ernie 34 1 World Trade Center
4 Big Bird 23423 44 Second Avenue

View File

@ -29,11 +29,11 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.3.8", "version": "0.4.2",
"license": "MIT", "license": "MIT",
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691", "gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.52.1", "@budibase/bbui": "^1.52.4",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"@fortawesome/fontawesome-free": "^5.14.0", "@fortawesome/fontawesome-free": "^5.14.0",
"apexcharts": "^3.22.1", "apexcharts": "^3.22.1",
@ -41,8 +41,8 @@
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"markdown-it": "^12.0.2", "markdown-it": "^12.0.2",
"quill": "^1.3.7", "quill": "^1.3.7",
"turndown": "^7.0.0",
"svelte-apexcharts": "^1.0.2", "svelte-apexcharts": "^1.0.2",
"svelte-flatpickr": "^3.1.0" "svelte-flatpickr": "^3.1.0",
"turndown": "^7.0.0"
} }
} }

View File

@ -11,7 +11,7 @@
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
</script> </script>
<a href={url} use:linkable {target} use:styleable={$component.styles}> <a href={url || '/'} use:linkable {target} use:styleable={$component.styles}>
{text} {text}
<slot /> <slot />
</a> </a>

View File

@ -6,8 +6,8 @@
export let logoUrl export let logoUrl
const logOut = () => { const logOut = async () => {
authStore.actions.logOut() await authStore.actions.logOut()
} }
</script> </script>

View File

@ -11,7 +11,9 @@
{#if options} {#if options}
<div use:chart={options} use:styleable={$component.styles} /> <div use:chart={options} use:styleable={$component.styles} />
{:else if options === false} {:else if options === false}
<div use:styleable={$component.styles}>Invalid chart options</div> <div use:styleable={$component.styles}>
Use the settings panel to build your chart -->
</div>
{/if} {/if}
<style> <style>

View File

@ -39,10 +39,10 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/bbui@^1.52.1": "@budibase/bbui@^1.52.4":
version "1.52.1" version "1.52.4"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.1.tgz#7f1612616205debeac0c65242b4856cce07e4cd0" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.4.tgz#ae3c17e1f49f14e65831703958bcddc6e64afd24"
integrity sha512-950HXR4Z8b0TgJH3Dt7gLgeHlgtBVMTtG+KkFTtID/zeiXohf60wr2cyAuttCZ3yb4rFRHC+SDXo2NOsKdenKw== integrity sha512-/wiv5dSyvXLgy2/zGEslnCsjwE8qqng1D8k5ScSOPEyMab8tzzd1XxfZAN9rp84zIMgAXeH6s5a4j4riR+jVkg==
dependencies: dependencies:
markdown-it "^12.0.2" markdown-it "^12.0.2"
quill "^1.3.7" quill "^1.3.7"