Merge branch 'component-sdk' of github.com:Budibase/budibase into feature/page-refactor
This commit is contained in:
commit
f2fc4f1a4c
|
@ -3,57 +3,17 @@
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import iframeTemplate from "./iframeTemplate"
|
import iframeTemplate from "./iframeTemplate"
|
||||||
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
||||||
import { Component } from "builderStore/store/screenTemplates/utils/Component"
|
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
|
|
||||||
// Styles for screenslot placeholder
|
// Create screen slot placeholder for use when a page is selected rather
|
||||||
const headingStyle = {
|
// than a screen
|
||||||
width: "500px",
|
|
||||||
padding: "8px",
|
|
||||||
}
|
|
||||||
const textStyle = {
|
|
||||||
...headingStyle,
|
|
||||||
"max-width": "",
|
|
||||||
"text-align": "left",
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
|
||||||
.normalStyle(headingStyle)
|
|
||||||
.type("h1")
|
|
||||||
.text("Screen Slot")
|
|
||||||
.instanceName("Heading")
|
|
||||||
const textScreenDisplay = new Component("@budibase/standard-components/text")
|
|
||||||
.normalStyle(textStyle)
|
|
||||||
.instanceName("Text")
|
|
||||||
.type("none")
|
|
||||||
.text(
|
|
||||||
"The screens that you create will be displayed inside this box. This box is just a placeholder, to show you the position of screens."
|
|
||||||
)
|
|
||||||
const container = new Component("@budibase/standard-components/container")
|
|
||||||
.normalStyle({
|
|
||||||
display: "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"align-items": "center",
|
|
||||||
flex: "1 1 auto",
|
|
||||||
})
|
|
||||||
.type("div")
|
|
||||||
.instanceName("Container")
|
|
||||||
.addChild(heading)
|
|
||||||
.addChild(textScreenDisplay)
|
|
||||||
const screenPlaceholder = new Screen()
|
const screenPlaceholder = new Screen()
|
||||||
.name("Screen Placeholder")
|
.name("Screen Placeholder")
|
||||||
.route("*")
|
.route("*")
|
||||||
.component("@budibase/standard-components/container")
|
.component("@budibase/standard-components/screenslotplaceholder")
|
||||||
.mainType("div")
|
|
||||||
.instanceName("Content Placeholder")
|
.instanceName("Content Placeholder")
|
||||||
.normalStyle({
|
|
||||||
flex: "1 1 auto",
|
|
||||||
})
|
|
||||||
.addChild(container)
|
|
||||||
.json()
|
.json()
|
||||||
// TODO: this ID is attached to how the screen slot is rendered, confusing, would be better a type etc
|
|
||||||
screenPlaceholder.props._id = "screenslot-placeholder"
|
|
||||||
|
|
||||||
// Extract data to pass to the iframe
|
// Extract data to pass to the iframe
|
||||||
$: page = $store.layouts[$store.currentPageName]
|
$: page = $store.layouts[$store.currentPageName]
|
||||||
|
@ -75,7 +35,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refrech the preview when required
|
// Refresh the preview when required
|
||||||
$: refreshContent(previewData)
|
$: refreshContent(previewData)
|
||||||
|
|
||||||
// Initialise the app when mounted
|
// Initialise the app when mounted
|
||||||
|
|
|
@ -11,22 +11,6 @@ export default `<html>
|
||||||
*, *:before, *:after {
|
*, *:before, *:after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
[data-bb-id="screenslot-placeholder"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-style: dashed !important;
|
|
||||||
border-width: 1px;
|
|
||||||
color: #000000;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
[data-bb-id="screenslot-placeholder"] span {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script src='/assets/budibase-client.js'></script>
|
<script src='/assets/budibase-client.js'></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -40,13 +40,16 @@
|
||||||
|
|
||||||
$: links = bindableProperties
|
$: links = bindableProperties
|
||||||
.filter(x => x.fieldSchema?.type === "link")
|
.filter(x => x.fieldSchema?.type === "link")
|
||||||
.map(property => ({
|
.map(property => {
|
||||||
|
return {
|
||||||
|
providerId: property.instance._id,
|
||||||
label: property.readableBinding,
|
label: property.readableBinding,
|
||||||
fieldName: property.fieldSchema.name,
|
fieldName: property.fieldSchema.name,
|
||||||
name: `all_${property.fieldSchema.tableId}`,
|
name: `all_${property.fieldSchema.tableId}`,
|
||||||
tableId: property.fieldSchema.tableId,
|
tableId: property.fieldSchema.tableId,
|
||||||
type: "link",
|
type: "link",
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getAppId } from "../utils"
|
import { getAppId } from "../utils/getAppId"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API cache for cached request responses.
|
* API cache for cached request responses.
|
||||||
|
|
|
@ -19,9 +19,10 @@ export const fetchDatasource = async (datasource, dataContext) => {
|
||||||
} else if (type === "view") {
|
} else if (type === "view") {
|
||||||
rows = await fetchViewData(datasource)
|
rows = await fetchViewData(datasource)
|
||||||
} else if (type === "link") {
|
} else if (type === "link") {
|
||||||
|
const row = dataContext[datasource.providerId]
|
||||||
rows = await fetchRelationshipData({
|
rows = await fetchRelationshipData({
|
||||||
rowId: dataContext?.data?._id,
|
rowId: row?._id,
|
||||||
tableId: dataContext?.data?.tableId,
|
tableId: row?.tableId,
|
||||||
fieldName,
|
fieldName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { writable } from "svelte/store"
|
||||||
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 { routeStore, screenStore, createDataContextStore } from "../store"
|
import { createDataStore, routeStore, screenStore } from "../store"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
setContext("data", createDataContextStore())
|
setContext("component", writable({}))
|
||||||
|
setContext("data", createDataStore())
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
|
|
|
@ -1,48 +1,43 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
import * as ComponentLibrary from "@budibase/standard-components"
|
import * as ComponentLibrary from "@budibase/standard-components"
|
||||||
import Router from "./Router.svelte"
|
import Router from "./Router.svelte"
|
||||||
|
import { enrichProps } from "../utils/componentProps"
|
||||||
|
import { bindingStore } from "../store"
|
||||||
|
|
||||||
export let definition = {}
|
export let definition = {}
|
||||||
|
|
||||||
// Extracts the actual component name from the library name
|
// Get local data binding context
|
||||||
const extractComponentName = name => {
|
const dataStore = getContext("data")
|
||||||
const split = name?.split("/")
|
|
||||||
return split?.[split.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracts valid props to pass to the real svelte component
|
// Create component context
|
||||||
const extractValidProps = component => {
|
const componentStore = writable({})
|
||||||
let props = {}
|
setContext("component", componentStore)
|
||||||
Object.entries(component)
|
|
||||||
.filter(([name]) => !name.startsWith("_"))
|
|
||||||
.forEach(([key, value]) => {
|
|
||||||
props[key] = value
|
|
||||||
})
|
|
||||||
return props
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the component constructor for the specified component
|
|
||||||
const getComponentConstructor = name => {
|
|
||||||
return name === "screenslot" ? Router : ComponentLibrary[componentName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract component definition info
|
// Extract component definition info
|
||||||
const componentName = extractComponentName(definition._component)
|
$: constructor = getComponentConstructor(definition._component)
|
||||||
const constructor = getComponentConstructor(componentName)
|
$: children = definition._children
|
||||||
const componentProps = extractValidProps(definition)
|
$: id = definition._id
|
||||||
const dataContext = getContext("data")
|
$: enrichedProps = enrichProps(definition, $dataStore, $bindingStore)
|
||||||
const enrichedProps = dataContext.actions.enrichDataBindings(componentProps)
|
|
||||||
const children = definition._children
|
|
||||||
|
|
||||||
// Set contexts to be consumed by component
|
// Update component context
|
||||||
setContext("style", { ...definition._styles, id: definition._id })
|
// ID is duplicated inside style so that the "styleable" helper can set
|
||||||
|
// an ID data tag for unique reference to components
|
||||||
|
$: componentStore.set({ id, styles: { ...definition._styles, id } })
|
||||||
|
|
||||||
|
// Gets the component constructor for the specified component
|
||||||
|
const getComponentConstructor = component => {
|
||||||
|
const split = component?.split("/")
|
||||||
|
const name = split?.[split.length - 1]
|
||||||
|
return name === "screenslot" ? Router : ComponentLibrary[name]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if constructor}
|
{#if constructor}
|
||||||
<svelte:component this={constructor} {...enrichedProps}>
|
<svelte:component this={constructor} {...enrichedProps}>
|
||||||
{#if children && children.length}
|
{#if children && children.length}
|
||||||
{#each children as child}
|
{#each children as child (child._id)}
|
||||||
<svelte:self definition={child} />
|
<svelte:self definition={child} />
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import { createDataContextStore } from "../store"
|
import { createDataStore } from "../store"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
|
||||||
// Get current contexts
|
// Clone and create new data context for this component tree
|
||||||
const dataContext = getContext("data")
|
const data = getContext("data")
|
||||||
const { id } = getContext("style")
|
const component = getContext("component")
|
||||||
|
const newData = createDataStore($data)
|
||||||
// Clone current context to this context
|
setContext("data", newData)
|
||||||
const newDataContext = createDataContextStore($dataContext)
|
$: newData.actions.addContext(row, $component.id)
|
||||||
setContext("data", newDataContext)
|
|
||||||
|
|
||||||
// Add additional layer to context
|
|
||||||
let loaded = false
|
|
||||||
onMount(() => {
|
|
||||||
newDataContext.actions.addContext(row, id)
|
|
||||||
loaded = true
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
<slot />
|
<slot />
|
||||||
{/if}
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import Router from "svelte-spa-router"
|
import Router from "svelte-spa-router"
|
||||||
import { routeStore, screenStore } from "../store"
|
import { routeStore } from "../store"
|
||||||
import Screen from "./Screen.svelte"
|
import Screen from "./Screen.svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
$: routerConfig = getRouterConfig($routeStore.routes)
|
$: routerConfig = getRouterConfig($routeStore.routes)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if routerConfig}
|
{#if routerConfig}
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<Router on:routeLoading={onRouteLoading} routes={routerConfig} />
|
<Router on:routeLoading={onRouteLoading} routes={routerConfig} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -11,8 +11,11 @@
|
||||||
|
|
||||||
// Redirect to home page if no matching route
|
// Redirect to home page if no matching route
|
||||||
$: screenDefinition == null && routeStore.actions.navigate("/")
|
$: screenDefinition == null && routeStore.actions.navigate("/")
|
||||||
|
|
||||||
|
// Make a screen array so we can use keying to properly re-render each screen
|
||||||
|
$: screens = screenDefinition ? [screenDefinition] : []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if screenDefinition}
|
{#each screens as screen (screen._id)}
|
||||||
<Component definition={screenDefinition} />
|
<Component definition={screen} />
|
||||||
{/if}
|
{/each}
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
import ClientApp from "./components/ClientApp.svelte"
|
import ClientApp from "./components/ClientApp.svelte"
|
||||||
|
import { builderStore } from "./store"
|
||||||
|
|
||||||
|
let app
|
||||||
|
|
||||||
// Initialise client app
|
|
||||||
const loadBudibase = () => {
|
const loadBudibase = () => {
|
||||||
window.document.body.innerHTML = ""
|
// Update builder store with any builder flags
|
||||||
new ClientApp({
|
builderStore.set({
|
||||||
|
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||||
|
page: window["##BUDIBASE_PREVIEW_PAGE##"],
|
||||||
|
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create app if one hasn't been created yet
|
||||||
|
if (!app) {
|
||||||
|
app = new ClientApp({
|
||||||
target: window.document.body,
|
target: window.document.body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Attach to window so the HTML template can call this when it loads
|
// Attach to window so the HTML template can call this when it loads
|
||||||
window.loadBudibase = loadBudibase
|
window.loadBudibase = loadBudibase
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as API from "./api"
|
import * as API from "./api"
|
||||||
import { authStore, routeStore, screenStore } from "./store"
|
import { authStore, routeStore, screenStore, bindingStore } from "./store"
|
||||||
import { styleable, getAppId } from "./utils"
|
import { styleable } from "./utils/styleable"
|
||||||
|
import { getAppId } from "./utils/getAppId"
|
||||||
import { link as linkable } from "svelte-spa-router"
|
import { link as linkable } from "svelte-spa-router"
|
||||||
import DataProvider from "./components/DataProvider.svelte"
|
import DataProvider from "./components/DataProvider.svelte"
|
||||||
|
|
||||||
|
@ -13,4 +14,5 @@ export default {
|
||||||
linkable,
|
linkable,
|
||||||
getAppId,
|
getAppId,
|
||||||
DataProvider,
|
DataProvider,
|
||||||
|
setBindableValue: bindingStore.actions.setBindableValue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import * as API from "../api"
|
import * as API from "../api"
|
||||||
import { getAppId } from "../utils"
|
import { getAppId } from "../utils/getAppId"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
const createAuthStore = () => {
|
const createAuthStore = () => {
|
||||||
const store = writable("")
|
const store = writable("")
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs a user in.
|
|
||||||
*/
|
|
||||||
const logIn = async ({ username, password }) => {
|
const logIn = async ({ username, password }) => {
|
||||||
const user = await API.logIn({ username, password })
|
const user = await API.logIn({ username, password })
|
||||||
if (!user.error) {
|
if (!user.error) {
|
||||||
|
@ -15,10 +12,6 @@ const createAuthStore = () => {
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs a user out.
|
|
||||||
*/
|
|
||||||
const logOut = () => {
|
const logOut = () => {
|
||||||
store.set("")
|
store.set("")
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
const createBindingStore = () => {
|
||||||
|
const store = writable({})
|
||||||
|
|
||||||
|
const setBindableValue = (value, componentId) => {
|
||||||
|
store.update(state => {
|
||||||
|
if (componentId) {
|
||||||
|
state[componentId] = value
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: { setBindableValue },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bindingStore = createBindingStore()
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
const createBuilderStore = () => {
|
||||||
|
const initialState = {
|
||||||
|
inBuilder: false,
|
||||||
|
page: null,
|
||||||
|
screen: null,
|
||||||
|
}
|
||||||
|
return writable(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const builderStore = createBuilderStore()
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
|
export const createDataStore = existingContext => {
|
||||||
|
const store = writable({ ...existingContext })
|
||||||
|
|
||||||
|
// Adds a context layer to the data context tree
|
||||||
|
const addContext = (row, componentId) => {
|
||||||
|
store.update(state => {
|
||||||
|
if (componentId) {
|
||||||
|
state[componentId] = row
|
||||||
|
state[`${componentId}_draft`] = cloneDeep(row)
|
||||||
|
state.closestComponentId = componentId
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
update: store.update,
|
||||||
|
actions: { addContext },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataStore = createDataStore()
|
|
@ -1,39 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { enrichDataBinding } from "../utils"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
|
|
||||||
const initialValue = {
|
|
||||||
data: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDataContextStore = existingContext => {
|
|
||||||
const initial = existingContext ? cloneDeep(existingContext) : initialValue
|
|
||||||
const store = writable(initial)
|
|
||||||
|
|
||||||
// Adds a context layer to the data context tree
|
|
||||||
const addContext = (row, componentId) => {
|
|
||||||
store.update(state => {
|
|
||||||
if (row && componentId) {
|
|
||||||
state[componentId] = row
|
|
||||||
state.data = row
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enriches props by running mustache and filling in any data bindings present
|
|
||||||
// in the prop values
|
|
||||||
const enrichDataBindings = props => {
|
|
||||||
const state = get(store)
|
|
||||||
let enrichedProps = {}
|
|
||||||
Object.entries(props).forEach(([key, value]) => {
|
|
||||||
enrichedProps[key] = enrichDataBinding(value, state)
|
|
||||||
})
|
|
||||||
return enrichedProps
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
actions: { addContext, enrichDataBindings },
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,8 @@
|
||||||
export { authStore } from "./auth"
|
export { authStore } from "./auth"
|
||||||
export { routeStore } from "./routes"
|
export { routeStore } from "./routes"
|
||||||
export { screenStore } from "./screens"
|
export { screenStore } from "./screens"
|
||||||
export { createDataContextStore } from "./dataContext"
|
export { builderStore } from "./builder"
|
||||||
|
export { bindingStore } from "./binding"
|
||||||
|
|
||||||
|
// Data stores are layered and duplicated, so it is not a singleton
|
||||||
|
export { createDataStore, dataStore } from "./data"
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived } from "svelte/store"
|
||||||
import { routeStore } from "./routes"
|
import { routeStore } from "./routes"
|
||||||
|
import { builderStore } from "./builder"
|
||||||
import * as API from "../api"
|
import * as API from "../api"
|
||||||
import { getAppId } from "../utils"
|
import { getAppId } from "../utils/getAppId"
|
||||||
|
|
||||||
const createScreenStore = () => {
|
const createScreenStore = () => {
|
||||||
const config = writable({
|
const config = writable({
|
||||||
screens: [],
|
screens: [],
|
||||||
page: {},
|
page: {},
|
||||||
})
|
})
|
||||||
const store = derived([config, routeStore], ([$config, $routeStore]) => {
|
const store = derived(
|
||||||
const { screens, page } = $config
|
[config, routeStore, builderStore],
|
||||||
const activeScreen =
|
([$config, $routeStore, $builderStore]) => {
|
||||||
screens.length === 1
|
let page
|
||||||
? screens[0]
|
let activeScreen
|
||||||
: screens.find(
|
if ($builderStore.inBuilder) {
|
||||||
|
// Use builder defined definitions if inside the builder preview
|
||||||
|
page = $builderStore.page
|
||||||
|
activeScreen = $builderStore.screen
|
||||||
|
} else {
|
||||||
|
// Otherwise find the correct screen by matching the current route
|
||||||
|
page = $config.page
|
||||||
|
const { screens } = $config
|
||||||
|
if (screens.length === 1) {
|
||||||
|
activeScreen = screens[0]
|
||||||
|
} else {
|
||||||
|
activeScreen = screens.find(
|
||||||
screen => screen.routing.route === $routeStore.activeRoute
|
screen => screen.routing.route === $routeStore.activeRoute
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
screens,
|
|
||||||
page,
|
|
||||||
activeScreen,
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
return { page, activeScreen }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const fetchScreens = async () => {
|
const fetchScreens = async () => {
|
||||||
let screens
|
|
||||||
let page
|
|
||||||
const inBuilder = !!window["##BUDIBASE_IN_BUILDER##"]
|
|
||||||
if (inBuilder) {
|
|
||||||
// Load screen and page from the window object if in the builder
|
|
||||||
screens = [window["##BUDIBASE_PREVIEW_SCREEN##"]]
|
|
||||||
page = window["##BUDIBASE_PREVIEW_PAGE##"]
|
|
||||||
} else {
|
|
||||||
// Otherwise load from API
|
|
||||||
const appDefinition = await API.fetchAppDefinition(getAppId())
|
const appDefinition = await API.fetchAppDefinition(getAppId())
|
||||||
screens = appDefinition.screens
|
config.set({
|
||||||
page = appDefinition.page
|
screens: appDefinition.screens,
|
||||||
}
|
page: appDefinition.page,
|
||||||
config.set({ screens, page })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { enrichDataBinding } from "./enrichDataBinding"
|
||||||
|
import { routeStore } from "../store"
|
||||||
|
import { saveRow, deleteRow } from "../api"
|
||||||
|
|
||||||
|
const saveRowHandler = async (action, context) => {
|
||||||
|
let draft = context[`${action.parameters.contextPath}_draft`]
|
||||||
|
if (action.parameters.fields) {
|
||||||
|
Object.entries(action.parameters.fields).forEach(([key, entry]) => {
|
||||||
|
draft[key] = enrichDataBinding(entry.value, context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await saveRow(draft)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRowHandler = async (action, context) => {
|
||||||
|
const { tableId, revId, rowId } = action.parameters
|
||||||
|
await deleteRow({
|
||||||
|
tableId: enrichDataBinding(tableId, context),
|
||||||
|
rowId: enrichDataBinding(rowId, context),
|
||||||
|
revId: enrichDataBinding(revId, context),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigationHandler = action => {
|
||||||
|
routeStore.actions.navigate(action.parameters.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlerMap = {
|
||||||
|
["Save Row"]: saveRowHandler,
|
||||||
|
["Delete Row"]: deleteRowHandler,
|
||||||
|
["Navigate To"]: navigationHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an array of actions and returns a function which will execute the
|
||||||
|
* actions in the current context.
|
||||||
|
*/
|
||||||
|
export const enrichButtonActions = (actions, context) => {
|
||||||
|
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
||||||
|
return async () => {
|
||||||
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
|
await handlers[i](actions[i], context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { enrichDataBindings } from "./enrichDataBinding"
|
||||||
|
import { enrichButtonActions } from "./buttonActions"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches component props.
|
||||||
|
* Data bindings are enriched, and button actions are enriched.
|
||||||
|
*/
|
||||||
|
export const enrichProps = (props, dataContexts, dataBindings) => {
|
||||||
|
// Exclude all private props that start with an underscore
|
||||||
|
let validProps = {}
|
||||||
|
Object.entries(props)
|
||||||
|
.filter(([name]) => !name.startsWith("_"))
|
||||||
|
.forEach(([key, value]) => {
|
||||||
|
validProps[key] = value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create context of all bindings and data contexts
|
||||||
|
// Duplicate the closest context as "data" which the builder requires
|
||||||
|
const context = {
|
||||||
|
...dataContexts,
|
||||||
|
...dataBindings,
|
||||||
|
data: dataContexts[dataContexts.closestComponentId],
|
||||||
|
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich all data bindings in top level props
|
||||||
|
let enrichedProps = enrichDataBindings(validProps, context)
|
||||||
|
|
||||||
|
// Enrich button actions if they exist
|
||||||
|
if (props._component.endsWith("/button") && enrichedProps.onClick) {
|
||||||
|
enrichedProps.onClick = enrichButtonActions(enrichedProps.onClick, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enrichedProps
|
||||||
|
}
|
|
@ -8,6 +8,9 @@ const entityMap = {
|
||||||
">": ">",
|
">": ">",
|
||||||
}
|
}
|
||||||
mustache.escape = text => {
|
mustache.escape = text => {
|
||||||
|
if (text == null || typeof text !== "string") {
|
||||||
|
return text
|
||||||
|
}
|
||||||
return text.replace(/[<>]/g, function fromEntityMap(s) {
|
return text.replace(/[<>]/g, function fromEntityMap(s) {
|
||||||
return entityMap[s] || s
|
return entityMap[s] || s
|
||||||
})
|
})
|
||||||
|
@ -30,3 +33,14 @@ export const enrichDataBinding = (input, context) => {
|
||||||
}
|
}
|
||||||
return mustache.render(input, context)
|
return mustache.render(input, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches each prop in a props object
|
||||||
|
*/
|
||||||
|
export const enrichDataBindings = (props, context) => {
|
||||||
|
let enrichedProps = {}
|
||||||
|
Object.entries(props).forEach(([key, value]) => {
|
||||||
|
enrichedProps[key] = enrichDataBinding(value, context)
|
||||||
|
})
|
||||||
|
return enrichedProps
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export { getAppId } from "./getAppId"
|
|
||||||
export { styleable } from "./styleable"
|
|
||||||
export { enrichDataBinding } from "./enrichDataBinding"
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to build a CSS string from a style object
|
||||||
|
*/
|
||||||
const buildStyleString = styles => {
|
const buildStyleString = styles => {
|
||||||
let str = ""
|
let str = ""
|
||||||
Object.entries(styles).forEach(([style, value]) => {
|
Object.entries(styles).forEach(([style, value]) => {
|
||||||
if (style && value) {
|
if (style && value != null) {
|
||||||
str += `${style}: ${value}; `
|
str += `${style}: ${value}; `
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -12,35 +18,52 @@ const buildStyleString = styles => {
|
||||||
* Svelte action to apply correct component styles.
|
* Svelte action to apply correct component styles.
|
||||||
*/
|
*/
|
||||||
export const styleable = (node, styles = {}) => {
|
export const styleable = (node, styles = {}) => {
|
||||||
const normalStyles = styles.normal || {}
|
let applyNormalStyles
|
||||||
|
let applyHoverStyles
|
||||||
|
|
||||||
|
// Creates event listeners and applies initial styles
|
||||||
|
const setupStyles = newStyles => {
|
||||||
|
const normalStyles = newStyles.normal || {}
|
||||||
const hoverStyles = {
|
const hoverStyles = {
|
||||||
...normalStyles,
|
...normalStyles,
|
||||||
...styles.hover,
|
...newStyles.hover,
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyNormalStyles() {
|
applyNormalStyles = () => {
|
||||||
node.style = buildStyleString(normalStyles)
|
node.style = buildStyleString(normalStyles)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyHoverStyles() {
|
applyHoverStyles = () => {
|
||||||
node.style = buildStyleString(hoverStyles)
|
node.style = buildStyleString(hoverStyles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add listeners to toggle hover styles
|
// Add listeners to toggle hover styles
|
||||||
node.addEventListener("mouseover", applyHoverStyles)
|
node.addEventListener("mouseover", applyHoverStyles)
|
||||||
node.addEventListener("mouseout", applyNormalStyles)
|
node.addEventListener("mouseout", applyNormalStyles)
|
||||||
|
node.setAttribute("data-bb-id", newStyles.id)
|
||||||
|
|
||||||
// Apply normal styles initially
|
// Apply initial normal styles
|
||||||
applyNormalStyles()
|
applyNormalStyles()
|
||||||
|
}
|
||||||
|
|
||||||
// Also apply data tags so we know how to reference each component
|
// Removes the current event listeners
|
||||||
node.setAttribute("data-bb-id", styles.id)
|
const removeListeners = () => {
|
||||||
|
|
||||||
return {
|
|
||||||
// Clean up event listeners when component is destroyed
|
|
||||||
destroy: () => {
|
|
||||||
node.removeEventListener("mouseover", applyHoverStyles)
|
node.removeEventListener("mouseover", applyHoverStyles)
|
||||||
node.removeEventListener("mouseout", applyNormalStyles)
|
node.removeEventListener("mouseout", applyNormalStyles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply initial styles
|
||||||
|
setupStyles(styles)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Clean up old listeners and apply new ones on update
|
||||||
|
update: newStyles => {
|
||||||
|
removeListeners()
|
||||||
|
setupStyles(newStyles)
|
||||||
|
},
|
||||||
|
// Clean up listeners when component is destroyed
|
||||||
|
destroy: () => {
|
||||||
|
removeListeners()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,19 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let className = "default"
|
export let className = "default"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let text
|
export let text
|
||||||
|
export let onClick
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="default" disabled={disabled || false} use:styleable={styles}>
|
<button
|
||||||
|
class="default"
|
||||||
|
disabled={disabled || false}
|
||||||
|
use:styleable={$component.styles}
|
||||||
|
on:click={onClick}>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { cssVars } from "./helpers"
|
import { cssVars } from "./helpers"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export const className = ""
|
export const className = ""
|
||||||
export let imageUrl = ""
|
export let imageUrl = ""
|
||||||
|
@ -26,7 +26,10 @@
|
||||||
$: showImage = !!imageUrl
|
$: showImage = !!imageUrl
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:cssVars={cssVariables} class="container" use:styleable={styles}>
|
<div
|
||||||
|
use:cssVars={cssVariables}
|
||||||
|
class="container"
|
||||||
|
use:styleable={$component.styles}>
|
||||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h2 class="heading">{heading}</h2>
|
<h2 class="heading">{heading}</h2>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { cssVars } from "./helpers"
|
import { cssVars } from "./helpers"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export const className = ""
|
export const className = ""
|
||||||
export let imageUrl = ""
|
export let imageUrl = ""
|
||||||
|
@ -29,7 +29,10 @@
|
||||||
$: showImage = !!imageUrl
|
$: showImage = !!imageUrl
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:cssVars={cssVariables} class="container" use:styleable={styles}>
|
<div
|
||||||
|
use:cssVars={cssVariables}
|
||||||
|
class="container"
|
||||||
|
use:styleable={$component.styles}>
|
||||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -2,62 +2,62 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let className = ""
|
export let className = ""
|
||||||
export let type = "div"
|
export let type = "div"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === 'div'}
|
{#if type === 'div'}
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{:else if type === 'header'}
|
{:else if type === 'header'}
|
||||||
<header use:styleable={styles}>
|
<header use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</header>
|
</header>
|
||||||
{:else if type === 'main'}
|
{:else if type === 'main'}
|
||||||
<main use:styleable={styles}>
|
<main use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
{:else if type === 'footer'}
|
{:else if type === 'footer'}
|
||||||
<footer use:styleable={styles}>
|
<footer use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</footer>
|
</footer>
|
||||||
{:else if type === 'aside'}
|
{:else if type === 'aside'}
|
||||||
<aside use:styleable={styles}>
|
<aside use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</aside>
|
</aside>
|
||||||
{:else if type === 'summary'}
|
{:else if type === 'summary'}
|
||||||
<summary use:styleable={styles}>
|
<summary use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</summary>
|
</summary>
|
||||||
{:else if type === 'details'}
|
{:else if type === 'details'}
|
||||||
<details use:styleable={styles}>
|
<details use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</details>
|
</details>
|
||||||
{:else if type === 'article'}
|
{:else if type === 'article'}
|
||||||
<article use:styleable={styles}>
|
<article use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</article>
|
</article>
|
||||||
{:else if type === 'nav'}
|
{:else if type === 'nav'}
|
||||||
<nav use:styleable={styles}>
|
<nav use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</nav>
|
</nav>
|
||||||
{:else if type === 'mark'}
|
{:else if type === 'mark'}
|
||||||
<mark use:styleable={styles}>
|
<mark use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</mark>
|
</mark>
|
||||||
{:else if type === 'figure'}
|
{:else if type === 'figure'}
|
||||||
<figure use:styleable={styles}>
|
<figure use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</figure>
|
</figure>
|
||||||
{:else if type === 'figcaption'}
|
{:else if type === 'figcaption'}
|
||||||
<figcaption use:styleable={styles}>
|
<figcaption use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</figcaption>
|
</figcaption>
|
||||||
{:else if type === 'paragraph'}
|
{:else if type === 'paragraph'}
|
||||||
<p use:styleable={styles}>
|
<p use:styleable={$component.styles}>
|
||||||
<slot />
|
<slot />
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
import { DatePicker } from "@budibase/bbui"
|
import { DatePicker } from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable, setBindableValue } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let placeholder
|
export let placeholder
|
||||||
export let value
|
|
||||||
|
let value
|
||||||
|
$: setBindableValue(value, $component.id)
|
||||||
|
|
||||||
function handleChange(event) {
|
function handleChange(event) {
|
||||||
const [fullDate] = event.detail
|
const [fullDate] = event.detail
|
||||||
|
@ -14,6 +16,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<DatePicker {placeholder} on:change={handleChange} {value} />
|
<DatePicker {placeholder} on:change={handleChange} {value} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let embed
|
export let embed
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
{@html embed}
|
{@html embed}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
||||||
import { capitalise } from "./helpers"
|
import { capitalise } from "./helpers"
|
||||||
|
|
||||||
const { styleable, screenStore, API } = getContext("sdk")
|
const { styleable, API } = getContext("sdk")
|
||||||
const dataContextStore = getContext("data")
|
const component = getContext("component")
|
||||||
const styles = getContext("style")
|
const data = getContext("data")
|
||||||
|
|
||||||
export let wide = false
|
export let wide = false
|
||||||
|
|
||||||
|
@ -15,19 +15,22 @@
|
||||||
let schema
|
let schema
|
||||||
let fields = []
|
let fields = []
|
||||||
|
|
||||||
$: getContextDetails($dataContextStore)
|
// Fetch info about the closest data context
|
||||||
|
$: getFormData($data[$data.closestComponentId])
|
||||||
|
|
||||||
const getContextDetails = async dataContext => {
|
const getFormData = async context => {
|
||||||
row = dataContext?.data
|
if (context) {
|
||||||
if (row) {
|
const tableDefinition = await API.fetchTableDefinition(context.tableId)
|
||||||
const tableDefinition = await API.fetchTableDefinition(row.tableId)
|
|
||||||
schema = tableDefinition.schema
|
schema = tableDefinition.schema
|
||||||
fields = Object.keys(schema)
|
fields = Object.keys(schema)
|
||||||
|
|
||||||
|
// Use the draft version for editing
|
||||||
|
row = $data[`${$data.closestComponentId}_draft`]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-content" use:styleable={styles}>
|
<div class="form-content" use:styleable={$component.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>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let className = ""
|
export let className = ""
|
||||||
export let type
|
export let type
|
||||||
|
@ -10,15 +10,15 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === 'h1'}
|
{#if type === 'h1'}
|
||||||
<h1 class={className} use:styleable={styles}>{text}</h1>
|
<h1 class={className} use:styleable={$component.styles}>{text}</h1>
|
||||||
{:else if type === 'h2'}
|
{:else if type === 'h2'}
|
||||||
<h2 class={className} use:styleable={styles}>{text}</h2>
|
<h2 class={className} use:styleable={$component.styles}>{text}</h2>
|
||||||
{:else if type === 'h3'}
|
{:else if type === 'h3'}
|
||||||
<h3 class={className} use:styleable={styles}>{text}</h3>
|
<h3 class={className} use:styleable={$component.styles}>{text}</h3>
|
||||||
{:else if type === 'h4'}
|
{:else if type === 'h4'}
|
||||||
<h4 class={className} use:styleable={styles}>{text}</h4>
|
<h4 class={className} use:styleable={$component.styles}>{text}</h4>
|
||||||
{:else if type === 'h5'}
|
{:else if type === 'h5'}
|
||||||
<h5 class={className} use:styleable={styles}>{text}</h5>
|
<h5 class={className} use:styleable={$component.styles}>{text}</h5>
|
||||||
{:else if type === 'h6'}
|
{:else if type === 'h6'}
|
||||||
<h6 class={className} use:styleable={styles}>{text}</h6>
|
<h6 class={className} use:styleable={$component.styles}>{text}</h6>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let size = "fa-lg"
|
export let size = "fa-lg"
|
||||||
|
@ -13,4 +13,4 @@
|
||||||
<i
|
<i
|
||||||
style={`color: ${color};`}
|
style={`color: ${color};`}
|
||||||
class={`${icon} ${size}`}
|
class={`${icon} ${size}`}
|
||||||
use:styleable={styles} />
|
use:styleable={$component.styles} />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let className = ""
|
export let className = ""
|
||||||
export let url = ""
|
export let url = ""
|
||||||
|
@ -17,4 +17,4 @@
|
||||||
class={className}
|
class={className}
|
||||||
src={url}
|
src={url}
|
||||||
alt={description}
|
alt={description}
|
||||||
use:styleable={styles} />
|
use:styleable={$component.styles} />
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable, setBindableValue } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let value = ""
|
// Keep bindable value up to date
|
||||||
export let className = ""
|
let value
|
||||||
export let type = "text"
|
$: setBindableValue(value, $component.id)
|
||||||
|
|
||||||
const onchange = ev => {
|
|
||||||
value = ev.target.value
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input
|
<input bind:value on:change={onchange} use:styleable={$component.styles} />
|
||||||
class={className}
|
|
||||||
{type}
|
|
||||||
{value}
|
|
||||||
on:change={onchange}
|
|
||||||
use:styleable={styles} />
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { linkable, styleable } = getContext("sdk")
|
const { linkable, styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let url = ""
|
export let url = ""
|
||||||
export let text = ""
|
export let text = ""
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
$: target = openInNewTab ? "_blank" : "_self"
|
$: target = openInNewTab ? "_blank" : "_self"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href={url} use:linkable {target} use:styleable={styles}>
|
<a href={url} use:linkable {target} use:styleable={$component.styles}>
|
||||||
{text}
|
{text}
|
||||||
<slot />
|
<slot />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
|
||||||
const { API, styleable, DataProvider } = getContext("sdk")
|
const { API, styleable, DataProvider } = getContext("sdk")
|
||||||
const dataContextStore = getContext("data")
|
const component = getContext("component")
|
||||||
const styles = getContext("style")
|
const data = getContext("data")
|
||||||
|
|
||||||
export let datasource = []
|
export let datasource = []
|
||||||
|
|
||||||
|
@ -12,12 +12,12 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!isEmpty(datasource)) {
|
if (!isEmpty(datasource)) {
|
||||||
rows = await API.fetchDatasource(datasource, $dataContextStore)
|
rows = await API.fetchDatasource(datasource, $data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
{#each rows as row}
|
{#each rows as row}
|
||||||
<DataProvider {row}>
|
<DataProvider {row}>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { authStore, styleable } = getContext("sdk")
|
const { authStore, styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let buttonText = "Log In"
|
export let buttonText = "Log In"
|
||||||
export let logo = ""
|
export let logo = ""
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root" use:styleable={styles}>
|
<div class="root" use:styleable={$component.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>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { authStore, linkable, styleable } = getContext("sdk")
|
const { authStore, linkable, styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let logoUrl
|
export let logoUrl
|
||||||
export let title
|
export let title
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="nav" use:styleable={styles}>
|
<div class="nav" use:styleable={$component.styles}>
|
||||||
<div class="nav__top">
|
<div class="nav__top">
|
||||||
<a href="/" use:linkable>
|
<a href="/" use:linkable>
|
||||||
{#if logoUrl}
|
{#if logoUrl}
|
||||||
|
|
|
@ -23,6 +23,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<RichText bind:content={value} {options} />
|
<RichText bind:content={value} {options} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
|
const { styleable } = getContext("sdk")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
<h1>Screen Slot</h1>
|
||||||
|
<span>
|
||||||
|
The screens that you create will be displayed inside this box.
|
||||||
|
<br />
|
||||||
|
This box is just a placeholder, to show you the position of screens.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-style: dashed !important;
|
||||||
|
border-width: 1px;
|
||||||
|
color: #000000;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let imageUrl = ""
|
export let imageUrl = ""
|
||||||
export let heading = ""
|
export let heading = ""
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
$: showImage = !!imageUrl
|
$: showImage = !!imageUrl
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container" use:styleable={styles}>
|
<div class="container" use:styleable={$component.styles}>
|
||||||
<a href={destinationUrl}>
|
<a href={destinationUrl}>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{#if showImage}
|
{#if showImage}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let text = ""
|
export let text = ""
|
||||||
export let className = ""
|
export let className = ""
|
||||||
|
@ -12,28 +12,28 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isTag('none')}
|
{#if isTag('none')}
|
||||||
<span use:styleable={styles}>{text}</span>
|
<span use:styleable={$component.styles}>{text}</span>
|
||||||
{:else if isTag('bold')}
|
{:else if isTag('bold')}
|
||||||
<b class={className} use:styleable={styles}>{text}</b>
|
<b class={className} use:styleable={$component.styles}>{text}</b>
|
||||||
{:else if isTag('strong')}
|
{:else if isTag('strong')}
|
||||||
<strong class={className} use:styleable={styles}>{text}</strong>
|
<strong class={className} use:styleable={$component.styles}>{text}</strong>
|
||||||
{:else if isTag('italic')}
|
{:else if isTag('italic')}
|
||||||
<i class={className} use:styleable={styles}>{text}</i>
|
<i class={className} use:styleable={$component.styles}>{text}</i>
|
||||||
{:else if isTag('emphasis')}
|
{:else if isTag('emphasis')}
|
||||||
<em class={className} use:styleable={styles}>{text}</em>
|
<em class={className} use:styleable={$component.styles}>{text}</em>
|
||||||
{:else if isTag('mark')}
|
{:else if isTag('mark')}
|
||||||
<mark class={className} use:styleable={styles}>{text}</mark>
|
<mark class={className} use:styleable={$component.styles}>{text}</mark>
|
||||||
{:else if isTag('small')}
|
{:else if isTag('small')}
|
||||||
<small class={className} use:styleable={styles}>{text}</small>
|
<small class={className} use:styleable={$component.styles}>{text}</small>
|
||||||
{:else if isTag('del')}
|
{:else if isTag('del')}
|
||||||
<del class={className} use:styleable={styles}>{text}</del>
|
<del class={className} use:styleable={$component.styles}>{text}</del>
|
||||||
{:else if isTag('ins')}
|
{:else if isTag('ins')}
|
||||||
<ins class={className} use:styleable={styles}>{text}</ins>
|
<ins class={className} use:styleable={$component.styles}>{text}</ins>
|
||||||
{:else if isTag('sub')}
|
{:else if isTag('sub')}
|
||||||
<sub class={className} use:styleable={styles}>{text}</sub>
|
<sub class={className} use:styleable={$component.styles}>{text}</sub>
|
||||||
{:else if isTag('sup')}
|
{:else if isTag('sup')}
|
||||||
<sup class={className} use:styleable={styles}>{text}</sup>
|
<sup class={className} use:styleable={$component.styles}>{text}</sup>
|
||||||
{:else}<span use:styleable={styles}>{text}</span>{/if}
|
{:else}<span use:styleable={$component.styles}>{text}</span>{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -3,15 +3,15 @@
|
||||||
import { chart } from "svelte-apexcharts"
|
import { chart } from "svelte-apexcharts"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const styles = getContext("style")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let options
|
export let options
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if options}
|
{#if options}
|
||||||
<div use:chart={options} use:styleable={styles} />
|
<div use:chart={options} use:styleable={$component.styles} />
|
||||||
{:else if options === false}
|
{:else if options === false}
|
||||||
<div use:styleable={styles}>Invalid chart options</div>
|
<div use:styleable={$component.styles}>Invalid chart options</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
const { API } = getContext("sdk")
|
||||||
const dataContext = getContext("data")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let datasource
|
||||||
|
@ -35,7 +34,7 @@
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
||||||
const result = await API.fetchDatasource(datasource, $dataContext)
|
const result = await API.fetchDatasource(datasource)
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = result
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
const { API } = getContext("sdk")
|
||||||
const dataContext = getContext("data")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let datasource
|
||||||
|
@ -33,7 +32,7 @@
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
||||||
const result = await API.fetchDatasource(datasource, $dataContext)
|
const result = await API.fetchDatasource(datasource)
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = result
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
const { API } = getContext("sdk")
|
||||||
const dataContext = getContext("data")
|
|
||||||
|
|
||||||
// Common props
|
// Common props
|
||||||
export let title
|
export let title
|
||||||
|
@ -41,7 +40,7 @@
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
||||||
const result = await API.fetchDatasource(datasource, $dataContext)
|
const result = await API.fetchDatasource(datasource)
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = result
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
const { API } = getContext("sdk")
|
||||||
const dataContext = getContext("data")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let datasource
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
||||||
const result = await API.fetchDatasource(datasource, $dataContext)
|
const result = await API.fetchDatasource(datasource)
|
||||||
const data = result
|
const data = result
|
||||||
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
|
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
|
||||||
.slice(0, 20)
|
.slice(0, 20)
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
// These maps need to be set up to handle whatever types that are used in the tables.
|
// These maps need to be set up to handle whatever types that are used in the tables.
|
||||||
const setters = new Map([["number", number]])
|
const setters = new Map([["number", number]])
|
||||||
const SDK = getContext("sdk")
|
const SDK = getContext("sdk")
|
||||||
const dataContext = getContext("data")
|
const component = getContext("component")
|
||||||
const styles = getContext("style")
|
|
||||||
const { API, styleable } = SDK
|
const { API, styleable } = SDK
|
||||||
|
|
||||||
export let datasource = {}
|
export let datasource = {}
|
||||||
|
@ -27,7 +26,13 @@
|
||||||
export let detailUrl
|
export let detailUrl
|
||||||
|
|
||||||
// Add setting height as css var to allow grid to use correct height
|
// Add setting height as css var to allow grid to use correct height
|
||||||
styles.normal["--grid-height"] = `${height}px`
|
$: gridStyles = {
|
||||||
|
...$component.styles,
|
||||||
|
normal: {
|
||||||
|
...$component.styles.normal,
|
||||||
|
["--grid-height"]: `${height}px`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// These can never change at runtime so don't need to be reactive
|
// These can never change at runtime so don't need to be reactive
|
||||||
let canEdit = editable && datasource && datasource.type !== "view"
|
let canEdit = editable && datasource && datasource.type !== "view"
|
||||||
|
@ -53,7 +58,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!isEmpty(datasource)) {
|
if (!isEmpty(datasource)) {
|
||||||
data = await API.fetchDatasource(datasource, $dataContext)
|
data = await API.fetchDatasource(datasource)
|
||||||
let schema
|
let schema
|
||||||
|
|
||||||
// Get schema for datasource
|
// Get schema for datasource
|
||||||
|
@ -143,7 +148,7 @@
|
||||||
href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
|
href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="container" use:styleable={styles}>
|
<div class="container" use:styleable={gridStyles}>
|
||||||
{#if dataLoaded}
|
{#if dataLoaded}
|
||||||
{#if canAddDelete}
|
{#if canAddDelete}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|
|
@ -23,4 +23,5 @@ export { default as rowdetail } from "./RowDetail.svelte"
|
||||||
export { default as newrow } from "./NewRow.svelte"
|
export { default as newrow } from "./NewRow.svelte"
|
||||||
export { default as datepicker } from "./DatePicker.svelte"
|
export { default as datepicker } from "./DatePicker.svelte"
|
||||||
export { default as icon } from "./Icon.svelte"
|
export { default as icon } from "./Icon.svelte"
|
||||||
|
export { default as screenslotplaceholder } from "./ScreenSlotPlaceholder.svelte"
|
||||||
export * from "./charts"
|
export * from "./charts"
|
||||||
|
|
Loading…
Reference in New Issue