Add lots of new work on client library rewrite

This commit is contained in:
Andrew Kingston 2020-11-17 12:08:24 +00:00
parent cbeea17db0
commit 94458b3907
42 changed files with 397 additions and 345 deletions

View File

@ -15,17 +15,17 @@
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@budibase/standard-components": "^0.3.8",
"@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"jsdom": "^16.0.1", "jsdom": "^16.0.1",
"rollup": "^2.11.2", "rollup": "^2.33.2",
"rollup-plugin-alias": "^2.2.0",
"rollup-plugin-commonjs": "^10.0.0",
"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-node-resolve": "^5.2.0",
"rollup-plugin-svelte": "^6.1.1", "rollup-plugin-svelte": "^6.1.1",
"svelte": "3.29.0", "svelte": "^3.29.0"
"svelte-jester": "^1.0.6"
}, },
"gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a" "gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a"
} }

View File

@ -1,9 +1,8 @@
import resolve from "rollup-plugin-node-resolve" import alias from "@rollup/plugin-alias"
import commonjs from "rollup-plugin-commonjs" import commonjs from "@rollup/plugin-commonjs"
import resolve from "@rollup/plugin-node-resolve"
import builtins from "rollup-plugin-node-builtins" import builtins from "rollup-plugin-node-builtins"
import nodeglobals from "rollup-plugin-node-globals"
import svelte from "rollup-plugin-svelte" import svelte from "rollup-plugin-svelte"
import alias from "rollup-plugin-alias"
import path from "path" import path from "path"
const production = !process.env.ROLLUP_WATCH const production = !process.env.ROLLUP_WATCH
@ -13,13 +12,7 @@ export default {
input: "src/index.js", input: "src/index.js",
output: [ output: [
{ {
sourcemap: true, file: "dist/budibase-client.js",
format: "iife",
name: "app",
file: `./dist/budibase-client.js`,
},
{
file: "dist/budibase-client.esm.mjs",
format: "esm", format: "esm",
sourcemap: "inline", sourcemap: "inline",
}, },
@ -45,7 +38,6 @@ export default {
}), }),
commonjs(), commonjs(),
builtins(), builtins(),
nodeglobals(),
], ],
watch: { watch: {
clearScreen: false, clearScreen: false,

View File

@ -2,11 +2,10 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import { componentStore } from "../store" import { componentStore } from "../store"
import Component from "./Component.svelte" import Component from "./Component.svelte"
import { getValidProps } from "../utils"
let frontendDefinition let frontendDefinition
let loaded = false let loaded = false
$: pageProps = frontendDefinition?.page?.props $: pageDefinition = frontendDefinition?.page?.props
onMount(async () => { onMount(async () => {
frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"] frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"]
@ -17,9 +16,5 @@
</script> </script>
{#if loaded} {#if loaded}
<Component <Component definition={pageDefinition} />
component={pageProps._component}
props={getValidProps(pageProps)}
children={pageProps._children}
styles={pageProps._styles.normal} />
{/if} {/if}

View File

@ -1,31 +1,35 @@
<script> <script>
import { componentStore } from "../store" import { componentStore } from "../store"
import { buildStyle, getValidProps } from "../utils" import { getValidProps } from "../utils"
export let props export let definition = {}
export let children
export let component
export let styles
$: componentConstructor = componentStore.actions.getComponent(component) $: componentProps = getValidProps(definition)
$: console.log("Rendering: " + component) $: children = definition._children
$: componentName = extractComponentName(definition._component)
$: constructor = componentStore.actions.getComponent(componentName)
$: id = `${componentName}-${definition._id}`
$: styles = { ...definition._styles, id }
// Extracts the actual component name from the library name
function extractComponentName(name) {
console.log(name)
if (name == null) {
console.log(definition)
}
const split = name.split("/")
return split[split.length - 1]
}
$: console.log("Rendering: " + componentName)
</script> </script>
{#if componentConstructor} {#if constructor}
<div <svelte:component this={constructor} {...componentProps} {styles}>
style={buildStyle(styles)}
data-bb-component={component}
data-bb-name={component._instanceName}>
<svelte:component this={componentConstructor} {...props}>
{#if children && children.length} {#if children && children.length}
{#each children as child} {#each children as child}
<svelte:self <svelte:self definition={child} />
props={getValidProps(child)}
component={child._component}
children={child._children}
styles={child._styles.normal} />
{/each} {/each}
{/if} {/if}
</svelte:component> </svelte:component>
</div>
{/if} {/if}

View File

@ -1,9 +1,10 @@
<script> <script>
import { onMount } from "svelte" import { onMount, setContext } from "svelte"
import Router from "svelte-spa-router" import Router from "svelte-spa-router"
import { routeStore, screenStore } from "@budibase/component-sdk" import { routeStore, screenStore, styleable } from "@budibase/component-sdk"
import Screen from "./Screen.svelte" import Screen from "./Screen.svelte"
export let styles
let routes let routes
onMount(() => { onMount(() => {
@ -14,17 +15,23 @@
await routeStore.actions.fetchRoutes() await routeStore.actions.fetchRoutes()
await screenStore.actions.fetchScreens() await screenStore.actions.fetchScreens()
routes = {} routes = {}
$routeStore.forEach(route => { $routeStore.routes.forEach(route => {
routes[route.path] = Screen routes[route.path] = Screen
}) })
// Add catch-all route so that we serve the Screen component always
routes["*"] = Screen
} }
function test(a, b) { function onRouteLoading({ detail }) {
console.log(a) routeStore.actions.setActiveRoute(detail.route)
console.log(b)
} }
setContext("test", 123)
</script> </script>
{#if routes} {#if routes}
<Router {routes} on:routeEvent={test} /> <div use:styleable={styles}>
<Router on:routeLoading={onRouteLoading} {routes} />
</div>
{/if} {/if}

View File

@ -1,20 +1,24 @@
<script> <script>
import { screenStore } from "@budibase/component-sdk" import { screenStore, routeStore } from "@budibase/component-sdk"
import { location } from "svelte-spa-router"
import Component from "./Component.svelte" import Component from "./Component.svelte"
import { getValidProps } from "../utils" import { getValidProps } from "../utils"
export let params export let params
// Get the screen definition for the current route // Get the screen definition for the current route
$: screenDefinition = screenStore.actions.getScreenByRoute($location) $: screenDefinition = $screenStore.activeScreen
$: screenStore.actions
// Update route params
$: routeStore.actions.setRouteParams(params)
// Redirect to home page if no matching route
$: {
if (screenDefinition == null) {
routeStore.actions.navigate("/")
}
}
</script> </script>
{#if screenDefinition} {#if screenDefinition}
<Component <Component definition={screenDefinition.props} />
component={screenDefinition.props._component}
props={getValidProps(screenDefinition.props)}
children={screenDefinition.props._children}
styles={screenDefinition.props._styles.normal} />
{/if} {/if}

View File

@ -2,6 +2,7 @@ import ClientApp from "./components/ClientApp.svelte"
// Initialise client app // Initialise client app
const loadBudibase = () => { const loadBudibase = () => {
window.document.body.innerHTML = ""
new ClientApp({ new ClientApp({
target: window.document.body, target: window.document.body,
}) })

View File

@ -2,10 +2,8 @@ import { writable, get } from "svelte/store"
import { getAppId } from "@budibase/component-sdk" import { getAppId } from "@budibase/component-sdk"
import Router from "../components/Router.svelte" import Router from "../components/Router.svelte"
const initialState = {} const createComponentStore = () => {
const store = writable({})
export const createComponentStore = () => {
const store = writable(initialState)
/** /**
* Loads the component library from the server * Loads the component library from the server
@ -25,14 +23,13 @@ export const createComponentStore = () => {
if (!componentName) { if (!componentName) {
return null return null
} }
const split = componentName.split("/")
const strippedName = split[split.length - 1]
// Edge case for screen slot // Edge case for screen slot
if (strippedName === "screenslot") { if (componentName === "screenslot") {
return Router return Router
} }
return get(store)[strippedName]
return get(store)[componentName]
} }
// Attach actions to the store // Attach actions to the store
@ -40,3 +37,5 @@ export const createComponentStore = () => {
return store return store
} }
export const componentStore = createComponentStore()

View File

@ -1,3 +1 @@
import { createComponentStore } from "./components" export { componentStore } from "./components"
export const componentStore = createComponentStore()

View File

@ -1,16 +1,3 @@
/**
* Builds a style string from a style object.
*/
export const buildStyle = styles => {
let str = ""
Object.entries(styles).forEach(([style, value]) => {
if (style && value) {
str += `${style}: ${value}; `
}
})
return str
}
/** /**
* Extracts all valid props from a component definition that should be passed to * Extracts all valid props from a component definition that should be passed to
* its actual component instance. * its actual component instance.

View File

@ -21,6 +21,7 @@
"rollup-plugin-svelte": "^6.1.1" "rollup-plugin-svelte": "^6.1.1"
}, },
"dependencies": { "dependencies": {
"svelte": "^3.29.0",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
} }
} }

View File

@ -14,21 +14,19 @@ export const fetchRow = async ({ tableId, rowId }) => {
/** /**
* Creates a row in a table. * Creates a row in a table.
*/ */
export const saveRow = async (params, state) => { export const saveRow = async row => {
return await api.post({ return await api.post({
url: `/api/${params.tableId}/rows`, url: `/api/${row.tableId}/rows`,
body: makeRowRequestBody(params, state), body: row,
}) })
} }
/** /**
* Updates a row in a table. * Updates a row in a table.
*/ */
export const updateRow = async (params, state) => { export const updateRow = async row => {
const row = makeRowRequestBody(params, state)
row._id = params._id
return await api.patch({ return await api.patch({
url: `/api/${params.tableId}/rows/${params._id}`, url: `/api/${row.tableId}/rows/${row._id}`,
body: row, body: row,
}) })
} }
@ -55,44 +53,6 @@ export const deleteRows = async ({ tableId, rows }) => {
}) })
} }
/**
* Sanitises and parses column types when saving and updating rows.
*/
const makeRowRequestBody = (parameters, state) => {
// start with the row thats currently in context
const body = { ...(state.data || {}) }
// dont send the table
if (body._table) delete body._table
// then override with supplied parameters
if (parameters.fields) {
for (let fieldName of Object.keys(parameters.fields)) {
const field = parameters.fields[fieldName]
// ensure fields sent are of the correct type
if (field.type === "boolean") {
if (field.value === "true") body[fieldName] = true
if (field.value === "false") body[fieldName] = false
} else if (field.type === "number") {
const val = parseFloat(field.value)
if (!isNaN(val)) {
body[fieldName] = val
}
} else if (field.type === "datetime") {
const date = new Date(field.value)
if (!isNaN(date.getTime())) {
body[fieldName] = date.toISOString()
}
} else {
body[fieldName] = field.value
}
}
}
return body
}
/** /**
* Enriches rows which contain certain field types so that they can * Enriches rows which contain certain field types so that they can
* be properly displayed. * be properly displayed.

View File

@ -0,0 +1 @@
export const DataProvider = "bb-data-provider"

View File

@ -0,0 +1,24 @@
import { writable } from "svelte/store"
export const createDataProviderContext = () => {
const store = writable({
rows: [],
table: null,
})
const setRows = rows => {
store.update(state => {
state.rows = rows
return state
})
}
const setTable = table => {
store.update(state => {
state.table = table
return state
})
}
return {
subscribe: store.subscribe,
actions: { setRows, setTable },
}
}

View File

@ -1 +1,2 @@
export const RouterContext = "bb-router" export * from "./dataProvider"
export * as ContextTypes from "./contextTypes"

View File

@ -1,5 +1,5 @@
export * from "./api" export * from "./api"
export * from "./store" export * from "./store"
export * as ContextTypes from "./context" export * from "./context"
export { getAppId } from "./utils" export * from "./utils"
export { link } from "svelte-spa-router" export { link as linkable } from "svelte-spa-router"

View File

@ -1,11 +1,9 @@
import { localStorageStore } from "../../../builder/src/builderStore/store/localStorage"
import * as api from "../api" import * as api from "../api"
import { getAppId } from "../utils" import { getAppId } from "../utils"
import { writable } from "svelte/store"
const initialState = ""
export const createAuthStore = () => { export const createAuthStore = () => {
const store = localStorageStore("budibase:token", initialState) const store = writable("")
/** /**
* Logs a user in. * Logs a user in.
@ -22,7 +20,7 @@ export const createAuthStore = () => {
* Logs a user out. * Logs a user out.
*/ */
const logOut = () => { const logOut = () => {
store.set(initialState) store.set("")
// Expire any cookies // Expire any cookies
const appId = getAppId() const appId = getAppId()
@ -33,10 +31,14 @@ export const createAuthStore = () => {
} }
} }
store.actions = { return {
logIn, subscribe: store.subscribe,
logOut, actions: { logIn, logOut },
} }
return store
} }
if (!window.bbSDKAuthStore) {
window.bbSDKAuthStore = createAuthStore()
}
export const authStore = window.bbSDKAuthStore

View File

@ -37,11 +37,14 @@ export const createConfigStore = () => {
handler && handler(error) handler && handler(error)
} }
store.actions = { return {
initialise, subscribe: store.subscribe,
reset, actions: { initialise, reset, handleError },
handleError,
} }
return store
} }
if (!window.bbSDKConfigStore) {
window.bbSDKConfigStore = createConfigStore()
}
export const configStore = window.bbSDKConfigStore

View File

@ -1,9 +1,4 @@
import { createConfigStore } from "./config" export { configStore } from "./config"
import { createAuthStore } from "./auth" export { authStore } from "./auth"
import { createRouteStore } from "./routes" export { routeStore } from "./routes"
import { createScreenStore } from "./screens" export { screenStore } from "./screens"
export const configStore = createConfigStore()
export const authStore = createAuthStore()
export const routeStore = createRouteStore()
export const screenStore = createScreenStore()

View File

@ -1,9 +1,12 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { push } from "svelte-spa-router" import { push } from "svelte-spa-router"
const initialState = []
export const createRouteStore = () => { export const createRouteStore = () => {
const initialState = {
routes: [],
routeParams: {},
activeRoute: null,
}
const store = writable(initialState) const store = writable(initialState)
const fetchRoutes = () => { const fetchRoutes = () => {
@ -12,10 +15,35 @@ export const createRouteStore = () => {
path: screen.route, path: screen.route,
screenId: screen._id, screenId: screen._id,
})) }))
store.set(routes) store.update(state => {
state.routes = routes
return state
})
}
const setRouteParams = routeParams => {
console.log("new route params: ")
console.log(routeParams)
store.update(state => {
state.routeParams = routeParams
return state
})
}
const setActiveRoute = route => {
store.update(state => {
state.activeRoute = route
return state
})
} }
const navigate = push const navigate = push
store.actions = { fetchRoutes, navigate }
return store return {
subscribe: store.subscribe,
actions: { fetchRoutes, navigate, setRouteParams, setActiveRoute },
}
} }
if (!window.bbSDKRouteStore) {
window.bbSDKRouteStore = createRouteStore()
}
export const routeStore = window.bbSDKRouteStore

View File

@ -1,25 +1,33 @@
import { writable, derived, get } from "svelte/store" import { writable, derived } from "svelte/store"
import { routeStore } from "./routes"
const initialState = []
export const createScreenStore = () => { export const createScreenStore = () => {
const store = writable(initialState) console.log("CREATE SCREEN STORE")
const routeLookupMap = derived(store, $screens => {
let map = {} const screens = writable([])
$screens.forEach(screen => { const store = derived([screens, routeStore], ([$screens, $routeStore]) => {
map[screen.route] = screen const activeScreen = $screens.find(
}) screen => screen.route === $routeStore.activeRoute
return map )
return {
screens: $screens,
activeScreen,
}
}) })
const fetchScreens = () => { const fetchScreens = () => {
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"] const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"]
store.set(frontendDefinition.screens) screens.set(frontendDefinition.screens)
} }
const getScreenByRoute = path => {
return get(routeLookupMap)[path]
}
store.actions = { fetchScreens, getScreenByRoute }
return store return {
subscribe: store.subscribe,
actions: { fetchScreens },
}
} }
if (!window.bbSDKScreenStore) {
window.bbSDKScreenStore = createScreenStore()
}
export const screenStore = window.bbSDKScreenStore

View File

@ -1 +1,2 @@
export { getAppId } from "./getAppId" export { getAppId } from "./getAppId"
export { styleable } from "./styleable"

View File

@ -0,0 +1,46 @@
const buildStyleString = styles => {
let str = ""
Object.entries(styles).forEach(([style, value]) => {
if (style && value) {
str += `${style}: ${value}; `
}
})
return str
}
/**
* Svelte action to apply correct component styles.
*/
export const styleable = (node, styles = {}) => {
const normalStyles = styles.normal || {}
const hoverStyles = {
...normalStyles,
...styles.hover,
}
function applyNormalStyles() {
node.style = buildStyleString(normalStyles)
}
function applyHoverStyles() {
node.style = buildStyleString(hoverStyles)
}
// Add listeners to toggle hover styles
node.addEventListener("mouseover", applyHoverStyles)
node.addEventListener("mouseout", applyNormalStyles)
// Apply normal styles initially
applyNormalStyles()
// Also apply data tags so we know how to reference each component
node.setAttribute("data-bb-id", styles.id)
return {
// Clean up event listeners when component is destroyed
destroy: () => {
node.removeEventListener("mouseover", applyHoverStyles)
node.removeEventListener("mouseout", applyNormalStyles)
},
}
}

View File

@ -828,6 +828,11 @@ svelte-spa-router@^3.0.5:
dependencies: dependencies:
regexparam "1.3.0" regexparam "1.3.0"
svelte@^3.29.0:
version "3.29.7"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.7.tgz#e254eb2d0d609ce0fd60f052d444ac4a66d90f7d"
integrity sha512-rx0g311kBODvEWUU01DFBUl3MJuJven04bvTVFUG/w0On/wuj0PajQY/QlXcJndFxG+W1s8iXKaB418tdHWc3A==
typedarray-to-buffer@~1.0.0: typedarray-to-buffer@~1.0.0:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-1.0.4.tgz#9bb8ba0e841fb3f4cf1fe7c245e9f3fa8a5fe99c" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-1.0.4.tgz#9bb8ba0e841fb3f4cf1fe7c245e9f3fa8a5fe99c"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/standard-components", "name": "@budibase/standard-components",
"svelte": "src/index.svelte", "svelte": "src/index.js",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.js", "module": "dist/index.js",
"scripts": { "scripts": {
@ -15,13 +15,13 @@
"devDependencies": { "devDependencies": {
"@budibase/client": "^0.3.6", "@budibase/client": "^0.3.6",
"@rollup/plugin-alias": "^3.1.1", "@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-replace": "^2.3.4",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"rollup": "^2.11.2", "rollup": "^2.11.2",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.1", "rollup-plugin-livereload": "^1.0.1",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-postcss": "^3.1.5", "rollup-plugin-postcss": "^3.1.5",
"rollup-plugin-svelte": "^6.1.1", "rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",

View File

@ -1,13 +1,13 @@
import svelte from "rollup-plugin-svelte"
import resolve from "rollup-plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import postcss from "rollup-plugin-postcss"
import alias from "@rollup/plugin-alias" import alias from "@rollup/plugin-alias"
import commonjs from "@rollup/plugin-commonjs"
import resolve from "@rollup/plugin-node-resolve"
import replace from "@rollup/plugin-replace"
import svelte from "rollup-plugin-svelte"
import postcss from "rollup-plugin-postcss"
import { terser } from "rollup-plugin-terser" import { terser } from "rollup-plugin-terser"
import path from "path" import path from "path"
const production = !process.env.ROLLUP_WATCH const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = ["isEmpty"]
const projectRootDir = path.resolve(__dirname) const projectRootDir = path.resolve(__dirname)
export default { export default {
@ -17,7 +17,7 @@ export default {
file: "dist/index.js", file: "dist/index.js",
format: "esm", format: "esm",
name: "budibaseStandardComponents", name: "budibaseStandardComponents",
sourcemap: true, sourcemap: false,
}, },
], ],
plugins: [ plugins: [
@ -33,19 +33,18 @@ export default {
], ],
}), }),
production && terser(), production && terser(),
postcss({ postcss(),
plugins: [],
}),
svelte({ svelte({
hydratable: true, dev: !production,
}), }),
resolve({ resolve({
browser: true, browser: true,
}), }),
commonjs({ commonjs(),
namedExports: { // Fix for https://github.com/sveltejs/svelte/issues/3165
"lodash/fp": lodash_fp_exports, replace({
}, "outros.c.push":
"if (outros === undefined) { block.o(local); return }\noutros.c.push",
}), }),
], ],
} }

View File

@ -1,25 +1,20 @@
<script> <script>
import { styleable } from "@budibase/component-sdk"
export let className = "default" export let className = "default"
export let disabled = false export let disabled = false
export let text export let text
export let styles
export let _bb
let theButton let theButton
$: if (_bb.props._children && _bb.props._children.length > 0)
theButton && _bb.attachChildren(theButton)
const clickHandler = () => {
_bb.call("onClick")
}
</script> </script>
<button <button
bind:this={theButton} bind:this={theButton}
class="default" class="default"
disabled={disabled || false} disabled={disabled || false}
on:click|once={clickHandler}> use:styleable={styles}>
{#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if} {text}
</button> </button>
<style> <style>
@ -37,28 +32,4 @@
white-space: nowrap; white-space: nowrap;
text-align: center; text-align: center;
} }
.border {
border: var(--border);
}
.color {
color: var(--color);
}
.background {
background: var(--background);
}
.hoverBorder:hover {
border: var(--hoverBorder);
}
.hoverColor:hover {
color: var(--hoverColor);
}
.hoverBack:hover {
background: var(--hoverBackground);
}
</style> </style>

View File

@ -1,58 +1,61 @@
<script> <script>
import { styleable } from "@budibase/component-sdk"
export let className = "" export let className = ""
export let type = "div" export let type = "div"
export let styles
$: console.log(type)
</script> </script>
{#if type === 'div'} {#if type === 'div'}
<div> <div use:styleable={styles}>
<slot /> <slot />
</div> </div>
{:else if type === 'header'} {:else if type === 'header'}
<header> <header use:styleable={styles}>
<slot /> <slot />
</header> </header>
{:else if type === 'main'} {:else if type === 'main'}
<main> <main use:styleable={styles}>
<slot /> <slot />
</main> </main>
{:else if type === 'footer'} {:else if type === 'footer'}
<footer> <footer use:styleable={styles}>
<slot /> <slot />
</footer> </footer>
{:else if type === 'aside'} {:else if type === 'aside'}
<aside> <aside use:styleable={styles}>
<slot /> <slot />
</aside> </aside>
{:else if type === 'summary'} {:else if type === 'summary'}
<summary> <summary use:styleable={styles}>
<slot /> <slot />
</summary> </summary>
{:else if type === 'details'} {:else if type === 'details'}
<details> <details use:styleable={styles}>
<slot /> <slot />
</details> </details>
{:else if type === 'article'} {:else if type === 'article'}
<article> <article use:styleable={styles}>
<slot /> <slot />
</article> </article>
{:else if type === 'nav'} {:else if type === 'nav'}
<nav> <nav use:styleable={styles}>
<slot /> <slot />
</nav> </nav>
{:else if type === 'mark'} {:else if type === 'mark'}
<mark><slot /></mark> <mark use:styleable={styles}>
<slot />
</mark>
{:else if type === 'figure'} {:else if type === 'figure'}
<figure> <figure use:styleable={styles}>
<slot /> <slot />
</figure> </figure>
{:else if type === 'figcaption'} {:else if type === 'figcaption'}
<figcaption> <figcaption use:styleable={styles}>
<slot /> <slot />
</figcaption> </figcaption>
{:else if type === 'paragraph'} {:else if type === 'paragraph'}
<p> <p use:styleable={styles}>
<slot /> <slot />
</p> </p>
{/if} {/if}

View File

@ -1,10 +1,10 @@
<script> <script>
import Form from "./Form.svelte" import Form from "./Form.svelte"
export let _bb
export let table export let table
export let title export let title
export let buttonText export let buttonText
export let styles
</script> </script>
<Form {_bb} {table} {title} {buttonText} wide={true} /> <Form {styles} {table} {title} {buttonText} wide />

View File

@ -1,5 +1,23 @@
<script> <script>
import { styleable } from "@budibase/component-sdk"
export let embed export let embed
export let styles
</script> </script>
{@html embed} <div use:styleable={styles}>
{@html embed}
</div>
<style>
div {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
}
div :global(> *) {
flex: 1 1 auto;
}
</style>

View File

@ -1,54 +1,57 @@
<script> <script>
import { getContext } from "svelte"
import { Label, DatePicker, Input, Select, Toggle } from "@budibase/bbui" import { Label, DatePicker, Input, Select, Toggle } from "@budibase/bbui"
import Dropzone from "./attachments/Dropzone.svelte" import Dropzone from "./attachments/Dropzone.svelte"
import LinkedRowSelector from "./LinkedRowSelector.svelte" import LinkedRowSelector from "./LinkedRowSelector.svelte"
import ErrorsBox from "./ErrorsBox.svelte" import ErrorsBox from "./ErrorsBox.svelte"
import { capitalise } from "./helpers" import { capitalise } from "./helpers"
import { styleable, ContextTypes, screenStore } from "@budibase/component-sdk"
export let _bb
export let table export let table
export let wide = false export let wide = false
export let styles
let store = _bb.store
let schema = {} let schema = {}
let rowId let rowId
let errors = {} let errors = {}
$: schema = $store.data && $store.data._table && $store.data._table.schema const dataProviderStore = getContext(ContextTypes.DataProvider)
$: row = $dataProviderStore.rows[0]
$: schema = $dataProviderStore.table && $dataProviderStore.table.schema
$: fields = schema ? Object.keys(schema) : [] $: fields = schema ? Object.keys(schema) : []
</script> </script>
<div class="form-content"> <div class="form-content" use:styleable={styles}>
<ErrorsBox errors={$store.saveRowErrors || {}} /> <!-- <ErrorsBox errors={$store.saveRowErrors || {}} />-->
{#each fields as field} {#each fields as field}
<div class="form-field" class:wide> <div class="form-field" class:wide>
{#if !(schema[field].type === 'boolean' && !wide)} {#if !(schema[field].type === 'boolean' && !wide)}
<Label extraSmall={!wide} grey>{capitalise(schema[field].name)}</Label> <Label extraSmall={!wide} grey>{capitalise(schema[field].name)}</Label>
{/if} {/if}
{#if schema[field].type === 'options'} {#if schema[field].type === 'options'}
<Select secondary bind:value={$store.data[field]}> <Select secondary bind:value={row[field]}>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each schema[field].constraints.inclusion as opt} {#each schema[field].constraints.inclusion as opt}
<option>{opt}</option> <option>{opt}</option>
{/each} {/each}
</Select> </Select>
{:else if schema[field].type === 'datetime'} {:else if schema[field].type === 'datetime'}
<DatePicker bind:value={$store.data[field]} /> <DatePicker bind:value={row[field]} />
{:else if schema[field].type === 'boolean'} {:else if schema[field].type === 'boolean'}
<Toggle <Toggle
text={wide ? null : capitalise(schema[field].name)} text={wide ? null : capitalise(schema[field].name)}
bind:checked={$store.data[field]} /> bind:checked={row[field]} />
{:else if schema[field].type === 'number'} {:else if schema[field].type === 'number'}
<Input type="number" bind:value={$store.data[field]} /> <Input type="number" bind:value={row[field]} />
{:else if schema[field].type === 'string'} {:else if schema[field].type === 'string'}
<Input bind:value={$store.data[field]} /> <Input bind:value={row[field]} />
{:else if schema[field].type === 'attachment'} {:else if schema[field].type === 'attachment'}
<Dropzone bind:files={$store.data[field]} /> <Dropzone bind:files={row[field]} />
{:else if schema[field].type === 'link'} {:else if schema[field].type === 'link'}
<LinkedRowSelector <LinkedRowSelector
secondary secondary
showLabel={false} showLabel={false}
bind:linkedRows={$store.data[field]} bind:linkedRows={row[field]}
schema={schema[field]} /> schema={schema[field]} />
{/if} {/if}
</div> </div>

View File

@ -1,29 +1,22 @@
<script> <script>
import { styleable } from "@budibase/component-sdk"
export let className = "" export let className = ""
export let type export let type
export let text = "" export let text = ""
export let styles
export let _bb
let containerElement
$: containerElement &&
!text &&
_bb.props.children &&
_bb.props.children.length &&
_bb.attachChildren(containerElement)
</script> </script>
{#if type === 'h1'} {#if type === 'h1'}
<h1 class={className} bind:this={containerElement}>{text}</h1> <h1 class={className} use:styleable={styles}>{text}</h1>
{:else if type === 'h2'} {:else if type === 'h2'}
<h2 class={className} bind:this={containerElement}>{text}</h2> <h2 class={className} use:styleable={styles}>{text}</h2>
{:else if type === 'h3'} {:else if type === 'h3'}
<h3 class={className} bind:this={containerElement}>{text}</h3> <h3 class={className} use:styleable={styles}>{text}</h3>
{:else if type === 'h4'} {:else if type === 'h4'}
<h4 class={className} bind:this={containerElement}>{text}</h4> <h4 class={className} use:styleable={styles}>{text}</h4>
{:else if type === 'h5'} {:else if type === 'h5'}
<h5 class={className} bind:this={containerElement}>{text}</h5> <h5 class={className} use:styleable={styles}>{text}</h5>
{:else if type === 'h6'} {:else if type === 'h6'}
<h6 class={className} bind:this={containerElement}>{text}</h6> <h6 class={className} use:styleable={styles}>{text}</h6>
{/if} {/if}

View File

@ -1,5 +1,5 @@
<script> <script>
import { link } from "@budibase/component-sdk" import { linkable } from "@budibase/component-sdk"
export let url = "" export let url = ""
export let text = "" export let text = ""
@ -8,7 +8,7 @@
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
</script> </script>
<a href={url} use:link {target}> <a href={url} use:linkable {target}>
{text} {text}
<slot /> <slot />
</a> </a>

View File

@ -1,21 +1,28 @@
<script> <script>
import { onMount } from "svelte" import { onMount, setContext } from "svelte"
import { fetchDatasource } from "@budibase/component-sdk" import {
fetchDatasource,
createDataProviderContext,
fetchTableDefinition,
ContextTypes,
} from "@budibase/component-sdk"
import { isEmpty } from "lodash/fp" import { isEmpty } from "lodash/fp"
export let _bb
export let datasource = [] export let datasource = []
let target let target
let store = _bb.store
const dataProviderContext = createDataProviderContext()
setContext(ContextTypes.DataProvider, dataProviderContext)
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
const data = await fetchDatasource(datasource) const rows = await fetchDatasource(datasource)
_bb.attachChildren(target, { dataProviderContext.actions.setRows(rows)
hydrate: false, if (datasource.tableId) {
context: data, const tableDefinition = await fetchTableDefinition(datasource.tableId)
}) dataProviderContext.actions.setTable(tableDefinition)
}
} }
}) })
</script> </script>

View File

@ -1,12 +1,12 @@
<script> <script>
import { authStore } from "@budibase/component-sdk" import { authStore, styleable } from "@budibase/component-sdk"
export let buttonText = "Log In" export let buttonText = "Log In"
export let logo = "" export let logo = ""
export let title = "" export let title = ""
export let buttonClass = "" export let buttonClass = ""
export let inputClass = "" export let inputClass = ""
export let _bb export let styles
let username = "" let username = ""
let password = "" let password = ""
@ -33,7 +33,7 @@
} }
</script> </script>
<div class="root"> <div class="root" use:styleable={styles}>
<div class="content"> <div class="content">
{#if logo} {#if logo}
<div class="logo-container"><img src={logo} alt="logo" /></div> <div class="logo-container"><img src={logo} alt="logo" /></div>

View File

@ -1,8 +1,9 @@
<script> <script>
import { authStore, link } from "@budibase/component-sdk" import { authStore, linkable, styleable } from "@budibase/component-sdk"
export let logoUrl export let logoUrl
export let title export let title
export let styles
const logOut = () => { const logOut = () => {
authStore.actions.logOut() authStore.actions.logOut()
@ -10,9 +11,9 @@
} }
</script> </script>
<div class="nav"> <div class="nav" use:styleable={styles}>
<div class="nav__top"> <div class="nav__top">
<a href="/" use:link> <a href="/" use:linkable>
{#if logoUrl} {#if logoUrl}
<img class="logo" alt="logo" src={logoUrl} height="48" /> <img class="logo" alt="logo" src={logoUrl} height="48" />
{/if} {/if}

View File

@ -1,17 +1,22 @@
<script> <script>
import { onMount } from "svelte" import { onMount, setContext } from "svelte"
import { import {
fetchTableDefinition, fetchTableDefinition,
fetchTableData, fetchTableData,
fetchRow, fetchRow,
screenStore,
routeStore,
createDataProviderContext,
ContextTypes,
} from "@budibase/component-sdk" } from "@budibase/component-sdk"
export let _bb
export let table export let table
let headers = [] let headers = []
let store = _bb.store
let target // Expose data provider context for this row
const dataProviderContext = createDataProviderContext()
setContext(ContextTypes.DataProvider, dataProviderContext)
async function fetchFirstRow() { async function fetchFirstRow() {
const rows = await fetchTableData(table) const rows = await fetchTableData(table)
@ -24,8 +29,10 @@
} }
const pathParts = window.location.pathname.split("/") const pathParts = window.location.pathname.split("/")
const routeParamId = _bb.routeParams().id const routeParamId = $routeStore.routeParams.id
console.log(routeParamId)
let row let row
let tableDefinition
// if srcdoc, then we assume this is the builder preview // if srcdoc, then we assume this is the builder preview
if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && table) { if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && table) {
@ -37,18 +44,14 @@
} }
if (row) { if (row) {
row._table = await fetchTableDefinition(row.tableId) tableDefinition = await fetchTableDefinition(row.tableId)
_bb.attachChildren(target, {
context: row,
})
} else {
_bb.attachChildren(target)
}
} }
onMount(async () => { dataProviderContext.actions.setRows([row])
await fetchData() dataProviderContext.actions.setTable(tableDefinition)
}) }
onMount(fetchData)
</script> </script>
<section bind:this={target} /> <slot />

View File

@ -18,7 +18,6 @@
ModalContent, ModalContent,
} from "@budibase/bbui" } from "@budibase/bbui"
export let _bb
export let datasource = {} export let datasource = {}
export let editable export let editable
export let theme = "alpine" export let theme = "alpine"
@ -32,7 +31,6 @@
let modal let modal
let store = _bb.store
let dataLoaded = false let dataLoaded = false
let data let data
let columnDefs let columnDefs
@ -52,7 +50,7 @@
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
data = await SDK.fetchDatasource(datasource, $store) data = await SDK.fetchDatasource(datasource)
let schema let schema
// Get schema for datasource // Get schema for datasource

View File

@ -8,7 +8,7 @@
let anchor let anchor
let dropdown let dropdown
export let _bb, table export let table
</script> </script>
<div bind:this={anchor}> <div bind:this={anchor}>
@ -20,7 +20,6 @@
<DropdownMenu bind:this={dropdown} {anchor} align="left"> <DropdownMenu bind:this={dropdown} {anchor} align="left">
<h5>Add New Row</h5> <h5>Add New Row</h5>
<Modal <Modal
{_bb}
{table} {table}
onClosed={dropdown.hide} onClosed={dropdown.hide}
on:newRow={() => dispatch('newRow')} /> on:newRow={() => dispatch('newRow')} />

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount, createEventDispatcher } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import { fade } from "svelte/transition" import { fetchRow, saveRow, routeStore } from "@budibase/component-sdk"
import { Button, Label, DatePicker } from "@budibase/bbui" import { Button, Label, DatePicker } from "@budibase/bbui"
import Dropzone from "../../attachments/Dropzone.svelte" import Dropzone from "../../attachments/Dropzone.svelte"
import debounce from "lodash.debounce" import debounce from "lodash.debounce"
@ -13,12 +13,10 @@
link: [], link: [],
} }
export let _bb
export let table export let table
export let onClosed export let onClosed
let row = { tableId: table._id } let row = { tableId: table._id }
let store = _bb.store
let schema = table.schema let schema = table.schema
let saved = false let saved = false
let rowId let rowId
@ -39,36 +37,31 @@
} }
} }
const SAVE_ROW_URL = `/api/${table._id}/rows` const response = await saveRow(row)
const response = await _bb.api.post(SAVE_ROW_URL, row)
const json = await response.json() if (!response.error) {
// store.update(state => {
if (response.status === 200) { // state[table._id] = state[table._id]
store.update(state => { // ? [...state[table._id], json]
state[table._id] = state[table._id] // : [json]
? [...state[table._id], json] // return state
: [json] // })
return state
})
errors = {} errors = {}
// wipe form, if new row, otherwise update // wipe form, if new row, otherwise update
// table to get new _rev // table to get new _rev
row = isNew ? { tableId: table._id } : json row = isNew ? { tableId: table._id } : response
onClosed() onClosed()
dispatch("newRow") dispatch("newRow")
} } else {
errors = [response.error]
if (response.status === 400) {
errors = json.errors
} }
}) })
onMount(async () => { onMount(async () => {
const routeParams = _bb.routeParams() const routeParams = $routeStore.routeParams
rowId = rowId =
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0]) Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
isNew = !rowId || rowId === "new" isNew = !rowId || rowId === "new"
@ -78,10 +71,7 @@
return return
} }
const GET_ROW_URL = `/api/${table._id}/rows/${rowId}` row = await fetchRow({ tableId: table._id, rowId })
const response = await _bb.api.get(GET_ROW_URL)
const json = await response.json()
row = json
}) })
</script> </script>

View File

@ -1,10 +1,12 @@
<script> <script>
import { linkable } from "@budibase/component-sdk"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
export let url export let url
let link let link
</script> </script>
<a href={url} bind:this={link} /> <a href={url} bind:this={link} use:linkable />
<Button small translucent on:click={() => link.click()}>View</Button> <Button small translucent on:click={() => link.click()}>View</Button>
<style> <style>

View File

@ -147,6 +147,9 @@ function viewDetailsRenderer(options, constraints, editable) {
if (options.detailUrl) { if (options.detailUrl) {
url = options.detailUrl.replace(":id", params.data._id) url = options.detailUrl.replace(":id", params.data._id)
} }
if (!url.startsWith("/")) {
url = `/${url}`
}
new ViewDetails({ new ViewDetails({
target: container, target: container,