Merge branch 'master' into feature/sql-attachments
This commit is contained in:
commit
b3108cd731
|
@ -5,10 +5,10 @@
|
||||||
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let align = "left"
|
export let align = "left"
|
||||||
export let portalTarget
|
export let portalTarget = undefined
|
||||||
export let openOnHover = false
|
export let openOnHover = false
|
||||||
export let animate
|
export let animate = true
|
||||||
export let offset
|
export let offset = undefined
|
||||||
|
|
||||||
const actionMenuContext = getContext("actionMenu")
|
const actionMenuContext = getContext("actionMenu")
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
export let url = ""
|
export let url = ""
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let initials = "JD"
|
export let initials = "JD"
|
||||||
export let color = null
|
export let color = ""
|
||||||
|
|
||||||
const DefaultColor = "#3aab87"
|
const DefaultColor = "#3aab87"
|
||||||
|
|
||||||
|
|
|
@ -28,23 +28,7 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const categories = [
|
const categories = [
|
||||||
{
|
{
|
||||||
label: "Theme",
|
label: "Theme colors",
|
||||||
colors: [
|
|
||||||
"gray-50",
|
|
||||||
"gray-75",
|
|
||||||
"gray-100",
|
|
||||||
"gray-200",
|
|
||||||
"gray-300",
|
|
||||||
"gray-400",
|
|
||||||
"gray-500",
|
|
||||||
"gray-600",
|
|
||||||
"gray-700",
|
|
||||||
"gray-800",
|
|
||||||
"gray-900",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Colors",
|
|
||||||
colors: [
|
colors: [
|
||||||
"red-100",
|
"red-100",
|
||||||
"orange-100",
|
"orange-100",
|
||||||
|
@ -91,6 +75,49 @@
|
||||||
"indigo-700",
|
"indigo-700",
|
||||||
"magenta-700",
|
"magenta-700",
|
||||||
|
|
||||||
|
"gray-50",
|
||||||
|
"gray-75",
|
||||||
|
"gray-100",
|
||||||
|
"gray-200",
|
||||||
|
"gray-300",
|
||||||
|
"gray-400",
|
||||||
|
"gray-500",
|
||||||
|
"gray-600",
|
||||||
|
"gray-700",
|
||||||
|
"gray-800",
|
||||||
|
"gray-900",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Static colors",
|
||||||
|
colors: [
|
||||||
|
"static-red-400",
|
||||||
|
"static-orange-400",
|
||||||
|
"static-yellow-400",
|
||||||
|
"static-green-400",
|
||||||
|
"static-seafoam-400",
|
||||||
|
"static-blue-400",
|
||||||
|
"static-indigo-400",
|
||||||
|
"static-magenta-400",
|
||||||
|
|
||||||
|
"static-red-800",
|
||||||
|
"static-orange-800",
|
||||||
|
"static-yellow-800",
|
||||||
|
"static-green-800",
|
||||||
|
"static-seafoam-800",
|
||||||
|
"static-blue-800",
|
||||||
|
"static-indigo-800",
|
||||||
|
"static-magenta-800",
|
||||||
|
|
||||||
|
"static-red-1200",
|
||||||
|
"static-orange-1200",
|
||||||
|
"static-yellow-1200",
|
||||||
|
"static-green-1200",
|
||||||
|
"static-seafoam-1200",
|
||||||
|
"static-blue-1200",
|
||||||
|
"static-indigo-1200",
|
||||||
|
"static-magenta-1200",
|
||||||
|
|
||||||
"static-white",
|
"static-white",
|
||||||
"static-black",
|
"static-black",
|
||||||
],
|
],
|
||||||
|
@ -137,10 +164,13 @@
|
||||||
: "var(--spectrum-global-color-gray-50)"
|
: "var(--spectrum-global-color-gray-50)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use contrasating check for the dim colours
|
// Use contrasting check for the dim colours
|
||||||
if (value?.includes("-100")) {
|
if (value?.includes("-100")) {
|
||||||
return "var(--spectrum-global-color-gray-900)"
|
return "var(--spectrum-global-color-gray-900)"
|
||||||
}
|
}
|
||||||
|
if (value?.includes("-1200") || value?.includes("-800")) {
|
||||||
|
return "var(--spectrum-global-color-static-gray-50)"
|
||||||
|
}
|
||||||
|
|
||||||
// Use black check for static white
|
// Use black check for static white
|
||||||
if (value?.includes("static-black")) {
|
if (value?.includes("static-black")) {
|
||||||
|
@ -169,7 +199,7 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<Popover bind:this={dropdown} anchor={preview} maxHeight={320} {offset} {align}>
|
<Popover bind:this={dropdown} anchor={preview} maxHeight={350} {offset} {align}>
|
||||||
<Layout paddingX="XL" paddingY="L">
|
<Layout paddingX="XL" paddingY="L">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#each categories as category}
|
{#each categories as category}
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import type { FocusEventHandler } from "svelte/elements"
|
|
||||||
|
|
||||||
export let value: string | null = null
|
export let value: string | undefined = ""
|
||||||
export let placeholder: string | null = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let readonly = false
|
export let readonly: boolean = false
|
||||||
export let id: string | null = null
|
export let id: string | undefined = undefined
|
||||||
export let height: number | null = null
|
export let height: string | number | undefined = undefined
|
||||||
export let minHeight: number | null = null
|
export let minHeight: string | number | undefined = undefined
|
||||||
export let align = null
|
export let align = null
|
||||||
|
export let updateOnChange: boolean = false
|
||||||
|
|
||||||
|
export const getCaretPosition = () => ({
|
||||||
|
start: textarea.selectionStart,
|
||||||
|
end: textarea.selectionEnd,
|
||||||
|
})
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let isFocused = false
|
let isFocused = false
|
||||||
let textarea: HTMLTextAreaElement
|
let textarea: HTMLTextAreaElement
|
||||||
const dispatch = createEventDispatcher<{ change: string }>()
|
let scrollable = false
|
||||||
const onChange: FocusEventHandler<HTMLTextAreaElement> = event => {
|
|
||||||
dispatch("change", event.currentTarget.value)
|
$: heightString = getStyleString("height", height)
|
||||||
isFocused = false
|
$: minHeightString = getStyleString("min-height", minHeight)
|
||||||
}
|
$: dispatch("scrollable", scrollable)
|
||||||
|
|
||||||
export function focus() {
|
export function focus() {
|
||||||
textarea.focus()
|
textarea.focus()
|
||||||
|
@ -28,18 +35,38 @@
|
||||||
return textarea.value
|
return textarea.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleString = (attribute: string, value: number | null) => {
|
const onBlur = () => {
|
||||||
if (!attribute || value == null) {
|
isFocused = false
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
scrollable = textarea.clientHeight < textarea.scrollHeight
|
||||||
|
if (!updateOnChange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateValue = () => {
|
||||||
|
if (readonly || disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dispatch("change", textarea.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleString = (
|
||||||
|
attribute: string,
|
||||||
|
value: string | number | undefined
|
||||||
|
) => {
|
||||||
|
if (value == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if (typeof value === "number" && isNaN(value)) {
|
if (typeof value !== "number" || isNaN(value)) {
|
||||||
return `${attribute}:${value};`
|
return `${attribute}:${value};`
|
||||||
}
|
}
|
||||||
return `${attribute}:${value}px;`
|
return `${attribute}:${value}px;`
|
||||||
}
|
}
|
||||||
|
|
||||||
$: heightString = getStyleString("height", height)
|
|
||||||
$: minHeightString = getStyleString("min-height", minHeight)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -57,8 +84,10 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
|
on:input={onChange}
|
||||||
on:focus={() => (isFocused = true)}
|
on:focus={() => (isFocused = true)}
|
||||||
on:blur={onChange}
|
on:blur={onBlur}
|
||||||
|
on:blur
|
||||||
on:keypress
|
on:keypress
|
||||||
>{value || ""}</textarea>
|
>{value || ""}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SpectrumMDE from "./SpectrumMDE.svelte"
|
import SpectrumMDE from "./SpectrumMDE.svelte"
|
||||||
|
|
||||||
export let value: string | null = null
|
export let value: string | undefined = undefined
|
||||||
export let height: string | null = null
|
export let height: string | undefined = undefined
|
||||||
|
|
||||||
let mde: any
|
let mde: any
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
.markdown-viewer :global(.EasyMDEContainer) {
|
.markdown-viewer :global(.EasyMDEContainer) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
|
@ -11,6 +11,16 @@
|
||||||
--bb-forest-green: #053835;
|
--bb-forest-green: #053835;
|
||||||
--bb-beige: #f6efea;
|
--bb-beige: #f6efea;
|
||||||
|
|
||||||
|
/* Custom spectrum additions */
|
||||||
|
--spectrum-global-color-static-red-1200: #740000;
|
||||||
|
--spectrum-global-color-static-orange-1200: #612300;
|
||||||
|
--spectrum-global-color-static-yellow-1200: #483300;
|
||||||
|
--spectrum-global-color-static-green-1200: #053f27;
|
||||||
|
--spectrum-global-color-static-seafoam-1200: #123c3a;
|
||||||
|
--spectrum-global-color-static-blue-1200: #003571;
|
||||||
|
--spectrum-global-color-static-indigo-1200: #262986;
|
||||||
|
--spectrum-global-color-static-magenta-1200: #700037;
|
||||||
|
|
||||||
--grey-1: #fafafa;
|
--grey-1: #fafafa;
|
||||||
--grey-2: #f5f5f5;
|
--grey-2: #f5f5f5;
|
||||||
--grey-3: #eeeeee;
|
--grey-3: #eeeeee;
|
||||||
|
|
|
@ -56,10 +56,7 @@
|
||||||
memo,
|
memo,
|
||||||
fetchData,
|
fetchData,
|
||||||
} from "@budibase/frontend-core"
|
} from "@budibase/frontend-core"
|
||||||
import {
|
import { getSchemaForDatasourcePlus } from "@/dataBinding"
|
||||||
getSchemaForDatasourcePlus,
|
|
||||||
readableToRuntimeBinding,
|
|
||||||
} from "@/dataBinding"
|
|
||||||
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
|
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
@ -1037,10 +1034,7 @@
|
||||||
{bindings}
|
{bindings}
|
||||||
{schema}
|
{schema}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
on:change={e =>
|
on:change={e => onChange({ [key]: e.detail })}
|
||||||
onChange({
|
|
||||||
[key]: readableToRuntimeBinding(bindings, e.detail),
|
|
||||||
})}
|
|
||||||
context={$memoContext}
|
context={$memoContext}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
/>
|
/>
|
||||||
|
@ -1065,6 +1059,7 @@
|
||||||
inputData[key] = e.detail
|
inputData[key] = e.detail
|
||||||
}}
|
}}
|
||||||
completions={stepCompletions}
|
completions={stepCompletions}
|
||||||
|
{bindings}
|
||||||
mode={codeMode}
|
mode={codeMode}
|
||||||
autocompleteEnabled={codeMode !== EditorModes.JS}
|
autocompleteEnabled={codeMode !== EditorModes.JS}
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
|
|
|
@ -152,6 +152,7 @@
|
||||||
<div class="field-wrap json-field">
|
<div class="field-wrap json-field">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={readableValue}
|
value={readableValue}
|
||||||
|
{bindings}
|
||||||
on:blur={e => {
|
on:blur={e => {
|
||||||
onChange({
|
onChange({
|
||||||
row: {
|
row: {
|
||||||
|
|
|
@ -59,7 +59,11 @@
|
||||||
import { javascript } from "@codemirror/lang-javascript"
|
import { javascript } from "@codemirror/lang-javascript"
|
||||||
import { EditorModes } from "./"
|
import { EditorModes } from "./"
|
||||||
import { themeStore } from "@/stores/portal"
|
import { themeStore } from "@/stores/portal"
|
||||||
import { FeatureFlag, type EditorMode } from "@budibase/types"
|
import {
|
||||||
|
type EnrichedBinding,
|
||||||
|
FeatureFlag,
|
||||||
|
type EditorMode,
|
||||||
|
} from "@budibase/types"
|
||||||
import { tooltips } from "@codemirror/view"
|
import { tooltips } from "@codemirror/view"
|
||||||
import type { BindingCompletion, CodeValidator } from "@/types"
|
import type { BindingCompletion, CodeValidator } from "@/types"
|
||||||
import { validateHbsTemplate } from "./validator/hbs"
|
import { validateHbsTemplate } from "./validator/hbs"
|
||||||
|
@ -80,6 +84,7 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let readonlyLineNumbers = false
|
export let readonlyLineNumbers = false
|
||||||
export let dropdown = DropdownPosition.Relative
|
export let dropdown = DropdownPosition.Relative
|
||||||
|
export let bindings: EnrichedBinding[] = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -100,7 +105,8 @@
|
||||||
let promptInput: TextArea
|
let promptInput: TextArea
|
||||||
$: aiGenEnabled =
|
$: aiGenEnabled =
|
||||||
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
||||||
mode.name === "javascript"
|
mode.name === "javascript" &&
|
||||||
|
!readonly
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (autofocus && isEditorInitialised) {
|
if (autofocus && isEditorInitialised) {
|
||||||
|
@ -165,15 +171,24 @@
|
||||||
popoverWidth = 30
|
popoverWidth = 30
|
||||||
let code = ""
|
let code = ""
|
||||||
try {
|
try {
|
||||||
const resp = await API.generateJs({ prompt })
|
const resp = await API.generateJs({ prompt, bindings })
|
||||||
code = resp.code
|
code = resp.code
|
||||||
|
|
||||||
|
if (code === "") {
|
||||||
|
throw new Error(
|
||||||
|
"we didn't understand your prompt, please phrase your request in another way"
|
||||||
|
)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
notifications.error("Unable to generate code, please try again later.")
|
if (e instanceof Error) {
|
||||||
|
notifications.error(`Unable to generate code: ${e.message}`)
|
||||||
|
} else {
|
||||||
|
notifications.error("Unable to generate code, please try again later.")
|
||||||
|
}
|
||||||
code = previousContents
|
code = previousContents
|
||||||
popoverWidth = 300
|
|
||||||
promptLoading = false
|
promptLoading = false
|
||||||
popover.hide()
|
resetPopover()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
value = code
|
value = code
|
||||||
|
@ -189,6 +204,8 @@
|
||||||
suggestedCode = null
|
suggestedCode = null
|
||||||
previousContents = null
|
previousContents = null
|
||||||
resetPopover()
|
resetPopover()
|
||||||
|
dispatch("change", editor.state.doc.toString())
|
||||||
|
dispatch("blur", editor.state.doc.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
const rejectSuggestion = () => {
|
const rejectSuggestion = () => {
|
||||||
|
@ -513,13 +530,18 @@
|
||||||
bind:this={popover}
|
bind:this={popover}
|
||||||
minWidth={popoverWidth}
|
minWidth={popoverWidth}
|
||||||
anchor={popoverAnchor}
|
anchor={popoverAnchor}
|
||||||
|
on:close={() => {
|
||||||
|
if (suggestedCode) {
|
||||||
|
acceptSuggestion()
|
||||||
|
}
|
||||||
|
}}
|
||||||
align="left-outside"
|
align="left-outside"
|
||||||
>
|
>
|
||||||
{#if promptLoading}
|
{#if promptLoading}
|
||||||
<div class="prompt-spinner">
|
<div class="prompt-spinner">
|
||||||
<Spinner size="20" color="white" />
|
<Spinner size="20" color="white" />
|
||||||
</div>
|
</div>
|
||||||
{:else if suggestedCode}
|
{:else if suggestedCode !== null}
|
||||||
<Button on:click={acceptSuggestion}>Accept</Button>
|
<Button on:click={acceptSuggestion}>Accept</Button>
|
||||||
<Button on:click={rejectSuggestion}>Reject</Button>
|
<Button on:click={rejectSuggestion}>Reject</Button>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { DrawerBindableInput } from "@/components/common/bindings"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerBindableInput
|
||||||
|
on:change
|
||||||
|
on:blur
|
||||||
|
on:drawerHide
|
||||||
|
on:drawerShow
|
||||||
|
{...$$props}
|
||||||
|
multiline
|
||||||
|
/>
|
|
@ -5,8 +5,8 @@
|
||||||
import { appsStore } from "@/stores/portal"
|
import { appsStore } from "@/stores/portal"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { createValidationStore } from "@/helpers/validation/yup"
|
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||||
import * as appValidation from "@/helpers/validation/yup/app"
|
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||||
import EditableIcon from "@/components/common/EditableIcon.svelte"
|
import EditableIcon from "@/components/common/EditableIcon.svelte"
|
||||||
import { isEqual } from "lodash"
|
import { isEqual } from "lodash"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
|
@ -359,6 +359,7 @@
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
bind:insertAtPos
|
bind:insertAtPos
|
||||||
{completions}
|
{completions}
|
||||||
|
{bindings}
|
||||||
{validations}
|
{validations}
|
||||||
autofocus={autofocusEditor}
|
autofocus={autofocusEditor}
|
||||||
placeholder={placeholder ||
|
placeholder={placeholder ||
|
||||||
|
@ -372,6 +373,7 @@
|
||||||
value={jsValue ? decodeJSBinding(jsValue) : ""}
|
value={jsValue ? decodeJSBinding(jsValue) : ""}
|
||||||
on:change={onChangeJSValue}
|
on:change={onChangeJSValue}
|
||||||
{completions}
|
{completions}
|
||||||
|
{bindings}
|
||||||
{validations}
|
{validations}
|
||||||
mode={EditorModes.JS}
|
mode={EditorModes.JS}
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
encodeJSBinding,
|
encodeJSBinding,
|
||||||
processObjectSync,
|
processObjectSync,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { runtimeToReadableBinding } from "@/dataBinding"
|
import {
|
||||||
|
runtimeToReadableBinding,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
} from "@/dataBinding"
|
||||||
import CodeEditor, { DropdownPosition } from "../CodeEditor/CodeEditor.svelte"
|
import CodeEditor, { DropdownPosition } from "../CodeEditor/CodeEditor.svelte"
|
||||||
import {
|
import {
|
||||||
getHelperCompletions,
|
getHelperCompletions,
|
||||||
|
@ -123,7 +126,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValue = (val: any) => {
|
const updateValue = (val: any) => {
|
||||||
dispatch("change", val)
|
dispatch("change", readableToRuntimeBinding(bindings, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeJSValue = (e: { detail: string }) => {
|
const onChangeJSValue = (e: { detail: string }) => {
|
||||||
|
@ -144,6 +147,7 @@
|
||||||
on:change={onChangeJSValue}
|
on:change={onChangeJSValue}
|
||||||
on:blur
|
on:blur
|
||||||
completions={jsCompletions}
|
completions={jsCompletions}
|
||||||
|
{bindings}
|
||||||
mode={EditorModes.JS}
|
mode={EditorModes.JS}
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
bind:insertAtPos
|
bind:insertAtPos
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
import { Icon, Input, Drawer, Button, CoreTextArea } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
|
@ -25,11 +25,13 @@
|
||||||
export let forceModal: boolean = false
|
export let forceModal: boolean = false
|
||||||
export let context = null
|
export let context = null
|
||||||
export let autocomplete: boolean | undefined = undefined
|
export let autocomplete: boolean | undefined = undefined
|
||||||
|
export let multiline: boolean = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let bindingDrawer: any
|
let bindingDrawer: any
|
||||||
let currentVal = value
|
let currentVal = value
|
||||||
|
let scrollable = false
|
||||||
|
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
$: tempValue = readableValue
|
$: tempValue = readableValue
|
||||||
|
@ -63,14 +65,16 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="control" class:disabled>
|
<div class="control" class:multiline class:disabled class:scrollable>
|
||||||
<Input
|
<svelte:component
|
||||||
|
this={multiline ? CoreTextArea : Input}
|
||||||
{label}
|
{label}
|
||||||
{disabled}
|
{disabled}
|
||||||
readonly={isJS}
|
readonly={isJS}
|
||||||
value={isJS ? "(JavaScript function)" : readableValue}
|
value={isJS ? "(JavaScript function)" : readableValue}
|
||||||
on:change={event => onChange(event.detail)}
|
on:change={event => onChange(event.detail)}
|
||||||
on:blur={onBlur}
|
on:blur={onBlur}
|
||||||
|
on:scrollable={e => (scrollable = e.detail)}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
|
@ -114,36 +118,38 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
/* Multiline styles */
|
||||||
right: 1px;
|
.control.multiline :global(textarea) {
|
||||||
bottom: 1px;
|
min-height: 0 !important;
|
||||||
position: absolute;
|
field-sizing: content;
|
||||||
justify-content: center;
|
max-height: 105px;
|
||||||
align-items: center;
|
padding: 6px 11px 6px 11px;
|
||||||
display: flex;
|
height: auto;
|
||||||
flex-direction: row;
|
resize: none;
|
||||||
box-sizing: border-box;
|
flex: 1 1 auto;
|
||||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
width: 0;
|
||||||
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
|
|
||||||
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
|
|
||||||
width: 31px;
|
|
||||||
color: var(--spectrum-alias-text-color);
|
|
||||||
background-color: var(--spectrum-global-color-gray-75);
|
|
||||||
transition: background-color
|
|
||||||
var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
|
||||||
height: calc(var(--spectrum-alias-item-height-m) - 2px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
right: 6px;
|
||||||
|
top: 8px;
|
||||||
|
position: absolute;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--spectrum-alias-text-color);
|
||||||
|
}
|
||||||
.icon:hover {
|
.icon:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--spectrum-alias-text-color-hover);
|
color: var(--spectrum-global-color-blue-600);
|
||||||
background-color: var(--spectrum-global-color-gray-50);
|
}
|
||||||
border-color: var(--spectrum-alias-border-color-hover);
|
.control.scrollable .icon {
|
||||||
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control:not(.disabled) :global(.spectrum-Textfield-input) {
|
.control:not(.disabled) :global(.spectrum-Textfield-input),
|
||||||
padding-right: 40px;
|
.control:not(.disabled) :global(textarea) {
|
||||||
|
padding-right: 26px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -31,9 +31,11 @@ import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
||||||
import FormStepControls from "./controls/FormStepControls.svelte"
|
import FormStepControls from "./controls/FormStepControls.svelte"
|
||||||
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
||||||
import TableConditionEditor from "./controls/TableConditionEditor.svelte"
|
import TableConditionEditor from "./controls/TableConditionEditor.svelte"
|
||||||
|
import MultilineDrawerBindableInput from "@/components/common/MultilineDrawerBindableInput.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableInput,
|
text: DrawerBindableInput,
|
||||||
|
"text/multiline": MultilineDrawerBindableInput,
|
||||||
plainText: Input,
|
plainText: Input,
|
||||||
select: Select,
|
select: Select,
|
||||||
radio: RadioGroup,
|
radio: RadioGroup,
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Body, notifications } from "@budibase/bbui"
|
|
||||||
import PasswordRepeatInput from "@/components/common/users/PasswordRepeatInput.svelte"
|
|
||||||
import { auth } from "@/stores/portal"
|
|
||||||
|
|
||||||
let password
|
|
||||||
let error
|
|
||||||
|
|
||||||
const updatePassword = async () => {
|
|
||||||
try {
|
|
||||||
await auth.updateSelf({ password })
|
|
||||||
notifications.success("Password changed successfully")
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Failed to update password")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeydown = evt => {
|
|
||||||
if (evt.key === "Enter" && !error && password) {
|
|
||||||
updatePassword()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
|
||||||
<ModalContent
|
|
||||||
title="Update password"
|
|
||||||
confirmText="Update password"
|
|
||||||
onConfirm={updatePassword}
|
|
||||||
disabled={error || !password}
|
|
||||||
>
|
|
||||||
<Body size="S">Enter your new password below.</Body>
|
|
||||||
<PasswordRepeatInput bind:password bind:error />
|
|
||||||
</ModalContent>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { auth } from "@/stores/portal"
|
|
||||||
|
|
||||||
const values = writable({
|
|
||||||
firstName: $auth.user.firstName,
|
|
||||||
lastName: $auth.user.lastName,
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateInfo = async () => {
|
|
||||||
try {
|
|
||||||
await auth.updateSelf($values)
|
|
||||||
notifications.success("Information updated successfully")
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
notifications.error("Failed to update information")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent title="My profile" confirmText="Save" onConfirm={updateInfo}>
|
|
||||||
<Body size="S">
|
|
||||||
Personalise the platform by adding your first name and last name.
|
|
||||||
</Body>
|
|
||||||
<Input disabled bind:value={$auth.user.email} label="Email" />
|
|
||||||
<Input bind:value={$values.firstName} label="First name" />
|
|
||||||
<Input bind:value={$values.lastName} label="Last name" />
|
|
||||||
</ModalContent>
|
|
|
@ -12,8 +12,8 @@
|
||||||
import { appsStore, admin, auth } from "@/stores/portal"
|
import { appsStore, admin, auth } from "@/stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { createValidationStore } from "@/helpers/validation/yup"
|
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||||
import * as appValidation from "@/helpers/validation/yup/app"
|
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||||
import TemplateCard from "@/components/common/TemplateCard.svelte"
|
import TemplateCard from "@/components/common/TemplateCard.svelte"
|
||||||
import { lowercase } from "@/helpers"
|
import { lowercase } from "@/helpers"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
Layout,
|
Layout,
|
||||||
keepOpen,
|
keepOpen,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createValidationStore } from "@/helpers/validation/yup"
|
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import * as appValidation from "@/helpers/validation/yup/app"
|
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||||
import { appsStore, auth } from "@/stores/portal"
|
import { appsStore, auth } from "@/stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { downloadFile } from "@budibase/frontend-core"
|
import { downloadFile } from "@budibase/frontend-core"
|
||||||
import { createValidationStore } from "@/helpers/validation/yup"
|
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let published
|
export let published
|
||||||
|
|
|
@ -219,6 +219,7 @@ export const PrettyRelationshipDefinitions = {
|
||||||
|
|
||||||
export const BUDIBASE_INTERNAL_DB_ID = INTERNAL_TABLE_SOURCE_ID
|
export const BUDIBASE_INTERNAL_DB_ID = INTERNAL_TABLE_SOURCE_ID
|
||||||
export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default"
|
export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default"
|
||||||
|
export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee"
|
||||||
export const BUDIBASE_DATASOURCE_TYPE = "budibase"
|
export const BUDIBASE_DATASOURCE_TYPE = "budibase"
|
||||||
export const DB_TYPE_INTERNAL = "internal"
|
export const DB_TYPE_INTERNAL = "internal"
|
||||||
export const DB_TYPE_EXTERNAL = "external"
|
export const DB_TYPE_EXTERNAL = "external"
|
||||||
|
|
|
@ -41,11 +41,6 @@ export const LAYOUT_NAMES = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// one or more word characters and whitespace
|
|
||||||
export const APP_NAME_REGEX = /^[\w\s]+$/
|
|
||||||
// zero or more non-whitespace characters
|
|
||||||
export const APP_URL_REGEX = /^[0-9a-zA-Z-_]+$/
|
|
||||||
|
|
||||||
export const DefaultAppTheme = {
|
export const DefaultAppTheme = {
|
||||||
primaryColor: "var(--spectrum-global-color-blue-600)",
|
primaryColor: "var(--spectrum-global-color-blue-600)",
|
||||||
primaryColorHover: "var(--spectrum-global-color-blue-500)",
|
primaryColorHover: "var(--spectrum-global-color-blue-500)",
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
Constants,
|
Constants,
|
||||||
Utils,
|
Utils,
|
||||||
RoleUtils,
|
RoleUtils,
|
||||||
|
emailValidator,
|
||||||
} from "@budibase/frontend-core"
|
} from "@budibase/frontend-core"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
||||||
import RoleSelect from "@/components/common/RoleSelect.svelte"
|
import RoleSelect from "@/components/common/RoleSelect.svelte"
|
||||||
import UpgradeModal from "@/components/common/users/UpgradeModal.svelte"
|
import UpgradeModal from "@/components/common/users/UpgradeModal.svelte"
|
||||||
import { emailValidator } from "@/helpers/validation"
|
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
|
import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
|
||||||
import BuilderGroupPopover from "./BuilderGroupPopover.svelte"
|
import BuilderGroupPopover from "./BuilderGroupPopover.svelte"
|
||||||
|
|
|
@ -1,13 +1,30 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { TableNames } from "@/constants"
|
import { TableNames } from "@/constants"
|
||||||
import { datasources } from "@/stores/builder"
|
import { datasources } from "@/stores/builder"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
$: {
|
onMount(() => {
|
||||||
|
// Get first valid table ID of first datasource
|
||||||
|
let tableId: string = TableNames.USERS
|
||||||
|
for (let ds of $datasources.list) {
|
||||||
|
if (Array.isArray(ds.entities) && ds.entities.length > 0) {
|
||||||
|
if (ds.entities[0]._id) {
|
||||||
|
tableId = ds.entities[0]._id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const keys = Object.keys(ds.entities || {})
|
||||||
|
if (keys.length > 0) {
|
||||||
|
tableId = keys[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($datasources.hasData) {
|
if ($datasources.hasData) {
|
||||||
$redirect(`./table/${TableNames.USERS}`)
|
$redirect(`./table/${tableId}`)
|
||||||
} else {
|
} else {
|
||||||
$redirect("./new")
|
$redirect("./new")
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import StyleSection from "./StyleSection.svelte"
|
import StyleSection from "./StyleSection.svelte"
|
||||||
import * as ComponentStyles from "./componentStyles"
|
import * as ComponentStyles from "./componentStyles"
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
|
import ColorPicker from "@/components/design/settings/controls/ColorPicker.svelte"
|
||||||
|
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
@ -18,6 +19,19 @@
|
||||||
styles.push(ComponentStyles[style])
|
styles.push(ComponentStyles[style])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add section for CSS variables if present
|
||||||
|
if (def?.cssVariables?.length) {
|
||||||
|
styles.push({
|
||||||
|
label: "Customization",
|
||||||
|
settings: def.cssVariables.map(variable => ({
|
||||||
|
label: variable.label,
|
||||||
|
key: variable.variable,
|
||||||
|
control: ColorPicker,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return styles
|
return styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
Checkbox,
|
Checkbox,
|
||||||
notifications,
|
notifications,
|
||||||
Select,
|
Select,
|
||||||
|
Stepper,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
themeStore,
|
themeStore,
|
||||||
|
@ -182,6 +183,16 @@
|
||||||
options: screenRouteOptions,
|
options: screenRouteOptions,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<PropertyControl
|
||||||
|
label="Logo height (px)"
|
||||||
|
control={Stepper}
|
||||||
|
value={$nav.logoHeight}
|
||||||
|
onChange={height => update("logoHeight", height)}
|
||||||
|
props={{
|
||||||
|
updateOnChange: false,
|
||||||
|
placeholder: "24",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
label="New tab"
|
label="New tab"
|
||||||
control={Checkbox}
|
control={Checkbox}
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
ghost.src =
|
ghost.src =
|
||||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||||
|
|
||||||
|
// Aliases for other strings to match to when searching
|
||||||
|
const aliases = {
|
||||||
|
text: ["headline", "paragraph"],
|
||||||
|
}
|
||||||
|
|
||||||
let searchString
|
let searchString
|
||||||
let searchRef
|
let searchRef
|
||||||
let selectedIndex
|
let selectedIndex
|
||||||
|
@ -148,11 +153,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterStructure = (structure, allowedComponents, search) => {
|
const filterStructure = (structure, allowedComponents, search) => {
|
||||||
selectedIndex = search ? 0 : null
|
|
||||||
componentList = []
|
|
||||||
if (!structure?.length) {
|
if (!structure?.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
search = search?.toLowerCase()
|
||||||
|
selectedIndex = search ? 0 : null
|
||||||
|
componentList = []
|
||||||
|
|
||||||
// Return only items which match the search string
|
// Return only items which match the search string
|
||||||
let filteredStructure = []
|
let filteredStructure = []
|
||||||
|
@ -161,8 +167,12 @@
|
||||||
const name = child.name.toLowerCase()
|
const name = child.name.toLowerCase()
|
||||||
|
|
||||||
// Check if the component matches the search string
|
// Check if the component matches the search string
|
||||||
if (search && !name.includes(search.toLowerCase())) {
|
if (search) {
|
||||||
return false
|
const nameMatch = name.includes(search)
|
||||||
|
const aliasMatch = (aliases[name] || []).some(x => x.includes(search))
|
||||||
|
if (!nameMatch && !aliasMatch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the component is allowed as a child
|
// Check if the component is allowed as a child
|
||||||
|
|
|
@ -32,8 +32,7 @@
|
||||||
"name": "Basic",
|
"name": "Basic",
|
||||||
"icon": "TextParagraph",
|
"icon": "TextParagraph",
|
||||||
"children": [
|
"children": [
|
||||||
"heading",
|
"textv2",
|
||||||
"text",
|
|
||||||
"button",
|
"button",
|
||||||
"buttongroup",
|
"buttongroup",
|
||||||
"tag",
|
"tag",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
$redirect("./data")
|
$redirect("./design")
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -24,13 +24,13 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { AppStatus } from "@/constants"
|
import { AppStatus } from "@/constants"
|
||||||
import { gradient } from "@/actions"
|
import { gradient } from "@/actions"
|
||||||
import ProfileModal from "@/components/settings/ProfileModal.svelte"
|
import { ProfileModal, ChangePasswordModal } from "@budibase/frontend-core"
|
||||||
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte"
|
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
import Spaceman from "assets/bb-space-man.svg"
|
import Spaceman from "assets/bb-space-man.svg"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { UserAvatar } from "@budibase/frontend-core"
|
import { UserAvatar } from "@budibase/frontend-core"
|
||||||
import { helpers, sdk } from "@budibase/shared-core"
|
import { helpers, sdk } from "@budibase/shared-core"
|
||||||
|
import { API } from "@/api"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let userInfoModal
|
let userInfoModal
|
||||||
|
@ -105,8 +105,8 @@
|
||||||
<img class="logo" alt="logo" src={$organisation.logoUrl || Logo} />
|
<img class="logo" alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<div slot="control" class="avatar">
|
<div slot="control" class="avatar">
|
||||||
<UserAvatar user={$auth.user} showTooltip={false} />
|
<UserAvatar size="M" user={$auth.user} showTooltip={false} />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="L" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||||
My profile
|
My profile
|
||||||
|
@ -201,10 +201,14 @@
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</div>
|
||||||
<Modal bind:this={userInfoModal}>
|
<Modal bind:this={userInfoModal}>
|
||||||
<ProfileModal />
|
<ProfileModal {API} user={$auth.user} on:save={() => auth.getSelf()} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal bind:this={changePasswordModal}>
|
<Modal bind:this={changePasswordModal}>
|
||||||
<ChangePasswordModal />
|
<ChangePasswordModal
|
||||||
|
{API}
|
||||||
|
passwordMinLength={$admin.passwordMinLength}
|
||||||
|
on:save={() => auth.getSelf()}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -239,6 +243,7 @@
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
grid-gap: var(--spacing-xs);
|
grid-gap: var(--spacing-xs);
|
||||||
|
transition: filter 130ms ease-out;
|
||||||
}
|
}
|
||||||
.avatar:hover {
|
.avatar:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -8,11 +8,10 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { auth, organisation } from "@/stores/portal"
|
import { auth, organisation, admin } from "@/stores/portal"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
import { TestimonialPage, PasswordRepeatInput } from "@budibase/frontend-core"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import PasswordRepeatInput from "../../../components/common/users/PasswordRepeatInput.svelte"
|
|
||||||
|
|
||||||
const resetCode = $params["?code"]
|
const resetCode = $params["?code"]
|
||||||
let form
|
let form
|
||||||
|
@ -80,9 +79,9 @@
|
||||||
<Heading size="M">Reset your password</Heading>
|
<Heading size="M">Reset your password</Heading>
|
||||||
<Body size="M">Must contain at least 12 characters</Body>
|
<Body size="M">Must contain at least 12 characters</Body>
|
||||||
<PasswordRepeatInput
|
<PasswordRepeatInput
|
||||||
bind:passwordForm={form}
|
|
||||||
bind:password
|
bind:password
|
||||||
bind:error={passwordError}
|
bind:error={passwordError}
|
||||||
|
minLength={$admin.passwordMinLength || 12}
|
||||||
/>
|
/>
|
||||||
<Button secondary cta on:click={reset}>
|
<Button secondary cta on:click={reset}>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
import { admin, auth } from "@/stores/portal"
|
import { admin, auth } from "@/stores/portal"
|
||||||
import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui"
|
import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import ProfileModal from "@/components/settings/ProfileModal.svelte"
|
import ProfileModal from "@budibase/frontend-core/src/components/ProfileModal.svelte"
|
||||||
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte"
|
import ChangePasswordModal from "@budibase/frontend-core/src/components/ChangePasswordModal.svelte"
|
||||||
import ThemeModal from "@/components/settings/ThemeModal.svelte"
|
import ThemeModal from "@/components/settings/ThemeModal.svelte"
|
||||||
import APIKeyModal from "@/components/settings/APIKeyModal.svelte"
|
import APIKeyModal from "@/components/settings/APIKeyModal.svelte"
|
||||||
import { UserAvatar } from "@budibase/frontend-core"
|
import { UserAvatar } from "@budibase/frontend-core"
|
||||||
|
import { API } from "@/api"
|
||||||
|
|
||||||
let themeModal
|
let themeModal
|
||||||
let profileModal
|
let profileModal
|
||||||
|
@ -26,8 +27,8 @@
|
||||||
|
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<div slot="control" class="user-dropdown">
|
<div slot="control" class="user-dropdown">
|
||||||
<UserAvatar user={$auth.user} showTooltip={false} />
|
<UserAvatar size="M" user={$auth.user} showTooltip={false} />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="L" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="UserEdit" on:click={() => profileModal.show()}>
|
<MenuItem icon="UserEdit" on:click={() => profileModal.show()}>
|
||||||
My profile
|
My profile
|
||||||
|
@ -60,10 +61,14 @@
|
||||||
<ThemeModal />
|
<ThemeModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal bind:this={profileModal}>
|
<Modal bind:this={profileModal}>
|
||||||
<ProfileModal />
|
<ProfileModal {API} user={$auth.user} on:save={() => auth.getSelf()} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal bind:this={updatePasswordModal}>
|
<Modal bind:this={updatePasswordModal}>
|
||||||
<ChangePasswordModal />
|
<ChangePasswordModal
|
||||||
|
{API}
|
||||||
|
passwordMinLength={$admin.passwordMinLength}
|
||||||
|
on:save={() => auth.getSelf()}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal bind:this={apiKeyModal}>
|
<Modal bind:this={apiKeyModal}>
|
||||||
<APIKeyModal />
|
<APIKeyModal />
|
||||||
|
@ -75,7 +80,8 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-xs);
|
||||||
|
transition: filter 130ms ease-out;
|
||||||
}
|
}
|
||||||
.user-dropdown:hover {
|
.user-dropdown:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, FancyForm, FancyInput } from "@budibase/bbui"
|
import { Button, FancyForm, FancyInput } from "@budibase/bbui"
|
||||||
import PanelHeader from "./PanelHeader.svelte"
|
import PanelHeader from "./PanelHeader.svelte"
|
||||||
import { APP_URL_REGEX } from "@/constants"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let disabled
|
export let disabled
|
||||||
export let name = ""
|
export let name = ""
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
return "URL must be provided"
|
return "URL must be provided"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!APP_URL_REGEX.test(url)) {
|
if (!Constants.APP_URL_REGEX.test(url)) {
|
||||||
return "Invalid URL"
|
return "Invalid URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
width: "auto",
|
width: "auto",
|
||||||
borderLeft: true,
|
|
||||||
displayName: "",
|
displayName: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@
|
||||||
Icon,
|
Icon,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups, licensing } from "@/stores/portal"
|
import { groups, licensing } from "@/stores/portal"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants, emailValidator } from "@budibase/frontend-core"
|
||||||
import { emailValidator } from "@/helpers/validation"
|
|
||||||
import { capitalise } from "@/helpers"
|
import { capitalise } from "@/helpers"
|
||||||
|
|
||||||
export let showOnboardingTypeModal
|
export let showOnboardingTypeModal
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
Icon,
|
Icon,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups, licensing, admin } from "@/stores/portal"
|
import { groups, licensing, admin } from "@/stores/portal"
|
||||||
import { emailValidator } from "@/helpers/validation"
|
import { emailValidator, Constants } from "@budibase/frontend-core"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
|
||||||
import { capitalise } from "@/helpers"
|
import { capitalise } from "@/helpers"
|
||||||
|
|
||||||
const BYTES_IN_MB = 1000000
|
const BYTES_IN_MB = 1000000
|
||||||
|
|
|
@ -5,10 +5,10 @@ import getValidRoute from "../getValidRoute"
|
||||||
import { getRowActionButtonTemplates } from "@/templates/rowActions"
|
import { getRowActionButtonTemplates } from "@/templates/rowActions"
|
||||||
|
|
||||||
const inline = async ({ tableOrView, permissions, screens }) => {
|
const inline = async ({ tableOrView, permissions, screens }) => {
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
const heading = new Component("@budibase/standard-components/textv2")
|
||||||
.instanceName("Table heading")
|
.instanceName("Table heading")
|
||||||
.customProps({
|
.customProps({
|
||||||
text: tableOrView.name,
|
text: `## ${tableOrView.name}`,
|
||||||
})
|
})
|
||||||
.gridDesktopColSpan(1, 13)
|
.gridDesktopColSpan(1, 13)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
|
@ -43,10 +43,10 @@ const modal = async ({ tableOrView, permissions, screens }) => {
|
||||||
.gridDesktopColSpan(7, 13)
|
.gridDesktopColSpan(7, 13)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
const heading = new Component("@budibase/standard-components/textv2")
|
||||||
.instanceName("Table heading")
|
.instanceName("Table heading")
|
||||||
.customProps({
|
.customProps({
|
||||||
text: tableOrView.name,
|
text: `## ${tableOrView.name}`,
|
||||||
})
|
})
|
||||||
.gridDesktopColSpan(1, 7)
|
.gridDesktopColSpan(1, 7)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
|
@ -40,10 +40,10 @@ const getTableScreenTemplate = ({
|
||||||
.gridDesktopColSpan(7, 13)
|
.gridDesktopColSpan(7, 13)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
const heading = new Component("@budibase/standard-components/textv2")
|
||||||
.instanceName("Table heading")
|
.instanceName("Table heading")
|
||||||
.customProps({
|
.customProps({
|
||||||
text: tableOrView.name,
|
text: `## ${tableOrView.name}`,
|
||||||
})
|
})
|
||||||
.gridDesktopColSpan(1, 7)
|
.gridDesktopColSpan(1, 7)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
|
@ -41,10 +41,10 @@ const sidePanel = async ({ tableOrView, permissions, screens }) => {
|
||||||
.gridDesktopColSpan(7, 13)
|
.gridDesktopColSpan(7, 13)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
const heading = new Component("@budibase/standard-components/textv2")
|
||||||
.instanceName("Table heading")
|
.instanceName("Table heading")
|
||||||
.customProps({
|
.customProps({
|
||||||
text: tableOrView.name,
|
text: `## ${tableOrView.name}`,
|
||||||
})
|
})
|
||||||
.gridDesktopColSpan(1, 7)
|
.gridDesktopColSpan(1, 7)
|
||||||
.gridDesktopRowSpan(1, 3)
|
.gridDesktopRowSpan(1, 3)
|
||||||
|
|
|
@ -1048,6 +1048,7 @@
|
||||||
},
|
},
|
||||||
"text": {
|
"text": {
|
||||||
"name": "Paragraph",
|
"name": "Paragraph",
|
||||||
|
"deprecated": true,
|
||||||
"description": "A component for displaying paragraph text.",
|
"description": "A component for displaying paragraph text.",
|
||||||
"icon": "TextParagraph",
|
"icon": "TextParagraph",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
|
@ -1171,6 +1172,7 @@
|
||||||
},
|
},
|
||||||
"heading": {
|
"heading": {
|
||||||
"name": "Headline",
|
"name": "Headline",
|
||||||
|
"deprecated": true,
|
||||||
"icon": "TextBold",
|
"icon": "TextBold",
|
||||||
"description": "A component for displaying heading text",
|
"description": "A component for displaying heading text",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
|
@ -7582,6 +7584,15 @@
|
||||||
"name": "Table",
|
"name": "Table",
|
||||||
"icon": "Table",
|
"icon": "Table",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"cssVariables": [{
|
||||||
|
"label": "Header color",
|
||||||
|
"variable": "--custom-header-cell-background",
|
||||||
|
"type": "color"
|
||||||
|
}, {
|
||||||
|
"label": "Stripe color",
|
||||||
|
"variable": "--custom-stripe-cell-background",
|
||||||
|
"type": "color"
|
||||||
|
}],
|
||||||
"size": {
|
"size": {
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 400
|
"height": 400
|
||||||
|
@ -7689,7 +7700,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "High contrast",
|
"label": "Striped rows",
|
||||||
"key": "stripeRows",
|
"key": "stripeRows",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
@ -7990,5 +8001,62 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"textv2": {
|
||||||
|
"name": "Text",
|
||||||
|
"description": "A component for displaying text",
|
||||||
|
"icon": "Text",
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 24
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text/multiline",
|
||||||
|
"label": "Text (Markdown supported)",
|
||||||
|
"key": "text",
|
||||||
|
"wide": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Alignment",
|
||||||
|
"key": "align",
|
||||||
|
"defaultValue": "left",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Left",
|
||||||
|
"value": "left",
|
||||||
|
"barIcon": "TextAlignLeft",
|
||||||
|
"barTitle": "Align left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Center",
|
||||||
|
"value": "center",
|
||||||
|
"barIcon": "TextAlignCenter",
|
||||||
|
"barTitle": "Align center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Right",
|
||||||
|
"value": "right",
|
||||||
|
"barIcon": "TextAlignRight",
|
||||||
|
"barTitle": "Align right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Justify",
|
||||||
|
"value": "justify",
|
||||||
|
"barIcon": "TextAlignJustify",
|
||||||
|
"barTitle": "Justify text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "Color",
|
||||||
|
"key": "color",
|
||||||
|
"showInBar": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { Heading, Icon, clickOutside } from "@budibase/bbui"
|
import { Heading, Icon, clickOutside } from "@budibase/bbui"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
import NavItem from "./NavItem.svelte"
|
import NavItem from "./NavItem.svelte"
|
||||||
|
import UserMenu from "./UserMenu.svelte"
|
||||||
|
|
||||||
const sdk = getContext("sdk")
|
const sdk = getContext("sdk")
|
||||||
const {
|
const {
|
||||||
|
@ -13,7 +14,6 @@
|
||||||
builderStore,
|
builderStore,
|
||||||
sidePanelStore,
|
sidePanelStore,
|
||||||
modalStore,
|
modalStore,
|
||||||
appStore,
|
|
||||||
} = sdk
|
} = sdk
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const navStateStore = writable({})
|
const navStateStore = writable({})
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
export let navWidth
|
export let navWidth
|
||||||
export let pageWidth
|
export let pageWidth
|
||||||
export let logoLinkUrl
|
export let logoLinkUrl
|
||||||
|
export let logoHeight
|
||||||
export let openLogoLinkInNewTab
|
export let openLogoLinkInNewTab
|
||||||
export let textAlign
|
export let textAlign
|
||||||
export let embedded = false
|
export let embedded = false
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
$: navStyle = getNavStyle(
|
$: navStyle = getNavStyle(
|
||||||
navBackground,
|
navBackground,
|
||||||
navTextColor,
|
navTextColor,
|
||||||
|
logoHeight,
|
||||||
$context.device.width,
|
$context.device.width,
|
||||||
$context.device.height
|
$context.device.height
|
||||||
)
|
)
|
||||||
|
@ -156,11 +158,6 @@
|
||||||
return !url.startsWith("http") ? `http://${url}` : url
|
return !url.startsWith("http") ? `http://${url}` : url
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateToPortal = () => {
|
|
||||||
if ($builderStore.inBuilder) return
|
|
||||||
window.location.href = "/builder/apps"
|
|
||||||
}
|
|
||||||
|
|
||||||
const getScreenXOffset = (navigation, mobile) => {
|
const getScreenXOffset = (navigation, mobile) => {
|
||||||
if (navigation !== "Left") {
|
if (navigation !== "Left") {
|
||||||
return 0
|
return 0
|
||||||
|
@ -175,7 +172,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNavStyle = (backgroundColor, textColor, width, height) => {
|
const getNavStyle = (
|
||||||
|
backgroundColor,
|
||||||
|
textColor,
|
||||||
|
logoHeight,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
) => {
|
||||||
let style = `--width:${width}px; --height:${height}px;`
|
let style = `--width:${width}px; --height:${height}px;`
|
||||||
if (backgroundColor) {
|
if (backgroundColor) {
|
||||||
style += `--navBackground:${backgroundColor};`
|
style += `--navBackground:${backgroundColor};`
|
||||||
|
@ -183,6 +186,7 @@
|
||||||
if (textColor) {
|
if (textColor) {
|
||||||
style += `--navTextColor:${textColor};`
|
style += `--navTextColor:${textColor};`
|
||||||
}
|
}
|
||||||
|
style += `--logoHeight:${logoHeight || 24}px;`
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,13 +271,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if !embedded}
|
{#if !embedded}
|
||||||
<div class="portal">
|
<div class="user top">
|
||||||
<Icon
|
<UserMenu compact />
|
||||||
hoverable
|
|
||||||
name="Apps"
|
|
||||||
on:click={navigateToPortal}
|
|
||||||
disabled={$appStore.isDevApp}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -297,13 +296,11 @@
|
||||||
{navStateStore}
|
{navStateStore}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="close">
|
</div>
|
||||||
<Icon
|
{/if}
|
||||||
hoverable
|
{#if !embedded}
|
||||||
name="Close"
|
<div class="user left">
|
||||||
on:click={() => (mobileOpen = false)}
|
<UserMenu />
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -394,21 +391,15 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
.layout--top .nav-wrapper {
|
|
||||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
}
|
|
||||||
.layout--left .nav-wrapper {
|
|
||||||
border-right: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 24px 32px 20px 32px;
|
padding: 18px 32px 18px 32px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
.nav :global(.spectrum-Icon) {
|
.nav :global(.spectrum-Icon) {
|
||||||
color: var(--navTextColor);
|
color: var(--navTextColor);
|
||||||
|
@ -522,7 +513,7 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
.logo img {
|
.logo img {
|
||||||
height: 36px;
|
height: var(--logoHeight);
|
||||||
}
|
}
|
||||||
.logo :global(h1) {
|
.logo :global(h1) {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -532,11 +523,8 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.portal {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
.links {
|
.links {
|
||||||
|
flex: 1 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -544,45 +532,38 @@
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xl);
|
||||||
margin-top: var(--spacing-xl);
|
margin-top: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.close {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-xl);
|
|
||||||
right: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.mobile-click-handler {
|
.mobile-click-handler {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Left overrides for both desktop and mobile */
|
||||||
|
.nav--left {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Desktop nav overrides */
|
/* Desktop nav overrides */
|
||||||
.desktop.layout--left .layout-body {
|
.desktop.layout--left .layout-body {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.desktop.layout--left .nav-wrapper {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.desktop.layout--left .main-wrapper {
|
.desktop.layout--left .main-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.desktop.layout--left .links {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desktop .nav--left {
|
.desktop .nav--left {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop .nav--left .links {
|
.desktop .nav--left .links {
|
||||||
margin-top: var(--spacing-m);
|
margin-top: var(--spacing-m);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
.desktop .nav--left .link {
|
.desktop .nav--left .user.top,
|
||||||
font-size: var(--spectrum-global-dimension-font-size-150);
|
.desktop .nav--top .user.left {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile nav overrides */
|
/* Mobile nav overrides */
|
||||||
|
@ -591,13 +572,9 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
box-shadow: 0 0 8px -1px rgba(0, 0, 0, 0.075);
|
box-shadow: 0 0 8px -1px rgba(0, 0, 0, 0.075);
|
||||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-right: none;
|
|
||||||
}
|
}
|
||||||
|
.mobile .user.left {
|
||||||
/* Show close button in drawer */
|
display: none;
|
||||||
.mobile .close {
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force standard top bar */
|
/* Force standard top bar */
|
||||||
|
@ -635,6 +612,7 @@
|
||||||
left: -250px;
|
left: -250px;
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
max-width: 75%;
|
||||||
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
|
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
|
||||||
height: var(--height);
|
height: var(--height);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -645,10 +623,10 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
.mobile .link {
|
.mobile .links :global(a) {
|
||||||
width: calc(100% - 30px);
|
flex: 0 0 auto;
|
||||||
font-size: 120%;
|
|
||||||
}
|
}
|
||||||
.mobile .links.visible {
|
.mobile .links.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import active from "svelte-spa-router/active"
|
import active from "svelte-spa-router/active"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
|
import { builderStore, screenStore } from "@/stores"
|
||||||
|
|
||||||
export let type
|
export let type
|
||||||
export let url
|
export let url
|
||||||
|
@ -16,7 +17,16 @@
|
||||||
|
|
||||||
let renderKey
|
let renderKey
|
||||||
|
|
||||||
$: expanded = !!$navStateStore[text]
|
$: isBuilderActive = testUrl => {
|
||||||
|
return (
|
||||||
|
$builderStore.inBuilder &&
|
||||||
|
testUrl &&
|
||||||
|
testUrl === $screenStore.activeScreen?.routing?.route
|
||||||
|
)
|
||||||
|
}
|
||||||
|
$: builderActive = isBuilderActive(url)
|
||||||
|
$: containsActiveLink = (subLinks || []).some(x => isBuilderActive(x.url))
|
||||||
|
$: expanded = !!$navStateStore[text] || containsActiveLink
|
||||||
$: renderLeftNav = leftNav || mobile
|
$: renderLeftNav = leftNav || mobile
|
||||||
$: icon = !renderLeftNav || expanded ? "ChevronDown" : "ChevronRight"
|
$: icon = !renderLeftNav || expanded ? "ChevronDown" : "ChevronRight"
|
||||||
|
|
||||||
|
@ -47,7 +57,7 @@
|
||||||
href="#{url}"
|
href="#{url}"
|
||||||
on:click={onClickLink}
|
on:click={onClickLink}
|
||||||
use:active={url}
|
use:active={url}
|
||||||
class:active={false}
|
class:builderActive
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
|
@ -73,6 +83,9 @@
|
||||||
href="#{subLink.url}"
|
href="#{subLink.url}"
|
||||||
on:click={onClickLink}
|
on:click={onClickLink}
|
||||||
use:active={subLink.url}
|
use:active={subLink.url}
|
||||||
|
class:active={false}
|
||||||
|
class:builderActive={isBuilderActive(subLink.url)}
|
||||||
|
class="sublink"
|
||||||
>
|
>
|
||||||
{subLink.text}
|
{subLink.text}
|
||||||
</a>
|
</a>
|
||||||
|
@ -91,22 +104,29 @@
|
||||||
<style>
|
<style>
|
||||||
/* Generic styles */
|
/* Generic styles */
|
||||||
a,
|
a,
|
||||||
|
.dropdown .text {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
a,
|
||||||
.text span {
|
.text span {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
color: var(--navTextColor);
|
color: var(--navTextColor);
|
||||||
font-size: var(--spectrum-global-dimension-font-size-200);
|
font-size: var(--spectrum-global-dimension-font-size-150);
|
||||||
transition: opacity 130ms ease-out;
|
transition: opacity 130ms ease-out;
|
||||||
font-weight: 600;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
a.active {
|
a.active:not(.sublink),
|
||||||
|
a.builderActive:not(.sublink),
|
||||||
|
.dropdown.left a.sublink.active,
|
||||||
|
.dropdown.left a.sublink.builderActive {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
a:hover,
|
a:hover,
|
||||||
.dropdown:not(.left.expanded):hover .text,
|
.text:hover span {
|
||||||
.text:hover {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
@ -167,8 +187,4 @@
|
||||||
.dropdown.dropdown.left.expanded .sublinks {
|
.dropdown.dropdown.left.expanded .sublinks {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
.dropdown.left a {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,105 +1,44 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { MarkdownViewer } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let text: string = ""
|
||||||
|
export let color: string | undefined = undefined
|
||||||
|
export let align: "left" | "center" | "right" | "justify" = "left"
|
||||||
|
|
||||||
const { styleable, builderStore } = getContext("sdk")
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
const { styleable } = getContext("sdk")
|
||||||
|
|
||||||
export let text
|
// Add in certain settings to styles
|
||||||
export let color
|
$: styles = enrichStyles($component.styles, color, align)
|
||||||
export let align
|
|
||||||
export let bold
|
|
||||||
export let italic
|
|
||||||
export let underline
|
|
||||||
export let size
|
|
||||||
|
|
||||||
let node
|
const enrichStyles = (
|
||||||
let touched = false
|
styles: any,
|
||||||
|
colorStyle: typeof color,
|
||||||
$: $component.editing && node?.focus()
|
alignStyle: typeof align
|
||||||
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
) => {
|
||||||
$: componentText = getComponentText(text, $builderStore, $component)
|
let additions: Record<string, string> = {
|
||||||
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
"text-align": alignStyle,
|
||||||
$: alignClass = `align--${align || "left"}`
|
|
||||||
|
|
||||||
// Add color styles to main styles object, otherwise the styleable helper
|
|
||||||
// overrides the color when it's passed as inline style.
|
|
||||||
$: styles = enrichStyles($component.styles, color)
|
|
||||||
|
|
||||||
const getComponentText = (text, builderState, componentState) => {
|
|
||||||
if (!builderState.inBuilder || componentState.editing) {
|
|
||||||
return text || ""
|
|
||||||
}
|
}
|
||||||
return text || componentState.name || "Placeholder text"
|
if (colorStyle) {
|
||||||
}
|
additions.color = colorStyle
|
||||||
|
|
||||||
const enrichStyles = (styles, color) => {
|
|
||||||
if (!color) {
|
|
||||||
return styles
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...styles,
|
...styles,
|
||||||
normal: {
|
normal: {
|
||||||
...styles?.normal,
|
...styles.normal,
|
||||||
color,
|
...additions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert contenteditable HTML to text and save
|
|
||||||
const updateText = e => {
|
|
||||||
if (touched) {
|
|
||||||
builderStore.actions.updateProp("text", e.target.textContent)
|
|
||||||
}
|
|
||||||
touched = false
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key $component.editing}
|
<div use:styleable={styles}>
|
||||||
<p
|
<MarkdownViewer value={text} />
|
||||||
bind:this={node}
|
</div>
|
||||||
contenteditable={$component.editing}
|
|
||||||
use:styleable={styles}
|
|
||||||
class:placeholder
|
|
||||||
class:bold
|
|
||||||
class:italic
|
|
||||||
class:underline
|
|
||||||
class="spectrum-Body {sizeClass} {alignClass}"
|
|
||||||
on:blur={$component.editing ? updateText : null}
|
|
||||||
on:input={() => (touched = true)}
|
|
||||||
>
|
|
||||||
{componentText}
|
|
||||||
</p>
|
|
||||||
{/key}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p {
|
div :global(img) {
|
||||||
display: inline-block;
|
max-width: 100%;
|
||||||
white-space: pre-wrap;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.placeholder {
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--spectrum-global-color-gray-600);
|
|
||||||
}
|
|
||||||
.bold {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.underline {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.align--left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.align--center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.align--right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.align--justify {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { ActionMenu, Icon, MenuItem, Modal } from "@budibase/bbui"
|
||||||
|
import {
|
||||||
|
UserAvatar,
|
||||||
|
ProfileModal,
|
||||||
|
ChangePasswordModal,
|
||||||
|
} from "@budibase/frontend-core"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { type User, type ContextUser, isSSOUser } from "@budibase/types"
|
||||||
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
import { API } from "@/api"
|
||||||
|
|
||||||
|
export let compact: boolean = false
|
||||||
|
|
||||||
|
const { authStore, environmentStore, notificationStore, appStore } =
|
||||||
|
getContext("sdk")
|
||||||
|
|
||||||
|
let profileModal: any
|
||||||
|
let changePasswordModal: any
|
||||||
|
|
||||||
|
$: text = getText($authStore)
|
||||||
|
$: isBuilder = sdk.users.hasBuilderPermissions($authStore)
|
||||||
|
$: isSSO = $authStore != null && isSSOUser($authStore)
|
||||||
|
$: isOwner = $authStore?.accountPortalAccess && $environmentStore.cloud
|
||||||
|
$: embedded = $appStore.embedded || $appStore.inIframe
|
||||||
|
|
||||||
|
const getText = (user?: User | ContextUser): string => {
|
||||||
|
if (!user) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (user.firstName) {
|
||||||
|
let text = user.firstName
|
||||||
|
if (user.lastName) {
|
||||||
|
text += ` ${user.lastName}`
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
} else {
|
||||||
|
return user.email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToPortal = () => {
|
||||||
|
window.location.href = isBuilder ? "/builder/portal/apps" : "/builder/apps"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $authStore}
|
||||||
|
<ActionMenu align={compact ? "right" : "left"}>
|
||||||
|
<svelte:fragment slot="control">
|
||||||
|
<div class="container">
|
||||||
|
<UserAvatar user={$authStore} size="M" showTooltip={false} />
|
||||||
|
{#if !compact}
|
||||||
|
<div class="text">
|
||||||
|
<div class="name">
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Icon size="L" name="ChevronDown" />
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<MenuItem icon="UserEdit" on:click={() => profileModal?.show()}>
|
||||||
|
My profile
|
||||||
|
</MenuItem>
|
||||||
|
{#if !isSSO}
|
||||||
|
<MenuItem
|
||||||
|
icon="LockClosed"
|
||||||
|
on:click={() => {
|
||||||
|
if (isOwner) {
|
||||||
|
window.location.href = `${$environmentStore.accountPortalUrl}/portal/account`
|
||||||
|
} else {
|
||||||
|
changePasswordModal?.show()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update password
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<MenuItem icon="Apps" on:click={goToPortal} disabled={embedded}>
|
||||||
|
Go to portal
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="LogOut"
|
||||||
|
on:click={authStore.actions.logOut}
|
||||||
|
disabled={embedded}
|
||||||
|
>
|
||||||
|
Log out
|
||||||
|
</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
|
|
||||||
|
<Modal bind:this={profileModal}>
|
||||||
|
<ProfileModal
|
||||||
|
{API}
|
||||||
|
user={$authStore}
|
||||||
|
on:save={() => authStore.actions.fetchUser()}
|
||||||
|
notifySuccess={notificationStore.actions.success}
|
||||||
|
notifyError={notificationStore.actions.error}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
<Modal bind:this={changePasswordModal}>
|
||||||
|
<ChangePasswordModal
|
||||||
|
{API}
|
||||||
|
passwordMinLength={$environmentStore.passwordMinLength}
|
||||||
|
on:save={() => authStore.actions.logOut()}
|
||||||
|
notifySuccess={notificationStore.actions.success}
|
||||||
|
notifyError={notificationStore.actions.error}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
transition: filter 130ms ease-out;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--navTextColor);
|
||||||
|
display: flex;
|
||||||
|
margin-left: var(--spacing-xs);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.container:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
filter: brightness(110%);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -125,9 +125,9 @@
|
||||||
order={0}
|
order={0}
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="heading"
|
type="textv2"
|
||||||
props={{
|
props={{
|
||||||
text: title,
|
text: title ? `## ${title}` : "",
|
||||||
}}
|
}}
|
||||||
order={0}
|
order={0}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -148,7 +148,10 @@
|
||||||
}}
|
}}
|
||||||
order={0}
|
order={0}
|
||||||
>
|
>
|
||||||
<BlockComponent type="heading" props={{ text: step.title }} />
|
<BlockComponent
|
||||||
|
type="textv2"
|
||||||
|
props={{ text: `## ${step.title}` }}
|
||||||
|
/>
|
||||||
{#if buttonPosition === "top"}
|
{#if buttonPosition === "top"}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="buttongroup"
|
type="buttongroup"
|
||||||
|
@ -157,7 +160,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
<BlockComponent type="text" props={{ text: step.desc }} order={1} />
|
<BlockComponent type="textv2" props={{ text: step.desc }} order={1} />
|
||||||
|
|
||||||
<BlockComponent type="container" order={2}>
|
<BlockComponent type="container" order={2}>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -190,7 +190,7 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="text"
|
type="textv2"
|
||||||
order={1}
|
order={1}
|
||||||
props={{
|
props={{
|
||||||
text: "Select a row to view its fields",
|
text: "Select a row to view its fields",
|
||||||
|
|
|
@ -74,11 +74,11 @@
|
||||||
order={0}
|
order={0}
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="heading"
|
type="textv2"
|
||||||
props={{ text: title || "" }}
|
props={{ text: title ? `## ${title}` : "" }}
|
||||||
order={0}
|
order={0}
|
||||||
/>
|
/>
|
||||||
{#if buttonPosition == "top"}
|
{#if buttonPosition === "top"}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="buttongroup"
|
type="buttongroup"
|
||||||
props={{
|
props={{
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
{/if}
|
{/if}
|
||||||
{#if description}
|
{#if description}
|
||||||
<BlockComponent type="text" props={{ text: description }} order={1} />
|
<BlockComponent type="textv2" props={{ text: description }} order={1} />
|
||||||
{/if}
|
{/if}
|
||||||
<BlockComponent type="container">
|
<BlockComponent type="container">
|
||||||
<div class="form-block fields" class:mobile={$context.device.mobile}>
|
<div class="form-block fields" class:mobile={$context.device.mobile}>
|
||||||
|
|
|
@ -184,9 +184,9 @@
|
||||||
order={0}
|
order={0}
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="heading"
|
type="textv2"
|
||||||
props={{
|
props={{
|
||||||
text: title,
|
text: title ? `## ${title}` : "",
|
||||||
}}
|
}}
|
||||||
order={0}
|
order={0}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const { styleable, builderStore } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
|
export let text
|
||||||
|
export let color
|
||||||
|
export let align
|
||||||
|
export let bold
|
||||||
|
export let italic
|
||||||
|
export let underline
|
||||||
|
export let size
|
||||||
|
|
||||||
|
let node
|
||||||
|
let touched = false
|
||||||
|
|
||||||
|
$: $component.editing && node?.focus()
|
||||||
|
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||||
|
$: componentText = getComponentText(text, $builderStore, $component)
|
||||||
|
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
||||||
|
$: alignClass = `align--${align || "left"}`
|
||||||
|
|
||||||
|
// Add color styles to main styles object, otherwise the styleable helper
|
||||||
|
// overrides the color when it's passed as inline style.
|
||||||
|
$: styles = enrichStyles($component.styles, color)
|
||||||
|
|
||||||
|
const getComponentText = (text, builderState, componentState) => {
|
||||||
|
if (!builderState.inBuilder || componentState.editing) {
|
||||||
|
return text || ""
|
||||||
|
}
|
||||||
|
return text || componentState.name || "Placeholder text"
|
||||||
|
}
|
||||||
|
|
||||||
|
const enrichStyles = (styles, color) => {
|
||||||
|
if (!color) {
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
normal: {
|
||||||
|
...styles?.normal,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert contenteditable HTML to text and save
|
||||||
|
const updateText = e => {
|
||||||
|
if (touched) {
|
||||||
|
builderStore.actions.updateProp("text", e.target.textContent)
|
||||||
|
}
|
||||||
|
touched = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key $component.editing}
|
||||||
|
<p
|
||||||
|
bind:this={node}
|
||||||
|
contenteditable={$component.editing}
|
||||||
|
use:styleable={styles}
|
||||||
|
class:placeholder
|
||||||
|
class:bold
|
||||||
|
class:italic
|
||||||
|
class:underline
|
||||||
|
class="spectrum-Body {sizeClass} {alignClass}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
|
on:input={() => (touched = true)}
|
||||||
|
>
|
||||||
|
{componentText}
|
||||||
|
</p>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
.bold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.align--left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.align--center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.align--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.align--justify {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -20,10 +20,8 @@ export { default as screenslot } from "./ScreenSlot.svelte"
|
||||||
export { default as button } from "./Button.svelte"
|
export { default as button } from "./Button.svelte"
|
||||||
export { default as buttongroup } from "./ButtonGroup.svelte"
|
export { default as buttongroup } from "./ButtonGroup.svelte"
|
||||||
export { default as repeater } from "./Repeater.svelte"
|
export { default as repeater } from "./Repeater.svelte"
|
||||||
export { default as text } from "./Text.svelte"
|
|
||||||
export { default as layout } from "./Layout.svelte"
|
export { default as layout } from "./Layout.svelte"
|
||||||
export { default as link } from "./Link.svelte"
|
export { default as link } from "./Link.svelte"
|
||||||
export { default as heading } from "./Heading.svelte"
|
|
||||||
export { default as image } from "./Image.svelte"
|
export { default as image } from "./Image.svelte"
|
||||||
export { default as embed } from "./Embed.svelte"
|
export { default as embed } from "./Embed.svelte"
|
||||||
export { default as icon } from "./Icon.svelte"
|
export { default as icon } from "./Icon.svelte"
|
||||||
|
@ -37,6 +35,7 @@ export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
|
||||||
export { default as sidepanel } from "./SidePanel.svelte"
|
export { default as sidepanel } from "./SidePanel.svelte"
|
||||||
export { default as modal } from "./Modal.svelte"
|
export { default as modal } from "./Modal.svelte"
|
||||||
export { default as gridblock } from "./GridBlock.svelte"
|
export { default as gridblock } from "./GridBlock.svelte"
|
||||||
|
export { default as textv2 } from "./Text.svelte"
|
||||||
export * from "./charts"
|
export * from "./charts"
|
||||||
export * from "./forms"
|
export * from "./forms"
|
||||||
export * from "./blocks"
|
export * from "./blocks"
|
||||||
|
@ -50,3 +49,5 @@ export { default as cardhorizontal } from "./deprecated/CardHorizontal.svelte"
|
||||||
export { default as stackedlist } from "./deprecated/StackedList.svelte"
|
export { default as stackedlist } from "./deprecated/StackedList.svelte"
|
||||||
export { default as card } from "./deprecated/Card.svelte"
|
export { default as card } from "./deprecated/Card.svelte"
|
||||||
export { default as section } from "./deprecated/Section.svelte"
|
export { default as section } from "./deprecated/Section.svelte"
|
||||||
|
export { default as text } from "./deprecated/Text.svelte"
|
||||||
|
export { default as heading } from "./deprecated/Heading.svelte"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Writable } from "svelte"
|
import { Writable } from "svelte"
|
||||||
import { Component, FieldGroupContext, FormContext, SDK } from "@/types"
|
import { Component, FieldGroupContext, FormContext } from "@/types"
|
||||||
import { Readable } from "svelte/store"
|
import { Readable } from "svelte/store"
|
||||||
|
import { SDK } from "@/index.ts"
|
||||||
|
|
||||||
declare module "svelte" {
|
declare module "svelte" {
|
||||||
export function getContext(key: "sdk"): SDK
|
export function getContext(key: "sdk"): SDK
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import ClientApp from "./components/ClientApp.svelte"
|
import ClientApp from "./components/ClientApp.svelte"
|
||||||
import UpdatingApp from "./components/UpdatingApp.svelte"
|
import UpdatingApp from "./components/UpdatingApp.svelte"
|
||||||
import {
|
import {
|
||||||
|
authStore,
|
||||||
builderStore,
|
builderStore,
|
||||||
appStore,
|
appStore,
|
||||||
blockStore,
|
blockStore,
|
||||||
|
@ -11,6 +12,7 @@ import {
|
||||||
hoverStore,
|
hoverStore,
|
||||||
stateStore,
|
stateStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
|
notificationStore,
|
||||||
} from "@/stores"
|
} from "@/stores"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { initWebsocket } from "@/websocket"
|
import { initWebsocket } from "@/websocket"
|
||||||
|
@ -26,6 +28,8 @@ import {
|
||||||
UIComponentError,
|
UIComponentError,
|
||||||
CustomComponent,
|
CustomComponent,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { ActionTypes } from "@/constants"
|
||||||
|
import { APIClient } from "@budibase/frontend-core"
|
||||||
|
|
||||||
// Provide svelte and svelte/internal as globals for custom components
|
// Provide svelte and svelte/internal as globals for custom components
|
||||||
import * as svelte from "svelte"
|
import * as svelte from "svelte"
|
||||||
|
@ -37,7 +41,6 @@ window.svelte = svelte
|
||||||
// Initialise spectrum icons
|
// Initialise spectrum icons
|
||||||
// eslint-disable-next-line local-rules/no-budibase-imports
|
// eslint-disable-next-line local-rules/no-budibase-imports
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||||
|
|
||||||
loadSpectrumIcons()
|
loadSpectrumIcons()
|
||||||
|
|
||||||
// Extend global window scope
|
// Extend global window scope
|
||||||
|
@ -74,6 +77,20 @@ declare global {
|
||||||
|
|
||||||
export type Context = Readable<Record<string, any>>
|
export type Context = Readable<Record<string, any>>
|
||||||
|
|
||||||
|
export interface SDK {
|
||||||
|
API: APIClient
|
||||||
|
styleable: any
|
||||||
|
Provider: any
|
||||||
|
ActionTypes: typeof ActionTypes
|
||||||
|
fetchDatasourceSchema: any
|
||||||
|
generateGoldenSample: any
|
||||||
|
builderStore: typeof builderStore
|
||||||
|
authStore: typeof authStore
|
||||||
|
notificationStore: typeof notificationStore
|
||||||
|
environmentStore: typeof environmentStore
|
||||||
|
appStore: typeof appStore
|
||||||
|
}
|
||||||
|
|
||||||
let app: ClientApp
|
let app: ClientApp
|
||||||
|
|
||||||
const loadBudibase = async () => {
|
const loadBudibase = async () => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ const initialState = {
|
||||||
isDevApp: false,
|
isDevApp: false,
|
||||||
clientLoadTime: window.INIT_TIME ? Date.now() - window.INIT_TIME : null,
|
clientLoadTime: window.INIT_TIME ? Date.now() - window.INIT_TIME : null,
|
||||||
embedded: false,
|
embedded: false,
|
||||||
|
inIframe: window.self !== window.top,
|
||||||
}
|
}
|
||||||
|
|
||||||
const createAppStore = () => {
|
const createAppStore = () => {
|
||||||
|
|
|
@ -1,38 +1,52 @@
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import {
|
||||||
|
AppSelfResponse,
|
||||||
|
ContextUserMetadata,
|
||||||
|
GetGlobalSelfResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
type AuthState = ContextUserMetadata | GetGlobalSelfResponse | undefined
|
||||||
|
|
||||||
const createAuthStore = () => {
|
const createAuthStore = () => {
|
||||||
const store = writable<{
|
const store = writable<AuthState>()
|
||||||
csrfToken?: string
|
|
||||||
} | null>(null)
|
const hasAppSelfUser = (
|
||||||
|
user: AppSelfResponse | null
|
||||||
|
): user is ContextUserMetadata => {
|
||||||
|
return user != null && "_id" in user
|
||||||
|
}
|
||||||
|
|
||||||
// Fetches the user object if someone is logged in and has reloaded the page
|
// Fetches the user object if someone is logged in and has reloaded the page
|
||||||
const fetchUser = async () => {
|
const fetchUser = async () => {
|
||||||
let globalSelf = null
|
let globalSelf, appSelf
|
||||||
let appSelf = null
|
|
||||||
|
|
||||||
// First try and get the global user, to see if we are logged in at all
|
// First try and get the global user, to see if we are logged in at all
|
||||||
try {
|
try {
|
||||||
globalSelf = await API.fetchBuilderSelf()
|
globalSelf = await API.fetchBuilderSelf()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
store.set(null)
|
store.set(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then try and get the user for this app to provide via context
|
// Then try and get the user for this app to provide via context
|
||||||
try {
|
try {
|
||||||
appSelf = await API.fetchSelf()
|
const res = await API.fetchSelf()
|
||||||
|
if (hasAppSelfUser(res)) {
|
||||||
|
appSelf = res
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Swallow
|
// Swallow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the app self if present, otherwise fallback to the global self
|
// Use the app self if present, otherwise fallback to the global self
|
||||||
store.set(appSelf || globalSelf || null)
|
store.set(appSelf || globalSelf)
|
||||||
}
|
}
|
||||||
|
|
||||||
const logOut = async () => {
|
const logOut = async () => {
|
||||||
try {
|
try {
|
||||||
await API.logOut()
|
await API.logOut()
|
||||||
|
window.location.href = "/"
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import type { GetEnvironmentResponse } from "@budibase/types"
|
||||||
|
|
||||||
const initialState = {
|
interface EnvironmentState extends GetEnvironmentResponse {
|
||||||
loaded: false,
|
loaded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: EnvironmentState = {
|
||||||
|
multiTenancy: false,
|
||||||
|
offlineMode: false,
|
||||||
cloud: false,
|
cloud: false,
|
||||||
|
disableAccountPortal: false,
|
||||||
|
isDev: false,
|
||||||
|
maintenance: [],
|
||||||
|
loaded: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEnvironmentStore = () => {
|
const createEnvironmentStore = () => {
|
||||||
const store = writable(initialState)
|
const store = writable<EnvironmentState>(initialState)
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
fetchEnvironment: async () => {
|
fetchEnvironment: async () => {
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./components"
|
export * from "./components"
|
||||||
export * from "./fields"
|
export * from "./fields"
|
||||||
export * from "./forms"
|
export * from "./forms"
|
||||||
export * from "./sdk"
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { ActionTypes } from "@/constants"
|
|
||||||
import { APIClient } from "@budibase/frontend-core"
|
|
||||||
import { Readable } from "svelte/store"
|
|
||||||
|
|
||||||
export interface SDK {
|
|
||||||
API: APIClient
|
|
||||||
styleable: any
|
|
||||||
Provider: any
|
|
||||||
ActionTypes: typeof ActionTypes
|
|
||||||
fetchDatasourceSchema: any
|
|
||||||
generateGoldenSample: any
|
|
||||||
builderStore: Readable<{
|
|
||||||
inBuilder: boolean
|
|
||||||
}> & {
|
|
||||||
actions: {
|
|
||||||
highlightSetting: (key: string) => void
|
|
||||||
addParentComponent: (
|
|
||||||
componentId: string,
|
|
||||||
fullAncestorType: string
|
|
||||||
) => void
|
|
||||||
updateProp: (key: string, value: any) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { ModalContent, Body, notifications } from "@budibase/bbui"
|
||||||
|
import PasswordRepeatInput from "./PasswordRepeatInput.svelte"
|
||||||
|
import type { APIClient } from "@budibase/frontend-core"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let API: APIClient
|
||||||
|
export let passwordMinLength: string | undefined = undefined
|
||||||
|
export let notifySuccess = notifications.success
|
||||||
|
export let notifyError = notifications.error
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let password: string = ""
|
||||||
|
let error: string = ""
|
||||||
|
|
||||||
|
const updatePassword = async () => {
|
||||||
|
try {
|
||||||
|
await API.updateSelf({ password })
|
||||||
|
notifySuccess("Password changed successfully")
|
||||||
|
dispatch("save")
|
||||||
|
} catch (error) {
|
||||||
|
notifyError("Failed to update password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (evt: KeyboardEvent) => {
|
||||||
|
if (evt.key === "Enter" && !error && password) {
|
||||||
|
updatePassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeydown} />
|
||||||
|
<ModalContent
|
||||||
|
title="Update password"
|
||||||
|
confirmText="Update password"
|
||||||
|
onConfirm={updatePassword}
|
||||||
|
disabled={!!error || !password}
|
||||||
|
>
|
||||||
|
<Body size="S">Enter your new password below.</Body>
|
||||||
|
<PasswordRepeatInput bind:password bind:error minLength={passwordMinLength} />
|
||||||
|
</ModalContent>
|
|
@ -1,20 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { FancyForm, FancyInput } from "@budibase/bbui"
|
import { FancyForm, FancyInput } from "@budibase/bbui"
|
||||||
import {
|
import { createValidationStore, requiredValidator } from "../utils/validation"
|
||||||
createValidationStore,
|
|
||||||
requiredValidator,
|
|
||||||
} from "@/helpers/validation"
|
|
||||||
import { admin } from "@/stores/portal"
|
|
||||||
|
|
||||||
export let password
|
export let password
|
||||||
export let passwordForm
|
|
||||||
export let error
|
export let error
|
||||||
|
export let minLength = "12"
|
||||||
$: passwordMinLength = $admin.passwordMinLength ?? 12
|
|
||||||
|
|
||||||
const validatePassword = value => {
|
const validatePassword = value => {
|
||||||
if (!value || value.length < passwordMinLength) {
|
if (!value || value.length < minLength) {
|
||||||
return `Please enter at least ${passwordMinLength} characters. We recommend using machine generated or random passwords.`
|
return `Please enter at least ${minLength} characters. We recommend using machine generated or random passwords.`
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -41,7 +35,7 @@
|
||||||
firstPasswordError
|
firstPasswordError
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FancyForm bind:this={passwordForm}>
|
<FancyForm>
|
||||||
<FancyInput
|
<FancyInput
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||||
|
import type { User, ContextUser } from "@budibase/types"
|
||||||
|
import type { APIClient } from "@budibase/frontend-core"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let user: User | ContextUser | undefined = undefined
|
||||||
|
export let API: APIClient
|
||||||
|
export let notifySuccess = notifications.success
|
||||||
|
export let notifyError = notifications.error
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const values = writable({
|
||||||
|
firstName: user?.firstName,
|
||||||
|
lastName: user?.lastName,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateInfo = async () => {
|
||||||
|
try {
|
||||||
|
await API.updateSelf($values)
|
||||||
|
notifySuccess("Information updated successfully")
|
||||||
|
dispatch("save")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
notifyError("Failed to update information")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent title="My profile" confirmText="Save" onConfirm={updateInfo}>
|
||||||
|
<Body size="S">
|
||||||
|
Personalise the platform by adding your first name and last name.
|
||||||
|
</Body>
|
||||||
|
<Input disabled value={user?.email || ""} label="Email" />
|
||||||
|
<Input bind:value={$values.firstName} label="First name" />
|
||||||
|
<Input bind:value={$values.lastName} label="Last name" />
|
||||||
|
</ModalContent>
|
|
@ -56,7 +56,7 @@
|
||||||
rowIdx={row?.__idx}
|
rowIdx={row?.__idx}
|
||||||
metadata={row?.__metadata?.row}
|
metadata={row?.__metadata?.row}
|
||||||
>
|
>
|
||||||
<div class="gutter">
|
<div class="gutter" class:selectable={$config.canSelectRows}>
|
||||||
{#if $$slots.default}
|
{#if $$slots.default}
|
||||||
<slot />
|
<slot />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -116,12 +116,9 @@
|
||||||
margin: 3px 0 0 0;
|
margin: 3px 0 0 0;
|
||||||
}
|
}
|
||||||
.number {
|
.number {
|
||||||
color: val(--cell-font-color, var(--spectrum-global-color-gray-500));
|
color: var(--spectrum-global-color-gray-500);
|
||||||
}
|
|
||||||
.checkbox.visible,
|
|
||||||
.number.visible {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete,
|
.delete,
|
||||||
.expand {
|
.expand {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
@ -137,4 +134,11 @@
|
||||||
.delete:hover :global(.spectrum-Icon) {
|
.delete:hover :global(.spectrum-Icon) {
|
||||||
color: var(--spectrum-global-color-red-600) !important;
|
color: var(--spectrum-global-color-red-600) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Visibility of checkbox and number */
|
||||||
|
.gutter.selectable .checkbox.visible,
|
||||||
|
.number.visible,
|
||||||
|
.gutter:not(.selectable) .number {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -303,9 +303,11 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="column-icon">
|
{#if !$config.quiet}
|
||||||
<Icon size="S" name={getColumnIcon(column)} />
|
<div class="column-icon">
|
||||||
</div>
|
<Icon size="S" name={getColumnIcon(column)} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="search-icon" on:click={startSearching}>
|
<div class="search-icon" on:click={startSearching}>
|
||||||
<Icon hoverable size="S" name="Search" />
|
<Icon hoverable size="S" name="Search" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -431,7 +433,7 @@
|
||||||
.header-cell :global(.cell) {
|
.header-cell :global(.cell) {
|
||||||
padding: 0 var(--cell-padding);
|
padding: 0 var(--cell-padding);
|
||||||
gap: calc(2 * var(--cell-spacing));
|
gap: calc(2 * var(--cell-spacing));
|
||||||
background: var(--grid-background-alt);
|
background: var(--header-cell-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icon colors */
|
/* Icon colors */
|
||||||
|
@ -463,6 +465,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.header-cell.searching .name {
|
.header-cell.searching .name {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
@ -217,6 +217,10 @@
|
||||||
--accent-color: var(--primaryColor, var(--spectrum-global-color-blue-400));
|
--accent-color: var(--primaryColor, var(--spectrum-global-color-blue-400));
|
||||||
--grid-background: var(--spectrum-global-color-gray-50);
|
--grid-background: var(--spectrum-global-color-gray-50);
|
||||||
--grid-background-alt: var(--spectrum-global-color-gray-100);
|
--grid-background-alt: var(--spectrum-global-color-gray-100);
|
||||||
|
--header-cell-background: var(
|
||||||
|
--custom-header-cell-background,
|
||||||
|
var(--grid-background-alt)
|
||||||
|
);
|
||||||
--cell-background: var(--grid-background);
|
--cell-background: var(--grid-background);
|
||||||
--cell-background-hover: var(--grid-background-alt);
|
--cell-background-hover: var(--grid-background-alt);
|
||||||
--cell-background-alt: var(--cell-background);
|
--cell-background-alt: var(--cell-background);
|
||||||
|
@ -246,7 +250,10 @@
|
||||||
cursor: grabbing !important;
|
cursor: grabbing !important;
|
||||||
}
|
}
|
||||||
.grid.stripe {
|
.grid.stripe {
|
||||||
--cell-background-alt: var(--spectrum-global-color-gray-75);
|
--cell-background-alt: var(
|
||||||
|
--custom-stripe-cell-background,
|
||||||
|
var(--spectrum-global-color-gray-75)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Data layers */
|
/* Data layers */
|
||||||
|
@ -352,11 +359,18 @@
|
||||||
|
|
||||||
/* Overrides for quiet */
|
/* Overrides for quiet */
|
||||||
.grid.quiet :global(.grid-data-content .row > .cell:not(:last-child)),
|
.grid.quiet :global(.grid-data-content .row > .cell:not(:last-child)),
|
||||||
.grid.quiet :global(.sticky-column .row > .cell),
|
.grid.quiet :global(.sticky-column .row .cell),
|
||||||
.grid.quiet :global(.new-row .row > .cell:not(:last-child)) {
|
.grid.quiet :global(.new-row .row > .cell:not(:last-child)),
|
||||||
|
.grid.quiet :global(.header-cell:not(:last-child) .cell) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
.grid.quiet :global(.sticky-column:before) {
|
.grid.quiet :global(.sticky-column:before) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.grid.quiet:not(.stripe) {
|
||||||
|
--header-cell-background: var(
|
||||||
|
--custom-header-cell-background,
|
||||||
|
var(--grid-background)
|
||||||
|
);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header {
|
.header {
|
||||||
background: var(--grid-background-alt);
|
background: var(--header-cell-background);
|
||||||
border-bottom: var(--cell-border);
|
border-bottom: var(--cell-border);
|
||||||
position: relative;
|
position: relative;
|
||||||
height: var(--default-row-height);
|
height: var(--default-row-height);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import DataCell from "../cells/DataCell.svelte"
|
import DataCell from "../cells/DataCell.svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { GutterWidth, NewRowID } from "../lib/constants"
|
import { DefaultRowHeight, GutterWidth, NewRowID } from "../lib/constants"
|
||||||
import GutterCell from "../cells/GutterCell.svelte"
|
import GutterCell from "../cells/GutterCell.svelte"
|
||||||
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
||||||
import { getCellID } from "../lib/utils"
|
import { getCellID } from "../lib/utils"
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
columnRenderMap,
|
columnRenderMap,
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
scrollTop,
|
scrollTop,
|
||||||
|
height,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
let visible = false
|
let visible = false
|
||||||
|
@ -47,6 +48,8 @@
|
||||||
$: hasNoRows = !$rows.length
|
$: hasNoRows = !$rows.length
|
||||||
$: renderedRowCount = $renderedRows.length
|
$: renderedRowCount = $renderedRows.length
|
||||||
$: offset = getOffset($hasNextPage, renderedRowCount, $rowHeight, $scrollTop)
|
$: offset = getOffset($hasNextPage, renderedRowCount, $rowHeight, $scrollTop)
|
||||||
|
$: spaceBelow = $height - offset - $rowHeight
|
||||||
|
$: flipButtons = spaceBelow < 36 + DefaultRowHeight
|
||||||
|
|
||||||
const getOffset = (hasNextPage, rowCount, rowHeight, scrollTop) => {
|
const getOffset = (hasNextPage, rowCount, rowHeight, scrollTop) => {
|
||||||
// If we have a next page of data then we aren't truly at the bottom, so we
|
// If we have a next page of data then we aren't truly at the bottom, so we
|
||||||
|
@ -244,7 +247,11 @@
|
||||||
</div>
|
</div>
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons" transition:fade|local={{ duration: 130 }}>
|
<div
|
||||||
|
class="buttons"
|
||||||
|
class:flip={flipButtons}
|
||||||
|
transition:fade|local={{ duration: 130 }}
|
||||||
|
>
|
||||||
<Button size="M" cta on:click={addRow} disabled={isAdding}>
|
<Button size="M" cta on:click={addRow} disabled={isAdding}>
|
||||||
<div class="button-with-keys">
|
<div class="button-with-keys">
|
||||||
Save
|
Save
|
||||||
|
@ -337,6 +344,9 @@
|
||||||
.button-with-keys :global(> div) {
|
.button-with-keys :global(> div) {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
.buttons.flip {
|
||||||
|
top: calc(var(--offset) - 36px - var(--default-row-height) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
/* Sticky column styles */
|
/* Sticky column styles */
|
||||||
.sticky-column {
|
.sticky-column {
|
||||||
|
|
|
@ -169,7 +169,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.header :global(.cell) {
|
.header :global(.cell) {
|
||||||
background: var(--grid-background-alt);
|
background: var(--header-cell-background);
|
||||||
}
|
}
|
||||||
.header :global(.cell::before) {
|
.header :global(.cell::before) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -2,8 +2,8 @@ export const SmallRowHeight = 36
|
||||||
export const MediumRowHeight = 64
|
export const MediumRowHeight = 64
|
||||||
export const LargeRowHeight = 92
|
export const LargeRowHeight = 92
|
||||||
export const DefaultRowHeight = SmallRowHeight
|
export const DefaultRowHeight = SmallRowHeight
|
||||||
export const VPadding = SmallRowHeight * 2
|
export const VPadding = 0
|
||||||
export const HPadding = 40
|
export const HPadding = 80
|
||||||
export const ScrollBarSize = 8
|
export const ScrollBarSize = 8
|
||||||
export const GutterWidth = 72
|
export const GutterWidth = 72
|
||||||
export const DefaultColumnWidth = 200
|
export const DefaultColumnWidth = 200
|
||||||
|
|
|
@ -140,13 +140,13 @@
|
||||||
div {
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: var(--spectrum-global-color-gray-500);
|
background: var(--spectrum-global-color-gray-500);
|
||||||
opacity: 0.5;
|
opacity: 0.35;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: opacity 130ms ease-out;
|
transition: opacity 130ms ease-out;
|
||||||
}
|
}
|
||||||
div:hover,
|
div:hover,
|
||||||
div.dragging {
|
div.dragging {
|
||||||
opacity: 1;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.v-scrollbar {
|
.v-scrollbar {
|
||||||
width: var(--scroll-bar-size);
|
width: var(--scroll-bar-size);
|
||||||
|
|
|
@ -7,8 +7,12 @@ type ConfigStore = {
|
||||||
[key in keyof BaseStoreProps]: Readable<BaseStoreProps[key]>
|
[key in keyof BaseStoreProps]: Readable<BaseStoreProps[key]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ConfigState extends BaseStoreProps {
|
||||||
|
canSelectRows: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface ConfigDerivedStore {
|
interface ConfigDerivedStore {
|
||||||
config: Readable<BaseStoreProps>
|
config: Readable<ConfigState>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Store = ConfigStore & ConfigDerivedStore
|
export type Store = ConfigStore & ConfigDerivedStore
|
||||||
|
@ -47,7 +51,7 @@ export const deriveStores = (context: StoreContext): ConfigDerivedStore => {
|
||||||
const config = derived(
|
const config = derived(
|
||||||
[props, definition, hasNonAutoColumn],
|
[props, definition, hasNonAutoColumn],
|
||||||
([$props, $definition, $hasNonAutoColumn]) => {
|
([$props, $definition, $hasNonAutoColumn]) => {
|
||||||
let config = { ...$props }
|
let config: ConfigState = { ...$props, canSelectRows: false }
|
||||||
const type = $props.datasource?.type
|
const type = $props.datasource?.type
|
||||||
|
|
||||||
// Disable some features if we're editing a view
|
// Disable some features if we're editing a view
|
||||||
|
@ -78,6 +82,9 @@ export const deriveStores = (context: StoreContext): ConfigDerivedStore => {
|
||||||
config.canEditColumns = false
|
config.canEditColumns = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if we can select rows
|
||||||
|
config.canSelectRows = !!config.canDeleteRows || !!config.canAddRows
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,6 +36,7 @@ const DependencyOrderedStores = [
|
||||||
NonPlus,
|
NonPlus,
|
||||||
Datasource,
|
Datasource,
|
||||||
Columns,
|
Columns,
|
||||||
|
Config as any,
|
||||||
Scroll,
|
Scroll,
|
||||||
Validation,
|
Validation,
|
||||||
Rows,
|
Rows,
|
||||||
|
@ -47,7 +48,6 @@ const DependencyOrderedStores = [
|
||||||
Users,
|
Users,
|
||||||
Menu,
|
Menu,
|
||||||
Pagination,
|
Pagination,
|
||||||
Config as any,
|
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Notifications,
|
Notifications,
|
||||||
Cache,
|
Cache,
|
||||||
|
|
|
@ -58,6 +58,7 @@ export const deriveStores = (context: StoreContext) => {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
buttonColumnWidth,
|
buttonColumnWidth,
|
||||||
|
config,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Memoize store primitives
|
// Memoize store primitives
|
||||||
|
@ -97,11 +98,14 @@ export const deriveStores = (context: StoreContext) => {
|
||||||
|
|
||||||
// Derive vertical limits
|
// Derive vertical limits
|
||||||
const contentHeight = derived(
|
const contentHeight = derived(
|
||||||
[rows, rowHeight, showHScrollbar],
|
[rows, rowHeight, showHScrollbar, config],
|
||||||
([$rows, $rowHeight, $showHScrollbar]) => {
|
([$rows, $rowHeight, $showHScrollbar, $config]) => {
|
||||||
let height = ($rows.length + 1) * $rowHeight + VPadding
|
let height = $rows.length * $rowHeight + VPadding
|
||||||
if ($showHScrollbar) {
|
if ($showHScrollbar) {
|
||||||
height += ScrollBarSize * 2
|
height += ScrollBarSize * 3
|
||||||
|
}
|
||||||
|
if ($config.canAddRows) {
|
||||||
|
height += $rowHeight
|
||||||
}
|
}
|
||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,6 @@ export { Grid } from "./grid"
|
||||||
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||||
export { default as CoreFilterBuilder } from "./CoreFilterBuilder.svelte"
|
export { default as CoreFilterBuilder } from "./CoreFilterBuilder.svelte"
|
||||||
export { default as FilterUsers } from "./FilterUsers.svelte"
|
export { default as FilterUsers } from "./FilterUsers.svelte"
|
||||||
|
export { default as ChangePasswordModal } from "./ChangePasswordModal.svelte"
|
||||||
|
export { default as ProfileModal } from "./ProfileModal.svelte"
|
||||||
|
export { default as PasswordRepeatInput } from "./PasswordRepeatInput.svelte"
|
||||||
|
|
|
@ -166,3 +166,9 @@ export const FieldPermissions = {
|
||||||
READONLY: "readonly",
|
READONLY: "readonly",
|
||||||
HIDDEN: "hidden",
|
HIDDEN: "hidden",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// one or more word characters and whitespace
|
||||||
|
export const APP_NAME_REGEX = /^[\w\s]+$/
|
||||||
|
|
||||||
|
// zero or more non-whitespace characters
|
||||||
|
export const APP_URL_REGEX = /^[0-9a-zA-Z-_]+$/
|
||||||
|
|
|
@ -14,3 +14,4 @@ export * from "./settings"
|
||||||
export * from "./relatedColumns"
|
export * from "./relatedColumns"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
export * from "./components"
|
export * from "./components"
|
||||||
|
export * from "./validation"
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { helpers } from "@budibase/shared-core"
|
||||||
import { TypeIconMap } from "../constants"
|
import { TypeIconMap } from "../constants"
|
||||||
|
|
||||||
export const getColumnIcon = column => {
|
export const getColumnIcon = column => {
|
||||||
if (column.schema.icon) {
|
// For some reason we have remix icons saved under this property sometimes,
|
||||||
|
// so we must ignore those as they are invalid spectrum icons
|
||||||
|
if (column.schema.icon && !column.schema.icon.startsWith("ri-")) {
|
||||||
return column.schema.icon
|
return column.schema.icon
|
||||||
}
|
}
|
||||||
if (column.calculationType) {
|
if (column.calculationType) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { string, mixed } from "yup"
|
import { string, mixed } from "yup"
|
||||||
import { APP_NAME_REGEX, APP_URL_REGEX } from "@/constants"
|
import { APP_NAME_REGEX, APP_URL_REGEX } from "../../../constants"
|
||||||
|
|
||||||
export const name = (validation, { apps, currentApp } = { apps: [] }) => {
|
export const name = (validation, { apps, currentApp } = { apps: [] }) => {
|
||||||
validation.addValidator(
|
validation.addValidator(
|
|
@ -1,7 +1,6 @@
|
||||||
import { capitalise } from "@/helpers"
|
|
||||||
import { object, string, number } from "yup"
|
import { object, string, number } from "yup"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { Helpers, notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
export const createValidationStore = () => {
|
export const createValidationStore = () => {
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
|
@ -77,7 +76,7 @@ export const createValidationStore = () => {
|
||||||
const [fieldError] = error.errors
|
const [fieldError] = error.errors
|
||||||
if (fieldError) {
|
if (fieldError) {
|
||||||
validation.update(store => {
|
validation.update(store => {
|
||||||
store.errors[propertyName] = capitalise(fieldError)
|
store.errors[propertyName] = Helpers.capitalise(fieldError)
|
||||||
store.valid = false
|
store.valid = false
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
|
@ -120,7 +119,7 @@ export const createValidationStore = () => {
|
||||||
} else {
|
} else {
|
||||||
error.inner.forEach(err => {
|
error.inner.forEach(err => {
|
||||||
validation.update(store => {
|
validation.update(store => {
|
||||||
store.errors[err.path] = capitalise(err.message)
|
store.errors[err.path] = Helpers.capitalise(err.message)
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -1 +1 @@
|
||||||
Subproject commit bb1ed6fa96ebed30e30659e47b0712567601f3c0
|
Subproject commit f709bb6a07483785c32ebb6f186709450d735ec3
|
|
@ -842,17 +842,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -864,6 +876,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"fieldName": {
|
"fieldName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the column which a relationship column is related to in another table."
|
"description": "The name of the column which a relationship column is related to in another table."
|
||||||
|
@ -914,17 +930,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -936,6 +964,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"formula": {
|
"formula": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
||||||
|
@ -965,8 +997,6 @@
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
"attachment_single",
|
"attachment_single",
|
||||||
"link",
|
|
||||||
"formula",
|
|
||||||
"auto",
|
"auto",
|
||||||
"ai",
|
"ai",
|
||||||
"json",
|
"json",
|
||||||
|
@ -984,17 +1014,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1005,6 +1047,10 @@
|
||||||
"autocolumn": {
|
"autocolumn": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1052,17 +1098,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1074,6 +1132,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"fieldName": {
|
"fieldName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the column which a relationship column is related to in another table."
|
"description": "The name of the column which a relationship column is related to in another table."
|
||||||
|
@ -1124,17 +1186,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1146,6 +1220,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"formula": {
|
"formula": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
||||||
|
@ -1175,8 +1253,6 @@
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
"attachment_single",
|
"attachment_single",
|
||||||
"link",
|
|
||||||
"formula",
|
|
||||||
"auto",
|
"auto",
|
||||||
"ai",
|
"ai",
|
||||||
"json",
|
"json",
|
||||||
|
@ -1194,17 +1270,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1215,6 +1303,10 @@
|
||||||
"autocolumn": {
|
"autocolumn": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1273,17 +1365,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1295,6 +1399,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"fieldName": {
|
"fieldName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the column which a relationship column is related to in another table."
|
"description": "The name of the column which a relationship column is related to in another table."
|
||||||
|
@ -1345,17 +1453,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1367,6 +1487,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
},
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
|
},
|
||||||
"formula": {
|
"formula": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
"description": "Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format."
|
||||||
|
@ -1396,8 +1520,6 @@
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
"attachment_single",
|
"attachment_single",
|
||||||
"link",
|
|
||||||
"formula",
|
|
||||||
"auto",
|
"auto",
|
||||||
"ai",
|
"ai",
|
||||||
"json",
|
"json",
|
||||||
|
@ -1415,17 +1537,29 @@
|
||||||
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
"description": "A constraint can be applied to the column which will be validated against when a row is saved.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number",
|
|
||||||
"object",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"presence": {
|
"presence": {
|
||||||
"type": "boolean",
|
"oneOf": [
|
||||||
"description": "Defines whether the column is required or not."
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the column is required or not."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"description": "Defines whether the column is required or not.",
|
||||||
|
"properties": {
|
||||||
|
"allowEmpty": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Defines whether the value is allowed to be empty or not."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inclusion": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the valid values for this column."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1436,6 +1570,10 @@
|
||||||
"autocolumn": {
|
"autocolumn": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Defines whether the column is automatically generated."
|
"description": "Defines whether the column is automatically generated."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Defines the width of the column in the data UI."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -752,20 +752,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
fieldName:
|
fieldName:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column which a relationship column is related to in
|
description: The name of the column which a relationship column is related to in
|
||||||
|
@ -812,20 +820,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
formula:
|
formula:
|
||||||
type: string
|
type: string
|
||||||
description: Defines a Handlebars or JavaScript formula to use, note that
|
description: Defines a Handlebars or JavaScript formula to use, note that
|
||||||
|
@ -851,8 +867,6 @@ components:
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
- attachment_single
|
- attachment_single
|
||||||
- link
|
|
||||||
- formula
|
|
||||||
- auto
|
- auto
|
||||||
- ai
|
- ai
|
||||||
- json
|
- json
|
||||||
|
@ -871,20 +885,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
tableOutput:
|
tableOutput:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -921,20 +943,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
fieldName:
|
fieldName:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column which a relationship column is related to in
|
description: The name of the column which a relationship column is related to in
|
||||||
|
@ -981,20 +1011,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
formula:
|
formula:
|
||||||
type: string
|
type: string
|
||||||
description: Defines a Handlebars or JavaScript formula to use, note that
|
description: Defines a Handlebars or JavaScript formula to use, note that
|
||||||
|
@ -1020,8 +1058,6 @@ components:
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
- attachment_single
|
- attachment_single
|
||||||
- link
|
|
||||||
- formula
|
|
||||||
- auto
|
- auto
|
||||||
- ai
|
- ai
|
||||||
- json
|
- json
|
||||||
|
@ -1040,20 +1076,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
_id:
|
_id:
|
||||||
description: The ID of the table.
|
description: The ID of the table.
|
||||||
type: string
|
type: string
|
||||||
|
@ -1097,20 +1141,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
fieldName:
|
fieldName:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column which a relationship column is related to in
|
description: The name of the column which a relationship column is related to in
|
||||||
|
@ -1157,20 +1209,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
formula:
|
formula:
|
||||||
type: string
|
type: string
|
||||||
description: Defines a Handlebars or JavaScript formula to use, note that
|
description: Defines a Handlebars or JavaScript formula to use, note that
|
||||||
|
@ -1196,8 +1256,6 @@ components:
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
- attachment_single
|
- attachment_single
|
||||||
- link
|
|
||||||
- formula
|
|
||||||
- auto
|
- auto
|
||||||
- ai
|
- ai
|
||||||
- json
|
- json
|
||||||
|
@ -1216,20 +1274,28 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
- object
|
|
||||||
- boolean
|
|
||||||
presence:
|
presence:
|
||||||
type: boolean
|
oneOf:
|
||||||
description: Defines whether the column is required or not.
|
- type: boolean
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
- type: object
|
||||||
|
description: Defines whether the column is required or not.
|
||||||
|
properties:
|
||||||
|
allowEmpty:
|
||||||
|
type: boolean
|
||||||
|
description: Defines whether the value is allowed to be empty or not.
|
||||||
|
inclusion:
|
||||||
|
type: array
|
||||||
|
description: Defines the valid values for this column.
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the column.
|
description: The name of the column.
|
||||||
autocolumn:
|
autocolumn:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Defines whether the column is automatically generated.
|
description: Defines whether the column is automatically generated.
|
||||||
|
width:
|
||||||
|
type: number
|
||||||
|
description: Defines the width of the column in the data UI.
|
||||||
_id:
|
_id:
|
||||||
description: The ID of the table.
|
description: The ID of the table.
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -27,7 +27,9 @@ const table = {
|
||||||
const baseColumnDef = {
|
const baseColumnDef = {
|
||||||
type: {
|
type: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: Object.values(FieldType),
|
enum: Object.values(FieldType).filter(
|
||||||
|
type => ![FieldType.LINK, FieldType.FORMULA].includes(type)
|
||||||
|
),
|
||||||
description:
|
description:
|
||||||
"Defines the type of the column, most explain themselves, a link column is a relationship.",
|
"Defines the type of the column, most explain themselves, a link column is a relationship.",
|
||||||
},
|
},
|
||||||
|
@ -38,11 +40,29 @@ const baseColumnDef = {
|
||||||
properties: {
|
properties: {
|
||||||
type: {
|
type: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: ["string", "number", "object", "boolean"],
|
|
||||||
},
|
},
|
||||||
presence: {
|
presence: {
|
||||||
type: "boolean",
|
oneOf: [
|
||||||
description: "Defines whether the column is required or not.",
|
{
|
||||||
|
type: "boolean",
|
||||||
|
description: "Defines whether the column is required or not.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "object",
|
||||||
|
description: "Defines whether the column is required or not.",
|
||||||
|
properties: {
|
||||||
|
allowEmpty: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Defines whether the value is allowed to be empty or not.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
inclusion: {
|
||||||
|
type: "array",
|
||||||
|
description: "Defines the valid values for this column.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -54,6 +74,10 @@ const baseColumnDef = {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
description: "Defines whether the column is automatically generated.",
|
description: "Defines whether the column is automatically generated.",
|
||||||
},
|
},
|
||||||
|
width: {
|
||||||
|
type: "number",
|
||||||
|
description: "Defines the width of the column in the data UI.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableSchema = {
|
const tableSchema = {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
DocumentType,
|
DocumentType,
|
||||||
generateAppID,
|
generateAppID,
|
||||||
generateDevAppID,
|
generateDevAppID,
|
||||||
|
generateScreenID,
|
||||||
getLayoutParams,
|
getLayoutParams,
|
||||||
getScreenParams,
|
getScreenParams,
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
|
@ -75,6 +76,7 @@ import sdk from "../../sdk"
|
||||||
import { builderSocket } from "../../websockets"
|
import { builderSocket } from "../../websockets"
|
||||||
import { DefaultAppTheme, sdk as sharedCoreSDK } from "@budibase/shared-core"
|
import { DefaultAppTheme, sdk as sharedCoreSDK } from "@budibase/shared-core"
|
||||||
import * as appMigrations from "../../appMigrations"
|
import * as appMigrations from "../../appMigrations"
|
||||||
|
import { createSampleDataTableScreen } from "../../constants/screens"
|
||||||
|
|
||||||
// utility function, need to do away with this
|
// utility function, need to do away with this
|
||||||
async function getLayouts() {
|
async function getLayouts() {
|
||||||
|
@ -176,11 +178,8 @@ async function createInstance(appId: string, template: AppTemplate) {
|
||||||
return { _id: appId }
|
return { _id: appId }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addSampleData = async (
|
async function addSampleDataDocs() {
|
||||||
ctx: UserCtx<void, AddAppSampleDataResponse>
|
|
||||||
) => {
|
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if default datasource exists before creating it
|
// Check if default datasource exists before creating it
|
||||||
await sdk.datasources.get(DEFAULT_BB_DATASOURCE_ID)
|
await sdk.datasources.get(DEFAULT_BB_DATASOURCE_ID)
|
||||||
|
@ -190,7 +189,41 @@ export const addSampleData = async (
|
||||||
// add in the default db data docs - tables, datasource, rows and links
|
// add in the default db data docs - tables, datasource, rows and links
|
||||||
await db.bulkDocs([...defaultDbDocs])
|
await db.bulkDocs([...defaultDbDocs])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addSampleDataScreen() {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
let screen = createSampleDataTableScreen()
|
||||||
|
screen._id = generateScreenID()
|
||||||
|
await db.put(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addSampleDataNavLinks() {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
let app = await sdk.applications.metadata.get()
|
||||||
|
if (!app.navigation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!app.navigation.links) {
|
||||||
|
app.navigation.links = []
|
||||||
|
}
|
||||||
|
app.navigation.links.push({
|
||||||
|
text: "Inventory",
|
||||||
|
url: "/inventory",
|
||||||
|
type: "link",
|
||||||
|
roleId: roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
})
|
||||||
|
|
||||||
|
await db.put(app)
|
||||||
|
|
||||||
|
// remove any cached metadata, so that it will be updated
|
||||||
|
await cache.app.invalidateAppMetadata(app.appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSampleData = async (
|
||||||
|
ctx: UserCtx<void, AddAppSampleDataResponse>
|
||||||
|
) => {
|
||||||
|
await addSampleDataDocs()
|
||||||
ctx.body = { message: "Sample tables added." }
|
ctx.body = { message: "Sample tables added." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +294,7 @@ async function performAppCreate(
|
||||||
const { body } = ctx.request
|
const { body } = ctx.request
|
||||||
const { name, url, encryptionPassword, templateKey } = body
|
const { name, url, encryptionPassword, templateKey } = body
|
||||||
|
|
||||||
let useTemplate
|
let useTemplate = false
|
||||||
if (typeof body.useTemplate === "string") {
|
if (typeof body.useTemplate === "string") {
|
||||||
useTemplate = body.useTemplate === "true"
|
useTemplate = body.useTemplate === "true"
|
||||||
} else if (typeof body.useTemplate === "boolean") {
|
} else if (typeof body.useTemplate === "boolean") {
|
||||||
|
@ -293,12 +326,14 @@ async function performAppCreate(
|
||||||
return await context.doInAppContext(appId, async () => {
|
return await context.doInAppContext(appId, async () => {
|
||||||
const instance = await createInstance(appId, instanceConfig)
|
const instance = await createInstance(appId, instanceConfig)
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
const isImport = !!instanceConfig.file
|
||||||
|
const addSampleData = !isImport && !useTemplate
|
||||||
|
|
||||||
if (instanceConfig.useTemplate && !instanceConfig.file) {
|
if (instanceConfig.useTemplate && !instanceConfig.file) {
|
||||||
await updateUserColumns(appId, db, ctx.user._id!)
|
await updateUserColumns(appId, db, ctx.user._id!)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newApplication: App = {
|
let newApplication: App = {
|
||||||
_id: DocumentType.APP_METADATA,
|
_id: DocumentType.APP_METADATA,
|
||||||
_rev: undefined,
|
_rev: undefined,
|
||||||
appId,
|
appId,
|
||||||
|
@ -317,11 +352,14 @@ async function performAppCreate(
|
||||||
navigation: "Top",
|
navigation: "Top",
|
||||||
title: name,
|
title: name,
|
||||||
navWidth: "Large",
|
navWidth: "Large",
|
||||||
navBackground: "var(--spectrum-global-color-gray-100)",
|
navBackground: "var(--spectrum-global-color-static-blue-1200)",
|
||||||
|
navTextColor: "var(--spectrum-global-color-static-white)",
|
||||||
links: [],
|
links: [],
|
||||||
},
|
},
|
||||||
theme: DefaultAppTheme,
|
theme: DefaultAppTheme,
|
||||||
customTheme: {
|
customTheme: {
|
||||||
|
primaryColor: "var(--spectrum-global-color-static-blue-1200)",
|
||||||
|
primaryColorHover: "var(--spectrum-global-color-static-blue-800)",
|
||||||
buttonBorderRadius: "16px",
|
buttonBorderRadius: "16px",
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
|
@ -332,7 +370,6 @@ async function performAppCreate(
|
||||||
creationVersion: undefined,
|
creationVersion: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isImport = !!instanceConfig.file
|
|
||||||
if (!isImport) {
|
if (!isImport) {
|
||||||
newApplication.creationVersion = envCore.VERSION
|
newApplication.creationVersion = envCore.VERSION
|
||||||
}
|
}
|
||||||
|
@ -379,6 +416,20 @@ async function performAppCreate(
|
||||||
await uploadAppFiles(appId)
|
await uploadAppFiles(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add sample datasource and example screen for non-templates/non-imports
|
||||||
|
if (addSampleData) {
|
||||||
|
try {
|
||||||
|
await addSampleDataDocs()
|
||||||
|
await addSampleDataScreen()
|
||||||
|
await addSampleDataNavLinks()
|
||||||
|
|
||||||
|
// Fetch the latest version of the app after these changes
|
||||||
|
newApplication = await sdk.applications.metadata.get()
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(400, "App created, but failed to add sample data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const latestMigrationId = appMigrations.getLatestEnabledMigrationId()
|
const latestMigrationId = appMigrations.getLatestEnabledMigrationId()
|
||||||
if (latestMigrationId) {
|
if (latestMigrationId) {
|
||||||
// Initialise the app migration version as the latest one
|
// Initialise the app migration version as the latest one
|
||||||
|
|
|
@ -127,10 +127,26 @@ describe("/applications", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
it("creates empty app", async () => {
|
const checkScreenCount = async (expectedCount: number) => {
|
||||||
|
const res = await config.api.application.getDefinition(
|
||||||
|
config.getProdAppId()
|
||||||
|
)
|
||||||
|
expect(res.screens.length).toEqual(expectedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkTableCount = async (expectedCount: number) => {
|
||||||
|
const tables = await config.api.table.fetch()
|
||||||
|
expect(tables.length).toEqual(expectedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("creates empty app with sample data", async () => {
|
||||||
const app = await config.api.application.create({ name: utils.newid() })
|
const app = await config.api.application.create({ name: utils.newid() })
|
||||||
expect(app._id).toBeDefined()
|
expect(app._id).toBeDefined()
|
||||||
expect(events.app.created).toHaveBeenCalledTimes(1)
|
expect(events.app.created).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// Ensure we created sample resources
|
||||||
|
await checkScreenCount(1)
|
||||||
|
await checkTableCount(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates app from template", async () => {
|
it("creates app from template", async () => {
|
||||||
|
@ -149,6 +165,11 @@ describe("/applications", () => {
|
||||||
expect(app._id).toBeDefined()
|
expect(app._id).toBeDefined()
|
||||||
expect(events.app.created).toHaveBeenCalledTimes(1)
|
expect(events.app.created).toHaveBeenCalledTimes(1)
|
||||||
expect(events.app.templateImported).toHaveBeenCalledTimes(1)
|
expect(events.app.templateImported).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// Ensure we did not create sample data. This template includes exactly
|
||||||
|
// this many of each resource.
|
||||||
|
await checkScreenCount(1)
|
||||||
|
await checkTableCount(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates app from file", async () => {
|
it("creates app from file", async () => {
|
||||||
|
@ -160,6 +181,11 @@ describe("/applications", () => {
|
||||||
expect(app._id).toBeDefined()
|
expect(app._id).toBeDefined()
|
||||||
expect(events.app.created).toHaveBeenCalledTimes(1)
|
expect(events.app.created).toHaveBeenCalledTimes(1)
|
||||||
expect(events.app.fileImported).toHaveBeenCalledTimes(1)
|
expect(events.app.fileImported).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// Ensure we did not create sample data. This file includes exactly
|
||||||
|
// this many of each resource.
|
||||||
|
await checkScreenCount(1)
|
||||||
|
await checkTableCount(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
|
|
@ -66,8 +66,8 @@ describe("/backups", () => {
|
||||||
await config.createScreen()
|
await config.createScreen()
|
||||||
let res = await sdk.backups.calculateBackupStats(config.getAppId()!)
|
let res = await sdk.backups.calculateBackupStats(config.getAppId()!)
|
||||||
expect(res.automations).toEqual(1)
|
expect(res.automations).toEqual(1)
|
||||||
expect(res.datasources).toEqual(1)
|
expect(res.datasources).toEqual(6)
|
||||||
expect(res.screens).toEqual(1)
|
expect(res.screens).toEqual(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
UsageInScreensResponse,
|
UsageInScreensResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { basicDatasourcePlus } from "../../../tests/utilities/structures"
|
import { basicDatasourcePlus } from "../../../tests/utilities/structures"
|
||||||
|
import { SAMPLE_DATA_SCREEN_NAME } from "../../../constants/screens"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
basicScreen,
|
basicScreen,
|
||||||
|
@ -21,8 +22,8 @@ const {
|
||||||
} = setup.structures
|
} = setup.structures
|
||||||
|
|
||||||
describe("/screens", () => {
|
describe("/screens", () => {
|
||||||
let config = setup.getConfig()
|
|
||||||
let screen: Screen
|
let screen: Screen
|
||||||
|
let config = setup.getConfig()
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
@ -32,9 +33,16 @@ describe("/screens", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
it("should be able to create a layout", async () => {
|
it("should create the sample data screen", async () => {
|
||||||
const screens = await config.api.screen.list()
|
const screens = await config.api.screen.list()
|
||||||
expect(screens.length).toEqual(1)
|
expect(screens.some(s => s.name === SAMPLE_DATA_SCREEN_NAME)).toEqual(
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to create a screen", async () => {
|
||||||
|
const screens = await config.api.screen.list()
|
||||||
|
expect(screens.length).toEqual(2)
|
||||||
expect(screens.some(s => s._id === screen._id)).toEqual(true)
|
expect(screens.some(s => s._id === screen._id)).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -92,8 +100,14 @@ describe("/screens", () => {
|
||||||
const res = await config.api.application.getDefinition(
|
const res = await config.api.application.getDefinition(
|
||||||
config.getProdAppId()
|
config.getProdAppId()
|
||||||
)
|
)
|
||||||
expect(res.screens.length).toEqual(screenIds.length)
|
|
||||||
expect(res.screens.map(s => s._id).sort()).toEqual(screenIds.sort())
|
// Filter out sample screen
|
||||||
|
const screens = res.screens.filter(
|
||||||
|
s => s.name !== SAMPLE_DATA_SCREEN_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screens.length).toEqual(screenIds.length)
|
||||||
|
expect(screens.map(s => s._id).sort()).toEqual(screenIds.sort())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,9 +136,15 @@ describe("/screens", () => {
|
||||||
const res = await config.api.application.getDefinition(
|
const res = await config.api.application.getDefinition(
|
||||||
config.prodAppId!
|
config.prodAppId!
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Filter out sample screen
|
||||||
|
const screens = res.screens.filter(
|
||||||
|
s => s.name !== SAMPLE_DATA_SCREEN_NAME
|
||||||
|
)
|
||||||
|
|
||||||
const screenIds = [screen._id!, screen1._id!]
|
const screenIds = [screen._id!, screen1._id!]
|
||||||
expect(res.screens.length).toEqual(screenIds.length)
|
expect(screens.length).toEqual(screenIds.length)
|
||||||
expect(res.screens.map(s => s._id).sort()).toEqual(screenIds.sort())
|
expect(screens.map(s => s._id).sort()).toEqual(screenIds.sort())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { roles } from "@budibase/backend-core"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "./layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "./layouts"
|
||||||
import { Screen, Table, Query, ViewV2, Component } from "@budibase/types"
|
import { Screen, Table, Query, ViewV2, Component } from "@budibase/types"
|
||||||
|
|
||||||
|
export const SAMPLE_DATA_SCREEN_NAME = "sample-data-inventory-screen"
|
||||||
|
|
||||||
export function createHomeScreen(
|
export function createHomeScreen(
|
||||||
config: {
|
config: {
|
||||||
roleId: string
|
roleId: string
|
||||||
|
@ -227,3 +229,365 @@ export function createQueryScreen(datasourceId: string, query: Query): Screen {
|
||||||
name: "screen-id",
|
name: "screen-id",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createSampleDataTableScreen(): Screen {
|
||||||
|
return {
|
||||||
|
showNavigation: true,
|
||||||
|
width: "Large",
|
||||||
|
routing: { route: "/inventory", roleId: "BASIC", homeScreen: false },
|
||||||
|
name: SAMPLE_DATA_SCREEN_NAME,
|
||||||
|
props: {
|
||||||
|
_id: "c38f2b9f250fb4c33965ce47e12c02a80",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "cf600445f0b0048c79c0c81606b30d542",
|
||||||
|
_component: "@budibase/standard-components/gridblock",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"--grid-desktop-col-start": 1,
|
||||||
|
"--grid-desktop-col-end": 13,
|
||||||
|
"--grid-desktop-row-start": 3,
|
||||||
|
"--grid-desktop-row-end": 19,
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_instanceName: "Inventory table",
|
||||||
|
_children: [],
|
||||||
|
table: {
|
||||||
|
label: "Inventory",
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
type: "table",
|
||||||
|
datasourceName: "Sample Data",
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: "Item Tags",
|
||||||
|
field: "Item Tags",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Purchase Date",
|
||||||
|
field: "Purchase Date",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Purchase Price",
|
||||||
|
field: "Purchase Price",
|
||||||
|
active: true,
|
||||||
|
format:
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
"${{ [cf600445f0b0048c79c0c81606b30d542].[Purchase Price] }}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Notes",
|
||||||
|
field: "Notes",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Status",
|
||||||
|
field: "Status",
|
||||||
|
active: true,
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
id: Math.random(),
|
||||||
|
target: "row",
|
||||||
|
metadataKey: "backgroundColor",
|
||||||
|
operator: "contains",
|
||||||
|
valueType: "array",
|
||||||
|
metadataValue: "var(--spectrum-global-color-red-100)",
|
||||||
|
noValue: false,
|
||||||
|
referenceValue: "Repair",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "SKU",
|
||||||
|
field: "SKU",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Item ID",
|
||||||
|
field: "Item ID",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Created At",
|
||||||
|
field: "Created At",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Updated At",
|
||||||
|
field: "Updated At",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Item Name",
|
||||||
|
field: "Item Name",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initialSortColumn: "Item ID",
|
||||||
|
allowAddRows: false,
|
||||||
|
allowEditRows: false,
|
||||||
|
allowDeleteRows: false,
|
||||||
|
stripeRows: false,
|
||||||
|
quiet: true,
|
||||||
|
onRowClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
key: "inventoryID",
|
||||||
|
type: "set",
|
||||||
|
value: "{{ [eventContext].[row]._id }}",
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Update State",
|
||||||
|
id: "fgVuxCvjL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
id: "c73fd03209dd44dd3937a33c6205b031d",
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Open Side Panel",
|
||||||
|
id: "hwnlhdSUb",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "c09edf7de69be44ce8f0215c3f62e43a5",
|
||||||
|
_component: "@budibase/standard-components/textv2",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"--grid-desktop-col-end": 3,
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "Table title",
|
||||||
|
align: "left",
|
||||||
|
text: "## Inventory",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "c5879d3daffbd47619a833d3f88f07526",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"--grid-desktop-col-start": 11,
|
||||||
|
"--grid-desktop-col-end": 13,
|
||||||
|
"--grid-desktop-row-start": 1,
|
||||||
|
"--grid-desktop-row-end": 3,
|
||||||
|
"--grid-desktop-h-align": "end",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "New row button",
|
||||||
|
text: "Create row",
|
||||||
|
type: "cta",
|
||||||
|
size: "M",
|
||||||
|
gap: "M",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
id: "c34d2b7c480144f3c800be15a62111d24",
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Open Side Panel",
|
||||||
|
id: "rYTWHu7k0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "c73fd03209dd44dd3937a33c6205b031d",
|
||||||
|
_component: "@budibase/standard-components/sidepanel",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "Edit row side panel",
|
||||||
|
ignoreClicksOutside: false,
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "c3a5c8d0caf35410f8b75d6cb493ac693",
|
||||||
|
_component: "@budibase/standard-components/formblock",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "Edit row form block",
|
||||||
|
dataSource: {
|
||||||
|
label: "Inventory",
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
type: "table",
|
||||||
|
datasourceName: "Sample Data",
|
||||||
|
resourceId: "ta_bb_inventory",
|
||||||
|
},
|
||||||
|
actionType: "Update",
|
||||||
|
buttonPosition: "bottom",
|
||||||
|
size: "spectrum--medium",
|
||||||
|
noRowsMessage: "We couldn't find a row to display",
|
||||||
|
disabled: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: "Save",
|
||||||
|
_id: "cc8c5d82717a54e68a610fe7204e25392",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Validate Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: "c3a5c8d0caf35410f8b75d6cb493ac693-form",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: "c3a5c8d0caf35410f8b75d6cb493ac693-form",
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
confirm: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Modal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "cta",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Delete",
|
||||||
|
_id: "cf20dbe1df3d648599932b04f7e630376",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Delete Row",
|
||||||
|
parameters: {
|
||||||
|
confirm: true,
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
rowId:
|
||||||
|
"{{ [c3a5c8d0caf35410f8b75d6cb493ac693-repeater].[_id] }}",
|
||||||
|
revId:
|
||||||
|
"{{ [c3a5c8d0caf35410f8b75d6cb493ac693-repeater].[_rev] }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Modal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
quiet: true,
|
||||||
|
type: "warning",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: null,
|
||||||
|
rowId: "{{ [state].[inventoryID] }}",
|
||||||
|
title:
|
||||||
|
"{{ [c3a5c8d0caf35410f8b75d6cb493ac693-repeater].[Item Name] }}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "c34d2b7c480144f3c800be15a62111d24",
|
||||||
|
_component: "@budibase/standard-components/sidepanel",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "New row side panel",
|
||||||
|
ignoreClicksOutside: false,
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "c61a1690c6ba0448db504eda38f766db1",
|
||||||
|
_component: "@budibase/standard-components/formblock",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
},
|
||||||
|
_instanceName: "New Form Block",
|
||||||
|
dataSource: {
|
||||||
|
label: "Inventory",
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
type: "table",
|
||||||
|
datasourceName: "Sample Data",
|
||||||
|
resourceId: "ta_bb_inventory",
|
||||||
|
},
|
||||||
|
actionType: "Create",
|
||||||
|
buttonPosition: "bottom",
|
||||||
|
size: "spectrum--medium",
|
||||||
|
noRowsMessage: "We couldn't find a row to display",
|
||||||
|
disabled: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: "Save",
|
||||||
|
_id: "ced8cf5175b9c40aabd216f23f072a44c",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Validate Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: "c61a1690c6ba0448db504eda38f766db1-form",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: "c61a1690c6ba0448db504eda38f766db1-form",
|
||||||
|
tableId: "ta_bb_inventory",
|
||||||
|
confirm: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Clear Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: "c61a1690c6ba0448db504eda38f766db1-form",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "cta",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: null,
|
||||||
|
title: "Add to inventory",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Inventory - List",
|
||||||
|
layout: "grid",
|
||||||
|
direction: "column",
|
||||||
|
hAlign: "stretch",
|
||||||
|
vAlign: "top",
|
||||||
|
size: "grow",
|
||||||
|
gap: "M",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
type: FieldType.NUMBER,
|
type: FieldType.NUMBER,
|
||||||
subtype: AutoFieldSubType.AUTO_ID,
|
subtype: AutoFieldSubType.AUTO_ID,
|
||||||
icon: "ri-magic-line",
|
icon: "ri-magic-line",
|
||||||
|
width: 120,
|
||||||
autocolumn: true,
|
autocolumn: true,
|
||||||
constraints: {
|
constraints: {
|
||||||
type: FieldType.NUMBER,
|
type: FieldType.NUMBER,
|
||||||
|
@ -128,6 +129,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
name: "Item Name",
|
name: "Item Name",
|
||||||
|
width: 160,
|
||||||
},
|
},
|
||||||
"Item Tags": {
|
"Item Tags": {
|
||||||
type: FieldType.ARRAY,
|
type: FieldType.ARRAY,
|
||||||
|
@ -150,6 +152,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
},
|
},
|
||||||
name: "Notes",
|
name: "Notes",
|
||||||
useRichText: null,
|
useRichText: null,
|
||||||
|
width: 220,
|
||||||
},
|
},
|
||||||
Status: {
|
Status: {
|
||||||
type: FieldType.ARRAY,
|
type: FieldType.ARRAY,
|
||||||
|
@ -161,6 +164,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
inclusion: ["Available", "Repair", "Broken"],
|
inclusion: ["Available", "Repair", "Broken"],
|
||||||
},
|
},
|
||||||
name: "Status",
|
name: "Status",
|
||||||
|
width: 110,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
SKU: {
|
SKU: {
|
||||||
|
@ -171,6 +175,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
presence: false,
|
presence: false,
|
||||||
},
|
},
|
||||||
name: "SKU",
|
name: "SKU",
|
||||||
|
width: 130,
|
||||||
},
|
},
|
||||||
"Purchase Date": {
|
"Purchase Date": {
|
||||||
type: FieldType.DATETIME,
|
type: FieldType.DATETIME,
|
||||||
|
@ -197,6 +202,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
name: "Purchase Price",
|
name: "Purchase Price",
|
||||||
|
width: 160,
|
||||||
},
|
},
|
||||||
...AUTO_COLUMNS,
|
...AUTO_COLUMNS,
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,8 +2,8 @@ export const inventoryImport = [
|
||||||
{
|
{
|
||||||
Status: ["Available"],
|
Status: ["Available"],
|
||||||
"Item Name": "Little Blue Van",
|
"Item Name": "Little Blue Van",
|
||||||
SKU: "",
|
SKU: "LBV-101",
|
||||||
Notes: "MAX PAYLOAD 595 kg \nMAX LOAD LENGTH 1620 mm",
|
Notes: "Max payload 595 kg \nMax load length 1620 mm",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
"Created At": "2022-11-10T19:11:40.141Z",
|
"Created At": "2022-11-10T19:11:40.141Z",
|
||||||
"Updated At": "2022-11-10T20:05:04.608Z",
|
"Updated At": "2022-11-10T20:05:04.608Z",
|
||||||
|
@ -29,7 +29,7 @@ export const inventoryImport = [
|
||||||
Status: ["Repair"],
|
Status: ["Repair"],
|
||||||
"Item Name": "Circular saw",
|
"Item Name": "Circular saw",
|
||||||
SKU: "AB2-100",
|
SKU: "AB2-100",
|
||||||
Notes: "",
|
Notes: "Won't start",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
"Created At": "2022-11-10T19:04:38.805Z",
|
"Created At": "2022-11-10T19:04:38.805Z",
|
||||||
"Updated At": "2022-11-10T20:20:24.000Z",
|
"Updated At": "2022-11-10T20:20:24.000Z",
|
||||||
|
@ -58,7 +58,7 @@ export const inventoryImport = [
|
||||||
Status: ["Available"],
|
Status: ["Available"],
|
||||||
"Item Name": "Power Screwdriver",
|
"Item Name": "Power Screwdriver",
|
||||||
SKU: "TKIT-002-A",
|
SKU: "TKIT-002-A",
|
||||||
Notes: "",
|
Notes: "Requires micro USB charger",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
"Created At": "2022-11-10T20:10:51.129Z",
|
"Created At": "2022-11-10T20:10:51.129Z",
|
||||||
"Updated At": "2022-11-10T20:13:37.821Z",
|
"Updated At": "2022-11-10T20:13:37.821Z",
|
||||||
|
@ -67,8 +67,8 @@ export const inventoryImport = [
|
||||||
{
|
{
|
||||||
Status: ["Available"],
|
Status: ["Available"],
|
||||||
"Item Name": "Large Blue Van",
|
"Item Name": "Large Blue Van",
|
||||||
SKU: "",
|
SKU: "LBV-102",
|
||||||
Notes: "MAX LOAD LENGTH 4256 mm",
|
Notes: "Max load length 4256 mm",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
"Created At": "2022-11-10T19:03:41.698Z",
|
"Created At": "2022-11-10T19:03:41.698Z",
|
||||||
"Updated At": "2022-11-10T20:04:57.932Z",
|
"Updated At": "2022-11-10T20:04:57.932Z",
|
||||||
|
@ -81,7 +81,7 @@ export const inventoryImport = [
|
||||||
"Purchase Price": 2500,
|
"Purchase Price": 2500,
|
||||||
"Purchase Date": "2022-11-09T12:00:00.000",
|
"Purchase Date": "2022-11-09T12:00:00.000",
|
||||||
Status: ["Available"],
|
Status: ["Available"],
|
||||||
"Item Name": "Office Laptop",
|
"Item Name": "Office laptop",
|
||||||
SKU: "PC-123-ABC",
|
SKU: "PC-123-ABC",
|
||||||
Notes: "Office Laptop \n",
|
Notes: "Office Laptop \n",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
|
@ -93,8 +93,8 @@ export const inventoryImport = [
|
||||||
{
|
{
|
||||||
Status: ["Available"],
|
Status: ["Available"],
|
||||||
"Item Name": "Little Red Van",
|
"Item Name": "Little Red Van",
|
||||||
SKU: "",
|
SKU: "LRV-904-VNQ",
|
||||||
Notes: "MAX PAYLOAD 595 kg \nMAX LOAD LENGTH 1620 mm",
|
Notes: "Max payload 595 kg \nMax load length 1620 mm",
|
||||||
tableId: "ta_bb_inventory",
|
tableId: "ta_bb_inventory",
|
||||||
"Created At": "2022-11-10T19:55:02.367Z",
|
"Created At": "2022-11-10T19:55:02.367Z",
|
||||||
"Updated At": "2022-11-10T20:05:13.504Z",
|
"Updated At": "2022-11-10T20:05:13.504Z",
|
||||||
|
|
|
@ -226,15 +226,22 @@ export interface components {
|
||||||
type?: "link";
|
type?: "link";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description The name of the column which a relationship column is related to in another table. */
|
/** @description The name of the column which a relationship column is related to in another table. */
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
/** @description The ID of the table which a relationship column is related to. */
|
/** @description The ID of the table which a relationship column is related to. */
|
||||||
|
@ -261,15 +268,22 @@ export interface components {
|
||||||
type?: "formula";
|
type?: "formula";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
||||||
formula?: string;
|
formula?: string;
|
||||||
/**
|
/**
|
||||||
|
@ -280,7 +294,7 @@ export interface components {
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* @description Defines the type of the column, most explain themselves, a link column is a relationship.
|
* @description Defines the type of the column
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
type?:
|
type?:
|
||||||
|
@ -293,8 +307,6 @@ export interface components {
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
| "attachment_single"
|
| "attachment_single"
|
||||||
| "link"
|
|
||||||
| "formula"
|
|
||||||
| "auto"
|
| "auto"
|
||||||
| "ai"
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
|
@ -306,15 +318,22 @@ export interface components {
|
||||||
| "bb_reference_single";
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -335,15 +354,22 @@ export interface components {
|
||||||
type?: "link";
|
type?: "link";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description The name of the column which a relationship column is related to in another table. */
|
/** @description The name of the column which a relationship column is related to in another table. */
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
/** @description The ID of the table which a relationship column is related to. */
|
/** @description The ID of the table which a relationship column is related to. */
|
||||||
|
@ -373,15 +399,22 @@ export interface components {
|
||||||
type?: "formula";
|
type?: "formula";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
||||||
formula?: string;
|
formula?: string;
|
||||||
/**
|
/**
|
||||||
|
@ -392,7 +425,7 @@ export interface components {
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* @description Defines the type of the column, most explain themselves, a link column is a relationship.
|
* @description Defines the type of the column
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
type?:
|
type?:
|
||||||
|
@ -405,8 +438,6 @@ export interface components {
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
| "attachment_single"
|
| "attachment_single"
|
||||||
| "link"
|
|
||||||
| "formula"
|
|
||||||
| "auto"
|
| "auto"
|
||||||
| "ai"
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
|
@ -418,15 +449,22 @@ export interface components {
|
||||||
| "bb_reference_single";
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description The ID of the table. */
|
/** @description The ID of the table. */
|
||||||
|
@ -449,15 +487,22 @@ export interface components {
|
||||||
type?: "link";
|
type?: "link";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description The name of the column which a relationship column is related to in another table. */
|
/** @description The name of the column which a relationship column is related to in another table. */
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
/** @description The ID of the table which a relationship column is related to. */
|
/** @description The ID of the table which a relationship column is related to. */
|
||||||
|
@ -487,15 +532,22 @@ export interface components {
|
||||||
type?: "formula";
|
type?: "formula";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
/** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */
|
||||||
formula?: string;
|
formula?: string;
|
||||||
/**
|
/**
|
||||||
|
@ -506,7 +558,7 @@ export interface components {
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* @description Defines the type of the column, most explain themselves, a link column is a relationship.
|
* @description Defines the type of the column
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
type?:
|
type?:
|
||||||
|
@ -519,8 +571,6 @@ export interface components {
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
| "attachment_single"
|
| "attachment_single"
|
||||||
| "link"
|
|
||||||
| "formula"
|
|
||||||
| "auto"
|
| "auto"
|
||||||
| "ai"
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
|
@ -532,15 +582,22 @@ export interface components {
|
||||||
| "bb_reference_single";
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
type?: string;
|
||||||
type?: "string" | "number" | "object" | "boolean";
|
presence?:
|
||||||
/** @description Defines whether the column is required or not. */
|
| boolean
|
||||||
presence?: boolean;
|
| {
|
||||||
|
/** @description Defines whether the value is allowed to be empty or not. */
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
};
|
||||||
|
/** @description Defines the valid values for this column. */
|
||||||
|
inclusion?: unknown[];
|
||||||
};
|
};
|
||||||
/** @description The name of the column. */
|
/** @description The name of the column. */
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @description Defines whether the column is automatically generated. */
|
/** @description Defines whether the column is automatically generated. */
|
||||||
autocolumn?: boolean;
|
autocolumn?: boolean;
|
||||||
|
/** @description Defines the width of the column in the data UI. */
|
||||||
|
width?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description The ID of the table. */
|
/** @description The ID of the table. */
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {
|
||||||
EnrichedQueryJson,
|
EnrichedQueryJson,
|
||||||
FieldType,
|
FieldType,
|
||||||
isLogicalSearchOperator,
|
isLogicalSearchOperator,
|
||||||
|
LockName,
|
||||||
|
LockType,
|
||||||
Operation,
|
Operation,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
|
@ -30,6 +32,7 @@ import {
|
||||||
} from "../../../tables/internal/sqs"
|
} from "../../../tables/internal/sqs"
|
||||||
import {
|
import {
|
||||||
context,
|
context,
|
||||||
|
locks,
|
||||||
sql,
|
sql,
|
||||||
SQLITE_DESIGN_DOC_ID,
|
SQLITE_DESIGN_DOC_ID,
|
||||||
SQS_DATASOURCE_INTERNAL,
|
SQS_DATASOURCE_INTERNAL,
|
||||||
|
@ -509,7 +512,14 @@ export async function search(
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = typeof err === "string" ? err : err.message
|
const msg = typeof err === "string" ? err : err.message
|
||||||
if (!opts?.retrying && resyncDefinitionsRequired(err.status, msg)) {
|
if (!opts?.retrying && resyncDefinitionsRequired(err.status, msg)) {
|
||||||
await sdk.tables.sqs.syncDefinition()
|
await locks.doWithLock(
|
||||||
|
{
|
||||||
|
type: LockType.AUTO_EXTEND,
|
||||||
|
name: LockName.SQS_SYNC_DEFINITIONS,
|
||||||
|
resource: context.getAppId(),
|
||||||
|
},
|
||||||
|
sdk.tables.sqs.syncDefinition
|
||||||
|
)
|
||||||
return search(options, source, { retrying: true })
|
return search(options, source, { retrying: true })
|
||||||
}
|
}
|
||||||
// previously the internal table didn't error when a column didn't exist in search
|
// previously the internal table didn't error when a column didn't exist in search
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import { EnrichedBinding } from "../../ui"
|
||||||
|
|
||||||
export interface GenerateJsRequest {
|
export interface GenerateJsRequest {
|
||||||
prompt: string
|
prompt: string
|
||||||
|
bindings?: EnrichedBinding[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenerateJsResponse {
|
export interface GenerateJsResponse {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
import { ContextUser } from "../../sdk"
|
||||||
|
|
||||||
// SSO
|
// SSO
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export interface UserSSO {
|
||||||
|
|
||||||
export type SSOUser = User & UserSSO
|
export type SSOUser = User & UserSSO
|
||||||
|
|
||||||
export function isSSOUser(user: User): user is SSOUser {
|
export function isSSOUser(user: User | ContextUser): user is SSOUser {
|
||||||
return !!(user as SSOUser).providerType
|
return !!(user as SSOUser).providerType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ export enum LockName {
|
||||||
QUOTA_USAGE_EVENT = "quota_usage_event",
|
QUOTA_USAGE_EVENT = "quota_usage_event",
|
||||||
APP_MIGRATION = "app_migrations",
|
APP_MIGRATION = "app_migrations",
|
||||||
PROCESS_USER_INVITE = "process_user_invite",
|
PROCESS_USER_INVITE = "process_user_invite",
|
||||||
|
SQS_SYNC_DEFINITIONS = "sys_sync_definitions",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LockOptions = {
|
export type LockOptions = {
|
||||||
|
|
Loading…
Reference in New Issue