Merge branch 'master' into execute-script-v2
This commit is contained in:
commit
da6fc09c99
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "3.4.6",
|
||||
"version": "3.4.9",
|
||||
"npmClient": "yarn",
|
||||
"concurrency": 20,
|
||||
"command": {
|
||||
|
|
|
@ -247,3 +247,7 @@ export function hasCircularStructure(json: any) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function urlHasProtocol(url: string): boolean {
|
||||
return !!url.match(/^.+:\/\/.+$/)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/typography/dist/index-vars.css"
|
||||
|
||||
export let size = "M"
|
||||
export let serif = false
|
||||
export let weight = null
|
||||
export let textAlign = null
|
||||
export let color = null
|
||||
export let size: "XS" | "S" | "M" | "L" | "XL" = "M"
|
||||
export let serif: boolean = false
|
||||
export let weight: string | null = null
|
||||
export let textAlign: string | null = null
|
||||
export let color: string | null = null
|
||||
</script>
|
||||
|
||||
<p
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"@budibase/shared-core": "*",
|
||||
"@budibase/string-templates": "*",
|
||||
"@budibase/types": "*",
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/autocomplete": "6.9.0",
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.1.8",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
|
|
|
@ -45,10 +45,11 @@
|
|||
import { EditorModes } from "./"
|
||||
import { themeStore } from "@/stores/portal"
|
||||
import type { EditorMode } from "@budibase/types"
|
||||
import type { BindingCompletion } from "@/types"
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
// TODO: work out what best type fits this
|
||||
export let completions: any[] = []
|
||||
export let completions: BindingCompletion[] = []
|
||||
export let mode: EditorMode = EditorModes.Handlebars
|
||||
export let value: string | null = ""
|
||||
export let placeholder: string | null = null
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { getManifest } from "@budibase/string-templates"
|
||||
import sanitizeHtml from "sanitize-html"
|
||||
import { groupBy } from "lodash"
|
||||
import {
|
||||
BindingCompletion,
|
||||
EditorModesMap,
|
||||
Helper,
|
||||
Snippet,
|
||||
} from "@budibase/types"
|
||||
import { EditorModesMap, Helper, Snippet } from "@budibase/types"
|
||||
import { CompletionContext } from "@codemirror/autocomplete"
|
||||
import { EditorView } from "@codemirror/view"
|
||||
import { BindingCompletion, BindingCompletionOption } from "@/types"
|
||||
|
||||
export const EditorModes: EditorModesMap = {
|
||||
JS: {
|
||||
|
@ -25,15 +22,7 @@ export const EditorModes: EditorModesMap = {
|
|||
},
|
||||
}
|
||||
|
||||
export const SECTIONS = {
|
||||
HB_HELPER: {
|
||||
name: "Helper",
|
||||
type: "helper",
|
||||
icon: "Code",
|
||||
},
|
||||
}
|
||||
|
||||
export const buildHelperInfoNode = (completion: any, helper: Helper) => {
|
||||
const buildHelperInfoNode = (helper: Helper) => {
|
||||
const ele = document.createElement("div")
|
||||
ele.classList.add("info-bubble")
|
||||
|
||||
|
@ -65,7 +54,7 @@ const toSpectrumIcon = (name: string) => {
|
|||
</svg>`
|
||||
}
|
||||
|
||||
export const buildSectionHeader = (
|
||||
const buildSectionHeader = (
|
||||
type: string,
|
||||
sectionName: string,
|
||||
icon: string,
|
||||
|
@ -84,30 +73,27 @@ export const buildSectionHeader = (
|
|||
}
|
||||
}
|
||||
|
||||
export const helpersToCompletion = (
|
||||
const helpersToCompletion = (
|
||||
helpers: Record<string, Helper>,
|
||||
mode: { name: "javascript" | "handlebars" }
|
||||
) => {
|
||||
const { type, name: sectionName, icon } = SECTIONS.HB_HELPER
|
||||
const helperSection = buildSectionHeader(type, sectionName, icon, 99)
|
||||
): BindingCompletionOption[] => {
|
||||
const helperSection = buildSectionHeader("helper", "Helpers", "Code", 99)
|
||||
|
||||
return Object.keys(helpers).flatMap(helperName => {
|
||||
let helper = helpers[helperName]
|
||||
const helper = helpers[helperName]
|
||||
return {
|
||||
label: helperName,
|
||||
info: (completion: BindingCompletion) => {
|
||||
return buildHelperInfoNode(completion, helper)
|
||||
},
|
||||
info: () => buildHelperInfoNode(helper),
|
||||
type: "helper",
|
||||
section: helperSection,
|
||||
detail: "Function",
|
||||
apply: (
|
||||
view: any,
|
||||
completion: BindingCompletion,
|
||||
view: EditorView,
|
||||
_completion: BindingCompletionOption,
|
||||
from: number,
|
||||
to: number
|
||||
) => {
|
||||
insertBinding(view, from, to, helperName, mode)
|
||||
insertBinding(view, from, to, helperName, mode, AutocompleteType.HELPER)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -115,7 +101,7 @@ export const helpersToCompletion = (
|
|||
|
||||
export const getHelperCompletions = (mode: {
|
||||
name: "javascript" | "handlebars"
|
||||
}) => {
|
||||
}): BindingCompletionOption[] => {
|
||||
// TODO: manifest needs to be properly typed
|
||||
const manifest: any = getManifest()
|
||||
return Object.keys(manifest).flatMap(key => {
|
||||
|
@ -123,49 +109,33 @@ export const getHelperCompletions = (mode: {
|
|||
})
|
||||
}
|
||||
|
||||
export const snippetAutoComplete = (snippets: Snippet[]) => {
|
||||
return function myCompletions(context: CompletionContext) {
|
||||
if (!snippets?.length) {
|
||||
return null
|
||||
}
|
||||
const word = context.matchBefore(/\w*/)
|
||||
if (!word || (word.from == word.to && !context.explicit)) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
from: word.from,
|
||||
options: snippets.map(snippet => ({
|
||||
label: `snippets.${snippet.name}`,
|
||||
type: "text",
|
||||
simple: true,
|
||||
apply: (
|
||||
view: any,
|
||||
completion: BindingCompletion,
|
||||
from: number,
|
||||
to: number
|
||||
) => {
|
||||
insertSnippet(view, from, to, completion.label)
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
export const snippetAutoComplete = (snippets: Snippet[]): BindingCompletion => {
|
||||
return setAutocomplete(
|
||||
snippets.map(snippet => ({
|
||||
section: buildSectionHeader("snippets", "Snippets", "Code", 100),
|
||||
label: `snippets.${snippet.name}`,
|
||||
displayLabel: snippet.name,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
const bindingFilter = (options: BindingCompletion[], query: string) => {
|
||||
const bindingFilter = (options: BindingCompletionOption[], query: string) => {
|
||||
return options.filter(completion => {
|
||||
const section_parsed = completion.section.name.toLowerCase()
|
||||
const section_parsed = completion.section?.toString().toLowerCase()
|
||||
const label_parsed = completion.label.toLowerCase()
|
||||
const query_parsed = query.toLowerCase()
|
||||
|
||||
return (
|
||||
section_parsed.includes(query_parsed) ||
|
||||
section_parsed?.includes(query_parsed) ||
|
||||
label_parsed.includes(query_parsed)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const hbAutocomplete = (baseCompletions: BindingCompletion[]) => {
|
||||
async function coreCompletion(context: CompletionContext) {
|
||||
export const hbAutocomplete = (
|
||||
baseCompletions: BindingCompletionOption[]
|
||||
): BindingCompletion => {
|
||||
function coreCompletion(context: CompletionContext) {
|
||||
let bindingStart = context.matchBefore(EditorModes.Handlebars.match)
|
||||
|
||||
let options = baseCompletions || []
|
||||
|
@ -191,9 +161,15 @@ export const hbAutocomplete = (baseCompletions: BindingCompletion[]) => {
|
|||
return coreCompletion
|
||||
}
|
||||
|
||||
export const jsAutocomplete = (baseCompletions: BindingCompletion[]) => {
|
||||
async function coreCompletion(context: CompletionContext) {
|
||||
let jsBinding = context.matchBefore(/\$\("[\s\w]*/)
|
||||
function wrappedAutocompleteMatch(context: CompletionContext) {
|
||||
return context.matchBefore(/\$\("[\s\w]*/)
|
||||
}
|
||||
|
||||
export const jsAutocomplete = (
|
||||
baseCompletions: BindingCompletionOption[]
|
||||
): BindingCompletion => {
|
||||
function coreCompletion(context: CompletionContext) {
|
||||
let jsBinding = wrappedAutocompleteMatch(context)
|
||||
let options = baseCompletions || []
|
||||
|
||||
if (jsBinding) {
|
||||
|
@ -217,10 +193,42 @@ export const jsAutocomplete = (baseCompletions: BindingCompletion[]) => {
|
|||
return coreCompletion
|
||||
}
|
||||
|
||||
export const buildBindingInfoNode = (
|
||||
completion: BindingCompletion,
|
||||
binding: any
|
||||
) => {
|
||||
export const jsHelperAutocomplete = (
|
||||
baseCompletions: BindingCompletionOption[]
|
||||
): BindingCompletion => {
|
||||
return setAutocomplete(
|
||||
baseCompletions.map(helper => ({
|
||||
...helper,
|
||||
displayLabel: helper.label,
|
||||
label: `helpers.${helper.label}()`,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
function setAutocomplete(
|
||||
options: BindingCompletionOption[]
|
||||
): BindingCompletion {
|
||||
return function (context: CompletionContext) {
|
||||
if (wrappedAutocompleteMatch(context)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const word = context.matchBefore(/\b\w*(\.\w*)?/)
|
||||
if (!word || (word.from == word.to && !context.explicit)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildBindingInfoNode = (binding: {
|
||||
valueHTML: string
|
||||
value: string | null
|
||||
}) => {
|
||||
if (!binding.valueHTML || binding.value == null) {
|
||||
return null
|
||||
}
|
||||
|
@ -278,18 +286,28 @@ export function jsInsert(
|
|||
return parsedInsert
|
||||
}
|
||||
|
||||
const enum AutocompleteType {
|
||||
BINDING,
|
||||
HELPER,
|
||||
TEXT,
|
||||
}
|
||||
|
||||
// Autocomplete apply behaviour
|
||||
export const insertBinding = (
|
||||
view: any,
|
||||
const insertBinding = (
|
||||
view: EditorView,
|
||||
from: number,
|
||||
to: number,
|
||||
text: string,
|
||||
mode: { name: "javascript" | "handlebars" }
|
||||
mode: { name: "javascript" | "handlebars" },
|
||||
type: AutocompleteType
|
||||
) => {
|
||||
let parsedInsert
|
||||
|
||||
if (mode.name == "javascript") {
|
||||
parsedInsert = jsInsert(view.state.doc?.toString(), from, to, text)
|
||||
parsedInsert = jsInsert(view.state.doc?.toString(), from, to, text, {
|
||||
helper: type === AutocompleteType.HELPER,
|
||||
disableWrapping: type === AutocompleteType.TEXT,
|
||||
})
|
||||
} else if (mode.name == "handlebars") {
|
||||
parsedInsert = hbInsert(view.state.doc?.toString(), from, to, text)
|
||||
} else {
|
||||
|
@ -319,30 +337,11 @@ export const insertBinding = (
|
|||
})
|
||||
}
|
||||
|
||||
export const insertSnippet = (
|
||||
view: any,
|
||||
from: number,
|
||||
to: number,
|
||||
text: string
|
||||
) => {
|
||||
let cursorPos = from + text.length
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from,
|
||||
to,
|
||||
insert: text,
|
||||
},
|
||||
selection: {
|
||||
anchor: cursorPos,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: typing in this function isn't great
|
||||
export const bindingsToCompletions = (
|
||||
bindings: any,
|
||||
mode: { name: "javascript" | "handlebars" }
|
||||
) => {
|
||||
): BindingCompletionOption[] => {
|
||||
const bindingByCategory = groupBy(bindings, "category")
|
||||
const categoryMeta = bindings?.reduce((acc: any, ele: any) => {
|
||||
acc[ele.category] = acc[ele.category] || {}
|
||||
|
@ -356,46 +355,54 @@ export const bindingsToCompletions = (
|
|||
return acc
|
||||
}, {})
|
||||
|
||||
const completions = Object.keys(bindingByCategory).reduce(
|
||||
(comps: any, catKey: string) => {
|
||||
const { icon, rank } = categoryMeta[catKey] || {}
|
||||
const completions = Object.keys(bindingByCategory).reduce<
|
||||
BindingCompletionOption[]
|
||||
>((comps, catKey) => {
|
||||
const { icon, rank } = categoryMeta[catKey] || {}
|
||||
|
||||
const bindingSectionHeader = buildSectionHeader(
|
||||
// @ts-ignore something wrong with this - logically this should be dictionary
|
||||
bindingByCategory.type,
|
||||
catKey,
|
||||
icon || "",
|
||||
typeof rank == "number" ? rank : 1
|
||||
)
|
||||
const bindingSectionHeader = buildSectionHeader(
|
||||
// @ts-ignore something wrong with this - logically this should be dictionary
|
||||
bindingByCategory.type,
|
||||
catKey,
|
||||
icon || "",
|
||||
typeof rank == "number" ? rank : 1
|
||||
)
|
||||
|
||||
return [
|
||||
...comps,
|
||||
...bindingByCategory[catKey].reduce((acc, binding) => {
|
||||
comps.push(
|
||||
...bindingByCategory[catKey].reduce<BindingCompletionOption[]>(
|
||||
(acc, binding) => {
|
||||
let displayType = binding.fieldSchema?.type || binding.display?.type
|
||||
acc.push({
|
||||
label:
|
||||
binding.display?.name || binding.readableBinding || "NO NAME",
|
||||
info: (completion: BindingCompletion) => {
|
||||
return buildBindingInfoNode(completion, binding)
|
||||
},
|
||||
info: () => buildBindingInfoNode(binding),
|
||||
type: "binding",
|
||||
detail: displayType,
|
||||
section: bindingSectionHeader,
|
||||
apply: (
|
||||
view: any,
|
||||
completion: BindingCompletion,
|
||||
view: EditorView,
|
||||
_completion: BindingCompletionOption,
|
||||
from: number,
|
||||
to: number
|
||||
) => {
|
||||
insertBinding(view, from, to, binding.readableBinding, mode)
|
||||
insertBinding(
|
||||
view,
|
||||
from,
|
||||
to,
|
||||
binding.readableBinding,
|
||||
mode,
|
||||
AutocompleteType.BINDING
|
||||
)
|
||||
},
|
||||
})
|
||||
return acc
|
||||
}, []),
|
||||
]
|
||||
},
|
||||
[]
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
return comps
|
||||
}, [])
|
||||
|
||||
return completions
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
snippetAutoComplete,
|
||||
EditorModes,
|
||||
bindingsToCompletions,
|
||||
jsHelperAutocomplete,
|
||||
} from "../CodeEditor"
|
||||
import BindingSidePanel from "./BindingSidePanel.svelte"
|
||||
import EvaluationSidePanel from "./EvaluationSidePanel.svelte"
|
||||
|
@ -34,7 +35,6 @@
|
|||
import { BindingMode, SidePanel } from "@budibase/types"
|
||||
import type {
|
||||
EnrichedBinding,
|
||||
BindingCompletion,
|
||||
Snippet,
|
||||
Helper,
|
||||
CaretPositionFn,
|
||||
|
@ -42,7 +42,7 @@
|
|||
JSONValue,
|
||||
} from "@budibase/types"
|
||||
import type { Log } from "@budibase/string-templates"
|
||||
import type { CompletionContext } from "@codemirror/autocomplete"
|
||||
import type { BindingCompletion, BindingCompletionOption } from "@/types"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -91,7 +91,10 @@
|
|||
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
||||
$: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos)
|
||||
$: hbsCompletions = getHBSCompletions(bindingCompletions)
|
||||
$: jsCompletions = getJSCompletions(bindingCompletions, snippets, useSnippets)
|
||||
$: jsCompletions = getJSCompletions(bindingCompletions, snippets, {
|
||||
useHelpers: allowHelpers,
|
||||
useSnippets,
|
||||
})
|
||||
$: {
|
||||
// Ensure a valid side panel option is always selected
|
||||
if (sidePanel && !sidePanelOptions.includes(sidePanel)) {
|
||||
|
@ -99,7 +102,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getHBSCompletions = (bindingCompletions: BindingCompletion[]) => {
|
||||
const getHBSCompletions = (bindingCompletions: BindingCompletionOption[]) => {
|
||||
return [
|
||||
hbAutocomplete([
|
||||
...bindingCompletions,
|
||||
|
@ -109,17 +112,23 @@
|
|||
}
|
||||
|
||||
const getJSCompletions = (
|
||||
bindingCompletions: BindingCompletion[],
|
||||
bindingCompletions: BindingCompletionOption[],
|
||||
snippets: Snippet[] | null,
|
||||
useSnippets?: boolean
|
||||
config: {
|
||||
useHelpers: boolean
|
||||
useSnippets: boolean
|
||||
}
|
||||
) => {
|
||||
const completions: ((_: CompletionContext) => any)[] = [
|
||||
jsAutocomplete([
|
||||
...bindingCompletions,
|
||||
...getHelperCompletions(EditorModes.JS),
|
||||
]),
|
||||
]
|
||||
if (useSnippets && snippets) {
|
||||
const completions: BindingCompletion[] = []
|
||||
if (bindingCompletions.length) {
|
||||
completions.push(jsAutocomplete([...bindingCompletions]))
|
||||
}
|
||||
if (config.useHelpers) {
|
||||
completions.push(
|
||||
jsHelperAutocomplete([...getHelperCompletions(EditorModes.JS)])
|
||||
)
|
||||
}
|
||||
if (config.useSnippets && snippets) {
|
||||
completions.push(snippetAutoComplete(snippets))
|
||||
}
|
||||
return completions
|
||||
|
@ -381,7 +390,7 @@
|
|||
autofocus={autofocusEditor}
|
||||
placeholder={placeholder ||
|
||||
"Add bindings by typing $ or use the menu on the right"}
|
||||
jsBindingWrapping
|
||||
jsBindingWrapping={bindingCompletions.length > 0}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
allowHBS={false}
|
||||
allowJS
|
||||
allowSnippets={false}
|
||||
allowHelpers={false}
|
||||
showTabBar={false}
|
||||
placeholder="return function(input) ❴ ... ❵"
|
||||
value={code}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { Input, Label } from "@budibase/bbui"
|
||||
import { previewStore, selectedScreen } from "@/stores/builder"
|
||||
import type { ComponentContext } from "@budibase/types"
|
||||
|
||||
export let baseRoute = ""
|
||||
|
||||
let testValue: string | undefined
|
||||
|
||||
$: routeParams = baseRoute.match(/:[a-zA-Z]+/g) || []
|
||||
$: hasUrlParams = routeParams.length > 0
|
||||
$: placeholder = getPlaceholder(baseRoute)
|
||||
$: baseInput = createBaseInput(baseRoute)
|
||||
$: updateTestValueFromContext($previewStore.selectedComponentContext)
|
||||
$: if ($selectedScreen) {
|
||||
testValue = ""
|
||||
}
|
||||
|
||||
const getPlaceholder = (route: string) => {
|
||||
const trimmed = route.replace(/\/$/, "")
|
||||
if (trimmed.startsWith("/:")) {
|
||||
return "1"
|
||||
}
|
||||
const segments = trimmed.split("/").slice(2)
|
||||
let count = 1
|
||||
return segments
|
||||
.map(segment => (segment.startsWith(":") ? count++ : segment))
|
||||
.join("/")
|
||||
}
|
||||
|
||||
// This function is needed to repopulate the test value from componentContext
|
||||
// when a user navigates to another component and then back again
|
||||
const updateTestValueFromContext = (context: ComponentContext | null) => {
|
||||
if (context?.url && !testValue) {
|
||||
const { wild, ...urlParams } = context.url
|
||||
const queryParams = context.query
|
||||
if (Object.values(urlParams).some(v => Boolean(v))) {
|
||||
let value = baseRoute
|
||||
.split("/")
|
||||
.slice(2)
|
||||
.map(segment =>
|
||||
segment.startsWith(":")
|
||||
? urlParams[segment.slice(1)] || ""
|
||||
: segment
|
||||
)
|
||||
.join("/")
|
||||
const qs = new URLSearchParams(queryParams).toString()
|
||||
if (qs) {
|
||||
value += `?${qs}`
|
||||
}
|
||||
testValue = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createBaseInput = (baseRoute: string) => {
|
||||
return baseRoute === "/" || baseRoute.split("/")[1]?.startsWith(":")
|
||||
? "/"
|
||||
: `/${baseRoute.split("/")[1]}/`
|
||||
}
|
||||
|
||||
const onVariableChange = (e: CustomEvent) => {
|
||||
previewStore.setUrlTestData({ route: baseRoute, testValue: e.detail })
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
previewStore.requestComponentContext()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if hasUrlParams}
|
||||
<div class="url-test-section">
|
||||
<div class="info">
|
||||
<Label size="M">Set temporary URL variables for design preview</Label>
|
||||
</div>
|
||||
<div class="url-test-container">
|
||||
<div class="base-input">
|
||||
<Input disabled={true} value={baseInput} />
|
||||
</div>
|
||||
<div class="variable-input">
|
||||
<Input value={testValue} on:change={onVariableChange} {placeholder} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.url-test-section {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.url-test-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.base-input {
|
||||
width: 98px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.base-input :global(.spectrum-Textfield-input) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
||||
.variable-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.variable-input :global(.spectrum-Textfield-input) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
</style>
|
|
@ -15,6 +15,7 @@
|
|||
import ButtonActionEditor from "@/components/design/settings/controls/ButtonActionEditor/ButtonActionEditor.svelte"
|
||||
import { getBindableProperties } from "@/dataBinding"
|
||||
import BarButtonList from "@/components/design/settings/controls/BarButtonList.svelte"
|
||||
import URLVariableTestInput from "@/components/design/settings/controls/URLVariableTestInput.svelte"
|
||||
|
||||
$: bindings = getBindableProperties($selectedScreen, null)
|
||||
$: screenSettings = getScreenSettings($selectedScreen)
|
||||
|
@ -93,6 +94,13 @@
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "urlTest",
|
||||
control: URLVariableTestInput,
|
||||
props: {
|
||||
baseRoute: screen.routing?.route,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return settings
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
import { fly } from "svelte/transition"
|
||||
import { findComponentPath } from "@/helpers/components"
|
||||
|
||||
// Smallest possible 1x1 transparent GIF
|
||||
const ghost = new Image(1, 1)
|
||||
ghost.src =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
|
||||
let searchString
|
||||
let searchRef
|
||||
let selectedIndex
|
||||
|
@ -217,7 +222,8 @@
|
|||
}
|
||||
})
|
||||
|
||||
const onDragStart = component => {
|
||||
const onDragStart = (e, component) => {
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
previewStore.startDrag(component)
|
||||
}
|
||||
|
||||
|
@ -250,13 +256,12 @@
|
|||
{#each category.children as component}
|
||||
<div
|
||||
draggable="true"
|
||||
on:dragstart={() => onDragStart(component.component)}
|
||||
on:dragstart={e => onDragStart(e, component.component)}
|
||||
on:dragend={onDragEnd}
|
||||
class="component"
|
||||
class:selected={selectedIndex === orderMap[component.component]}
|
||||
on:click={() => addComponent(component.component)}
|
||||
on:mouseover={() => (selectedIndex = null)}
|
||||
on:focus
|
||||
on:mouseenter={() => (selectedIndex = null)}
|
||||
>
|
||||
<Icon name={component.icon} />
|
||||
<Body size="XS">{component.name}</Body>
|
||||
|
@ -308,7 +313,6 @@
|
|||
}
|
||||
.component:hover {
|
||||
background: var(--spectrum-global-color-gray-300);
|
||||
cursor: pointer;
|
||||
}
|
||||
.component :global(.spectrum-Body) {
|
||||
line-height: 1.2 !important;
|
||||
|
|
|
@ -189,8 +189,8 @@
|
|||
} else if (type === "reload-plugin") {
|
||||
await componentStore.refreshDefinitions()
|
||||
} else if (type === "drop-new-component") {
|
||||
const { component, parent, index } = data
|
||||
await componentStore.create(component, null, parent, index)
|
||||
const { component, parent, index, props } = data
|
||||
await componentStore.create(component, props, parent, index)
|
||||
} else if (type === "add-parent-component") {
|
||||
const { componentId, parentType } = data
|
||||
await componentStore.addParent(componentId, parentType)
|
||||
|
|
|
@ -39,7 +39,7 @@ interface AppMetaState {
|
|||
appInstance: { _id: string } | null
|
||||
initialised: boolean
|
||||
hasAppPackage: boolean
|
||||
usedPlugins: Plugin[] | null
|
||||
usedPlugins: Plugin[]
|
||||
automations: AutomationSettings
|
||||
routes: { [key: string]: any }
|
||||
version?: string
|
||||
|
@ -76,7 +76,7 @@ export const INITIAL_APP_META_STATE: AppMetaState = {
|
|||
appInstance: null,
|
||||
initialised: false,
|
||||
hasAppPackage: false,
|
||||
usedPlugins: null,
|
||||
usedPlugins: [],
|
||||
automations: {},
|
||||
routes: {},
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ export class AppMetaStore extends BudiStore<AppMetaState> {
|
|||
appInstance: app.instance,
|
||||
revertableVersion: app.revertableVersion,
|
||||
upgradableVersion: app.upgradableVersion,
|
||||
usedPlugins: app.usedPlugins || null,
|
||||
usedPlugins: app.usedPlugins || [],
|
||||
icon: app.icon,
|
||||
features: {
|
||||
...INITIAL_APP_META_STATE.features,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
findComponentParent,
|
||||
findAllMatchingComponents,
|
||||
makeComponentUnique,
|
||||
findComponentType,
|
||||
} from "@/helpers/components"
|
||||
import { getComponentFieldOptions } from "@/helpers/formFields"
|
||||
import { selectedScreen } from "./screens"
|
||||
|
@ -139,10 +140,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
|
||||
/**
|
||||
* Retrieve the component definition object
|
||||
* @param {string} componentType
|
||||
* @example
|
||||
* '@budibase/standard-components/container'
|
||||
* @returns {object}
|
||||
*/
|
||||
getDefinition(componentType: string) {
|
||||
if (!componentType) {
|
||||
|
@ -151,10 +148,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
return get(this.store).components[componentType]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
getDefaultDatasource() {
|
||||
// Ignore users table
|
||||
const validTables = get(tables).list.filter(x => x._id !== "ta_users")
|
||||
|
@ -188,8 +181,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
/**
|
||||
* Takes an enriched component instance and applies any required migration
|
||||
* logic
|
||||
* @param {object} enrichedComponent
|
||||
* @returns {object} migrated Component
|
||||
*/
|
||||
migrateSettings(enrichedComponent: Component) {
|
||||
const componentPrefix = "@budibase/standard-components"
|
||||
|
@ -230,22 +221,15 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
for (let setting of filterableTypes || []) {
|
||||
const isLegacy = Array.isArray(enrichedComponent[setting.key])
|
||||
if (isLegacy) {
|
||||
const processedSetting = utils.processSearchFilters(
|
||||
enrichedComponent[setting.key] = utils.processSearchFilters(
|
||||
enrichedComponent[setting.key]
|
||||
)
|
||||
enrichedComponent[setting.key] = processedSetting
|
||||
migrated = true
|
||||
}
|
||||
}
|
||||
return migrated
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} component
|
||||
* @param {object} opts
|
||||
* @returns
|
||||
*/
|
||||
enrichEmptySettings(
|
||||
component: Component,
|
||||
opts: { screen?: Screen; parent?: Component; useDefaultValues?: boolean }
|
||||
|
@ -280,14 +264,25 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
type: "table",
|
||||
}
|
||||
} else if (setting.type === "dataProvider") {
|
||||
// Pick closest data provider where required
|
||||
let providerId
|
||||
|
||||
// Pick closest parent data provider if one exists
|
||||
const path = findComponentPath(screen.props, treeId)
|
||||
const providers = path.filter((component: Component) =>
|
||||
component._component?.endsWith("/dataprovider")
|
||||
)
|
||||
if (providers.length) {
|
||||
const id = providers[providers.length - 1]?._id
|
||||
component[setting.key] = `{{ literal ${safe(id)} }}`
|
||||
providerId = providers[providers.length - 1]?._id
|
||||
|
||||
// If none in our direct path, select the first one the screen
|
||||
if (!providerId) {
|
||||
providerId = findComponentType(
|
||||
screen.props,
|
||||
"@budibase/standard-components/dataprovider"
|
||||
)?._id
|
||||
}
|
||||
|
||||
if (providerId) {
|
||||
component[setting.key] = `{{ literal ${safe(providerId)} }}`
|
||||
}
|
||||
} else if (setting.type.startsWith("field/")) {
|
||||
// Autofill form field names
|
||||
|
@ -427,17 +422,10 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @param {object} presetProps
|
||||
* @param {object} parent
|
||||
* @returns
|
||||
*/
|
||||
createInstance(
|
||||
componentType: string,
|
||||
presetProps: any,
|
||||
parent: any
|
||||
presetProps?: Record<string, any>,
|
||||
parent?: Component
|
||||
): Component | null {
|
||||
const screen = get(selectedScreen)
|
||||
if (!screen) {
|
||||
|
@ -463,7 +451,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
_id: Helpers.uuid(),
|
||||
_component: definition.component,
|
||||
_styles: {
|
||||
normal: {},
|
||||
normal: { ...presetProps?._styles?.normal },
|
||||
hover: {},
|
||||
active: {},
|
||||
},
|
||||
|
@ -512,19 +500,11 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @param {object} presetProps
|
||||
* @param {object} parent
|
||||
* @param {number} index
|
||||
* @returns
|
||||
*/
|
||||
async create(
|
||||
componentType: string,
|
||||
presetProps: any,
|
||||
parent: Component,
|
||||
index: number
|
||||
presetProps?: Record<string, any>,
|
||||
parent?: Component,
|
||||
index?: number
|
||||
) {
|
||||
const state = get(this.store)
|
||||
const componentInstance = this.createInstance(
|
||||
|
@ -611,13 +591,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
return componentInstance
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} patchFn
|
||||
* @param {string} componentId
|
||||
* @param {string} screenId
|
||||
* @returns
|
||||
*/
|
||||
async patch(
|
||||
patchFn: (component: Component, screen: Screen) => any,
|
||||
componentId?: string,
|
||||
|
@ -652,11 +625,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
await screenStore.patch(patchScreen, screenId)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} component
|
||||
* @returns
|
||||
*/
|
||||
async delete(component: Component) {
|
||||
if (!component) {
|
||||
return
|
||||
|
@ -737,13 +705,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} targetComponent
|
||||
* @param {string} mode
|
||||
* @param {object} targetScreen
|
||||
* @returns
|
||||
*/
|
||||
async paste(
|
||||
targetComponent: Component,
|
||||
mode: string,
|
||||
|
@ -1101,6 +1062,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
|
||||
async updateStyles(styles: Record<string, string>, id: string) {
|
||||
const patchFn = (component: Component) => {
|
||||
delete component._placeholder
|
||||
component._styles.normal = {
|
||||
...component._styles.normal,
|
||||
...styles,
|
||||
|
@ -1231,7 +1193,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
}
|
||||
|
||||
// Create new parent instance
|
||||
const newParentDefinition = this.createInstance(parentType, null, parent)
|
||||
const newParentDefinition = this.createInstance(parentType)
|
||||
if (!newParentDefinition) {
|
||||
return
|
||||
}
|
||||
|
@ -1267,10 +1229,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
|
||||
/**
|
||||
* Check if the components settings have been cached
|
||||
* @param {string} componentType
|
||||
* @example
|
||||
* '@budibase/standard-components/container'
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isCached(componentType: string) {
|
||||
const settings = get(this.store).settingsCache
|
||||
|
@ -1279,11 +1237,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
|
||||
/**
|
||||
* Cache component settings
|
||||
* @param {string} componentType
|
||||
* @param {object} definition
|
||||
* @example
|
||||
* '@budibase/standard-components/container'
|
||||
* @returns {array} the settings
|
||||
*/
|
||||
cacheSettings(componentType: string, definition: ComponentDefinition | null) {
|
||||
let settings: ComponentSetting[] = []
|
||||
|
@ -1313,12 +1266,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
|||
/**
|
||||
* Retrieve an array of the component settings.
|
||||
* These settings are cached because they cannot change at run time.
|
||||
*
|
||||
* Searches a component's definition for a setting matching a certain predicate.
|
||||
* @param {string} componentType
|
||||
* @example
|
||||
* '@budibase/standard-components/container'
|
||||
* @returns {Array<object>}
|
||||
*/
|
||||
getComponentSettings(componentType: string) {
|
||||
if (!componentType) {
|
||||
|
|
|
@ -4,15 +4,13 @@ import { appStore } from "@/stores/builder"
|
|||
import { BudiStore } from "../BudiStore"
|
||||
import { AppNavigation, AppNavigationLink, UIObject } from "@budibase/types"
|
||||
|
||||
interface BuilderNavigationStore extends AppNavigation {}
|
||||
|
||||
export const INITIAL_NAVIGATION_STATE = {
|
||||
navigation: "Top",
|
||||
links: [],
|
||||
textAlign: "Left",
|
||||
}
|
||||
|
||||
export class NavigationStore extends BudiStore<BuilderNavigationStore> {
|
||||
export class NavigationStore extends BudiStore<AppNavigation> {
|
||||
constructor() {
|
||||
super(INITIAL_NAVIGATION_STATE)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { get } from "svelte/store"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { PreviewDevice, ComponentContext } from "@budibase/types"
|
||||
|
||||
type PreviewDevice = "desktop" | "tablet" | "mobile"
|
||||
type PreviewEventHandler = (name: string, payload?: any) => void
|
||||
type ComponentContext = Record<string, any>
|
||||
|
||||
interface PreviewState {
|
||||
previewDevice: PreviewDevice
|
||||
|
@ -54,7 +53,7 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
|||
}))
|
||||
}
|
||||
|
||||
startDrag(component: any) {
|
||||
async startDrag(component: string) {
|
||||
this.sendEvent("dragging-new-component", {
|
||||
dragging: true,
|
||||
component,
|
||||
|
@ -86,6 +85,10 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
|||
this.sendEvent("builder-state", data)
|
||||
}
|
||||
|
||||
setUrlTestData(data: Record<string, any>) {
|
||||
this.sendEvent("builder-url-test-data", data)
|
||||
}
|
||||
|
||||
requestComponentContext() {
|
||||
this.sendEvent("request-context")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { CompletionContext, Completion } from "@codemirror/autocomplete"
|
||||
|
||||
export type BindingCompletion = (context: CompletionContext) => {
|
||||
from: number
|
||||
options: Completion[]
|
||||
} | null
|
||||
|
||||
export type BindingCompletionOption = Completion
|
|
@ -0,0 +1 @@
|
|||
export * from "./bindings"
|
|
@ -1455,7 +1455,8 @@
|
|||
"type": "icon",
|
||||
"label": "Icon",
|
||||
"key": "icon",
|
||||
"required": true
|
||||
"required": true,
|
||||
"defaultValue": "ri-star-fill"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
"type": "module",
|
||||
"svelte": "src/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/budibase-client.js",
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { createAPIClient } from "@budibase/frontend-core"
|
||||
import { authStore } from "../stores/auth"
|
||||
import {
|
||||
notificationStore,
|
||||
devToolsEnabled,
|
||||
devToolsStore,
|
||||
} from "../stores/index"
|
||||
import { notificationStore, devToolsEnabled, devToolsStore } from "../stores"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export const API = createAPIClient({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext, onDestroy, onMount, setContext } from "svelte"
|
||||
import { builderStore } from "stores/builder.js"
|
||||
import { blockStore } from "stores/blocks"
|
||||
import { builderStore } from "@/stores/builder.js"
|
||||
import { blockStore } from "@/stores/blocks"
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable } = getContext("sdk")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { getContext, onDestroy } from "svelte"
|
||||
import { generate } from "shortid"
|
||||
import { builderStore } from "../stores/builder.js"
|
||||
import Component from "components/Component.svelte"
|
||||
import Component from "@/components/Component.svelte"
|
||||
|
||||
export let type
|
||||
export let props
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { Constants, CookieUtils } from "@budibase/frontend-core"
|
||||
import { getThemeClassNames } from "@budibase/shared-core"
|
||||
import Component from "./Component.svelte"
|
||||
import SDK from "sdk"
|
||||
import SDK from "@/sdk"
|
||||
import {
|
||||
featuresStore,
|
||||
createContextStore,
|
||||
|
@ -22,28 +22,30 @@
|
|||
environmentStore,
|
||||
sidePanelStore,
|
||||
modalStore,
|
||||
} from "stores"
|
||||
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
||||
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
||||
import PeekScreenDisplay from "components/overlay/PeekScreenDisplay.svelte"
|
||||
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
||||
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
||||
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
||||
import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
|
||||
import QueryParamsProvider from "components/context/QueryParamsProvider.svelte"
|
||||
import SettingsBar from "components/preview/SettingsBar.svelte"
|
||||
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
||||
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
||||
} from "@/stores"
|
||||
import NotificationDisplay from "./overlay/NotificationDisplay.svelte"
|
||||
import ConfirmationDisplay from "./overlay/ConfirmationDisplay.svelte"
|
||||
import PeekScreenDisplay from "./overlay/PeekScreenDisplay.svelte"
|
||||
import UserBindingsProvider from "./context/UserBindingsProvider.svelte"
|
||||
import DeviceBindingsProvider from "./context/DeviceBindingsProvider.svelte"
|
||||
import StateBindingsProvider from "./context/StateBindingsProvider.svelte"
|
||||
import TestUrlBindingsProvider from "./context/TestUrlBindingsProvider.svelte"
|
||||
import RowSelectionProvider from "./context/RowSelectionProvider.svelte"
|
||||
import QueryParamsProvider from "./context/QueryParamsProvider.svelte"
|
||||
import SettingsBar from "./preview/SettingsBar.svelte"
|
||||
import SelectionIndicator from "./preview/SelectionIndicator.svelte"
|
||||
import HoverIndicator from "./preview/HoverIndicator.svelte"
|
||||
import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
|
||||
import DNDHandler from "components/preview/DNDHandler.svelte"
|
||||
import GridDNDHandler from "components/preview/GridDNDHandler.svelte"
|
||||
import KeyboardManager from "components/preview/KeyboardManager.svelte"
|
||||
import DevToolsHeader from "components/devtools/DevToolsHeader.svelte"
|
||||
import DevTools from "components/devtools/DevTools.svelte"
|
||||
import FreeFooter from "components/FreeFooter.svelte"
|
||||
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
||||
import DNDHandler from "./preview/DNDHandler.svelte"
|
||||
import GridDNDHandler from "./preview/GridDNDHandler.svelte"
|
||||
import KeyboardManager from "./preview/KeyboardManager.svelte"
|
||||
import DevToolsHeader from "./devtools/DevToolsHeader.svelte"
|
||||
import DevTools from "./devtools/DevTools.svelte"
|
||||
import FreeFooter from "./FreeFooter.svelte"
|
||||
import MaintenanceScreen from "./MaintenanceScreen.svelte"
|
||||
import SnippetsProvider from "./context/SnippetsProvider.svelte"
|
||||
import EmbedProvider from "./context/EmbedProvider.svelte"
|
||||
import DNDSelectionIndicators from "./preview/DNDSelectionIndicators.svelte"
|
||||
|
||||
// Provide contexts
|
||||
setContext("sdk", SDK)
|
||||
|
@ -168,107 +170,110 @@
|
|||
<StateBindingsProvider>
|
||||
<RowSelectionProvider>
|
||||
<QueryParamsProvider>
|
||||
<SnippetsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice ===
|
||||
"tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice ===
|
||||
"mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
<TestUrlBindingsProvider>
|
||||
<SnippetsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue
|
||||
persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice ===
|
||||
"tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice ===
|
||||
"mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
{/if}
|
||||
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue
|
||||
persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
{/if}
|
||||
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
||||
<FreeFooter />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
||||
<FreeFooter />
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
<DNDSelectionIndicators />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
{/if}
|
||||
</div>
|
||||
</SnippetsProvider>
|
||||
</SnippetsProvider>
|
||||
</TestUrlBindingsProvider>
|
||||
</QueryParamsProvider>
|
||||
</RowSelectionProvider>
|
||||
</StateBindingsProvider>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<script>
|
||||
import { getContext, setContext, onMount } from "svelte"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { enrichProps, propsAreSame } from "utils/componentProps"
|
||||
import { enrichProps, propsAreSame } from "@/utils/componentProps"
|
||||
import { getSettingsDefinition } from "@budibase/frontend-core"
|
||||
import {
|
||||
builderStore,
|
||||
|
@ -20,12 +20,15 @@
|
|||
appStore,
|
||||
dndComponentPath,
|
||||
dndIsDragging,
|
||||
} from "stores"
|
||||
} from "@/stores"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
||||
import EmptyPlaceholder from "components/app/EmptyPlaceholder.svelte"
|
||||
import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte"
|
||||
import ComponentErrorState from "components/error-states/ComponentErrorState.svelte"
|
||||
import {
|
||||
getActiveConditions,
|
||||
reduceConditionActions,
|
||||
} from "@/utils/conditions"
|
||||
import EmptyPlaceholder from "@/components/app/EmptyPlaceholder.svelte"
|
||||
import ScreenPlaceholder from "@/components/app/ScreenPlaceholder.svelte"
|
||||
import ComponentErrorState from "@/components/error-states/ComponentErrorState.svelte"
|
||||
import {
|
||||
decodeJSBinding,
|
||||
findHBSBlocks,
|
||||
|
@ -35,7 +38,7 @@
|
|||
getActionContextKey,
|
||||
getActionDependentContextKeys,
|
||||
} from "../utils/buttonActions.js"
|
||||
import { gridLayout } from "utils/grid"
|
||||
import { gridLayout } from "@/utils/grid"
|
||||
|
||||
export let instance = {}
|
||||
export let parent = null
|
||||
|
@ -120,7 +123,7 @@
|
|||
$: children = instance._children || []
|
||||
$: id = instance._id
|
||||
$: name = isRoot ? "Screen" : instance._instanceName
|
||||
$: icon = definition?.icon
|
||||
$: icon = instance._icon || definition?.icon
|
||||
|
||||
// Determine if the component is selected or is part of the critical path
|
||||
// leading to the selected component
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { themeStore } from "stores"
|
||||
import { themeStore } from "@/stores"
|
||||
import { setContext } from "svelte"
|
||||
import { Context } from "@budibase/bbui"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { setContext, getContext, onMount } from "svelte"
|
||||
import Router, { querystring } from "svelte-spa-router"
|
||||
import { routeStore, stateStore } from "stores"
|
||||
import { routeStore, stateStore } from "@/stores"
|
||||
import Screen from "./Screen.svelte"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { screenStore, routeStore, builderStore } from "stores"
|
||||
import { screenStore, routeStore, builderStore } from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import Component from "./Component.svelte"
|
||||
import Provider from "./context/Provider.svelte"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// because it functions similarly to one
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { get, derived, readable } from "svelte/store"
|
||||
import { featuresStore } from "stores"
|
||||
import { featuresStore } from "@/stores"
|
||||
import { Grid } from "@budibase/frontend-core"
|
||||
// import { processStringSync } from "@budibase/string-templates"
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks"
|
||||
import { enrichSearchColumns, enrichFilter } from "@/utils/blocks"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export let title
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
// Datasource
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { FieldType } from "@budibase/types"
|
||||
|
||||
export let field
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import FormBlockWrapper from "./form/FormBlockWrapper.svelte"
|
||||
import { get, writable } from "svelte/store"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import Placeholder from "@/components/app/Placeholder.svelte"
|
||||
import { getContext } from "svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { get } from "svelte/store"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { generate } from "shortid"
|
||||
import { get } from "svelte/store"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import Placeholder from "@/components/app/Placeholder.svelte"
|
||||
import { getContext } from "svelte"
|
||||
import FormBlockComponent from "../FormBlockComponent.svelte"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { GridRowHeight, GridColumns } from "constants"
|
||||
import { GridRowHeight, GridColumns } from "@/constants"
|
||||
import { memo } from "@budibase/frontend-core"
|
||||
|
||||
export let onClick
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
import { getContext } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
import { generate } from "shortid"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks"
|
||||
import { enrichSearchColumns, enrichFilter } from "@/utils/blocks"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
|
||||
export let title
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { Table } from "@budibase/bbui"
|
||||
import SlotRenderer from "./SlotRenderer.svelte"
|
||||
import { canBeSortColumn } from "@budibase/frontend-core"
|
||||
import Provider from "components/context/Provider.svelte"
|
||||
import Provider from "@/components/context/Provider.svelte"
|
||||
|
||||
export let dataProvider
|
||||
export let columns
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Field from "./Field.svelte"
|
||||
import { CoreDropzone, ProgressCircle, Helpers } from "@budibase/bbui"
|
||||
import { getContext, onMount, onDestroy } from "svelte"
|
||||
import { builderStore } from "stores/builder.js"
|
||||
import { builderStore } from "@/stores/builder.js"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
|
||||
export let datasourceId
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { themeStore } from "stores"
|
||||
import { themeStore } from "@/stores"
|
||||
|
||||
let width = window.innerWidth
|
||||
let height = window.innerHeight
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext, setContext, onDestroy } from "svelte"
|
||||
import { dataSourceStore, createContextStore } from "stores"
|
||||
import { ActionTypes } from "constants"
|
||||
import { dataSourceStore, createContextStore } from "@/stores"
|
||||
import { ActionTypes } from "@/constants"
|
||||
import { generate } from "shortid"
|
||||
|
||||
const { ContextScopes } = getContext("sdk")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { routeStore } from "stores"
|
||||
import { routeStore } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="query" data={$routeStore.queryParams}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { rowSelectionStore } from "stores"
|
||||
import { rowSelectionStore } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="rowSelection" data={$rowSelectionStore}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { snippets } from "stores"
|
||||
import { snippets } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="snippets" data={$snippets}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { stateStore } from "stores"
|
||||
import { stateStore } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="state" data={$stateStore}>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { routeStore } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="url" data={$routeStore.testUrlParams}>
|
||||
<slot />
|
||||
</Provider>
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { authStore, currentRole } from "stores"
|
||||
import { ActionTypes } from "constants"
|
||||
import { authStore, currentRole } from "@/stores"
|
||||
import { ActionTypes } from "@/constants"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
|
||||
// Register this as a refreshable datasource so that user changes cause
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { Layout, Heading, Tabs, Tab, Icon } from "@budibase/bbui"
|
||||
import DevToolsStatsTab from "./DevToolsStatsTab.svelte"
|
||||
import DevToolsComponentTab from "./DevToolsComponentTab.svelte"
|
||||
import { devToolsStore } from "stores"
|
||||
import { devToolsStore } from "@/stores"
|
||||
|
||||
const context = getContext("context")
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Layout, Select, Body } from "@budibase/bbui"
|
||||
import { componentStore } from "stores/index.js"
|
||||
import { componentStore } from "@/stores"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
|
||||
const ReadableBindingMap = {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Layout, Toggle } from "@budibase/bbui"
|
||||
import { getSettingsDefinition } from "@budibase/frontend-core"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
import { componentStore } from "stores/index.js"
|
||||
import { componentStore } from "@/stores"
|
||||
|
||||
let showEnrichedSettings = true
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Body, Layout, Heading, Button, Tabs, Tab } from "@budibase/bbui"
|
||||
import { builderStore, devToolsStore, componentStore } from "stores"
|
||||
import { builderStore, devToolsStore, componentStore } from "@/stores"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
import DevToolsComponentSettingsTab from "./DevToolsComponentSettingsTab.svelte"
|
||||
import DevToolsComponentContextTab from "./DevToolsComponentContextTab.svelte"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import { Heading, Select, ActionButton } from "@budibase/bbui"
|
||||
import { devToolsStore, appStore } from "../../stores"
|
||||
import { devToolsStore, appStore } from "@/stores"
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
|
||||
const context = getContext("context")
|
||||
const SELF_ROLE = "self"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { notificationStore } from "stores"
|
||||
import { notificationStore } from "@/stores"
|
||||
|
||||
export let label
|
||||
export let value
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Layout } from "@budibase/bbui"
|
||||
import { authStore, appStore, screenStore, componentStore } from "stores"
|
||||
import { authStore, appStore, screenStore, componentStore } from "@/stores"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { confirmationStore } from "stores"
|
||||
import { confirmationStore } from "@/stores"
|
||||
import { Modal, ModalContent } from "@budibase/bbui"
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { notificationStore } from "stores"
|
||||
import { notificationStore } from "@/stores"
|
||||
import { Notification } from "@budibase/bbui"
|
||||
import { fly } from "svelte/transition"
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
notificationStore,
|
||||
routeStore,
|
||||
stateStore,
|
||||
} from "stores"
|
||||
} from "@/stores"
|
||||
import { Modal, ModalContent, ActionButton } from "@budibase/bbui"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import {
|
||||
builderStore,
|
||||
screenStore,
|
||||
dndStore,
|
||||
dndParent,
|
||||
dndIsDragging,
|
||||
} from "stores"
|
||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||
import { builderStore, screenStore, dndStore, isGridScreen } from "@/stores"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { findComponentById } from "utils/components.js"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
import { isGridEvent } from "utils/grid"
|
||||
import { findComponentById } from "@/utils/components.js"
|
||||
import { isGridEvent } from "@/utils/grid"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
import { Component } from "@budibase/types"
|
||||
|
||||
type ChildCoords = {
|
||||
placeholder: boolean
|
||||
centerX: number
|
||||
centerY: number
|
||||
left: number
|
||||
right: number
|
||||
top: number
|
||||
bottom: number
|
||||
}
|
||||
|
||||
const ThrottleRate = 130
|
||||
|
||||
|
@ -22,17 +25,19 @@
|
|||
$: source = $dndStore.source
|
||||
$: target = $dndStore.target
|
||||
$: drop = $dndStore.drop
|
||||
$: gridScreen = $isGridScreen
|
||||
|
||||
// Local flag for whether we are awaiting an async drop event
|
||||
let dropping = false
|
||||
|
||||
// Util to get the inner DOM node by a component ID
|
||||
const getDOMNode = id => {
|
||||
return document.getElementsByClassName(`${id}-dom`)[0]
|
||||
// Util to get the inner DOM element by a component ID
|
||||
const getDOMElement = (id: string): HTMLElement | undefined => {
|
||||
const el = document.getElementsByClassName(`${id}-dom`)[0]
|
||||
return el instanceof HTMLElement ? el : undefined
|
||||
}
|
||||
|
||||
// Util to calculate the variance of a set of data
|
||||
const variance = arr => {
|
||||
const variance = (arr: number[]) => {
|
||||
const mean = arr.reduce((a, b) => a + b, 0) / arr.length
|
||||
let squareSum = 0
|
||||
arr.forEach(value => {
|
||||
|
@ -61,36 +66,43 @@
|
|||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
const onDragStart = (e: DragEvent) => {
|
||||
if (isGridEvent(e)) {
|
||||
return
|
||||
}
|
||||
if (!(e.target instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
const component = e.target.closest(".component")
|
||||
if (!component?.classList.contains("draggable")) {
|
||||
if (
|
||||
!(component instanceof HTMLElement) ||
|
||||
!component.classList.contains("draggable")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Hide drag ghost image
|
||||
e.dataTransfer.setDragImage(new Image(), 0, 0)
|
||||
e.dataTransfer?.setDragImage(new Image(), 0, 0)
|
||||
|
||||
// Add event handler to clear all drag state when dragging ends
|
||||
component.addEventListener("dragend", stopDragging)
|
||||
|
||||
// Update state
|
||||
const id = component.dataset.id
|
||||
const parentId = component.dataset.parent
|
||||
const parent = findComponentById(
|
||||
const id = component.dataset.id!
|
||||
const parentId = component.dataset.parent!
|
||||
const parent: Component = findComponentById(
|
||||
get(screenStore).activeScreen?.props,
|
||||
parentId
|
||||
)
|
||||
const index = parent._children.findIndex(
|
||||
x => x._id === component.dataset.id
|
||||
)
|
||||
const index = parent._children!.findIndex(child => child._id === id)
|
||||
dndStore.actions.startDraggingExistingComponent({
|
||||
id,
|
||||
bounds: component.children[0].getBoundingClientRect(),
|
||||
parent: parentId,
|
||||
index,
|
||||
name: component.dataset.name,
|
||||
icon: component.dataset.icon,
|
||||
type: parent._children![index]!._component,
|
||||
})
|
||||
builderStore.actions.selectComponent(id)
|
||||
|
||||
|
@ -109,18 +121,18 @@
|
|||
|
||||
// Core logic for handling drop events and determining where to render the
|
||||
// drop target placeholder
|
||||
const processEvent = Utils.throttle((mouseX, mouseY) => {
|
||||
const processEvent = Utils.throttle((mouseX: number, mouseY: number) => {
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
let { id, parent, node, acceptsChildren, empty } = target
|
||||
let { id, parent, element, acceptsChildren, empty } = target
|
||||
|
||||
// If we're over something that does not accept children then we go up a
|
||||
// level and consider the mouse position relative to the parent
|
||||
if (!acceptsChildren) {
|
||||
id = parent
|
||||
empty = false
|
||||
node = getDOMNode(parent)
|
||||
element = getDOMElement(parent)
|
||||
}
|
||||
|
||||
// We're now hovering over something which does accept children.
|
||||
|
@ -133,51 +145,54 @@
|
|||
return
|
||||
}
|
||||
|
||||
// As the first DOM node in a component may not necessarily contain the
|
||||
// As the first DOM element in a component may not necessarily contain the
|
||||
// child components, we can find to try the parent of the first child
|
||||
// component and use that as the real parent DOM node
|
||||
const childNode = node.getElementsByClassName("component")[0]
|
||||
if (childNode?.parentNode) {
|
||||
node = childNode.parentNode
|
||||
const childElement = element?.getElementsByClassName("component")[0]
|
||||
if (childElement?.parentNode instanceof HTMLElement) {
|
||||
element = childElement.parentNode
|
||||
}
|
||||
|
||||
// Append an ephemeral div to allow us to determine layout if only one
|
||||
// child exists
|
||||
let ephemeralDiv
|
||||
if (node.children.length === 1) {
|
||||
if (element?.children.length === 1) {
|
||||
ephemeralDiv = document.createElement("div")
|
||||
ephemeralDiv.dataset.id = DNDPlaceholderID
|
||||
node.appendChild(ephemeralDiv)
|
||||
element.appendChild(ephemeralDiv)
|
||||
}
|
||||
|
||||
// We're now hovering over something which accepts children and is not
|
||||
// empty, so we need to work out where to inside the placeholder
|
||||
// Calculate the coordinates of various locations on each child.
|
||||
const childCoords = [...(node.children || [])].map(node => {
|
||||
const child = node.children?.[0] || node
|
||||
const bounds = child.getBoundingClientRect()
|
||||
return {
|
||||
placeholder: node.dataset.id === DNDPlaceholderID,
|
||||
centerX: bounds.left + bounds.width / 2,
|
||||
centerY: bounds.top + bounds.height / 2,
|
||||
left: bounds.left,
|
||||
right: bounds.right,
|
||||
top: bounds.top,
|
||||
bottom: bounds.bottom,
|
||||
}
|
||||
})
|
||||
const childCoords: ChildCoords[] = [...(element?.children || [])]
|
||||
.filter(el => el instanceof HTMLElement)
|
||||
.map(el => {
|
||||
const child = el.children?.[0] || el
|
||||
const bounds = child.getBoundingClientRect()
|
||||
return {
|
||||
placeholder: el.dataset.id === DNDPlaceholderID,
|
||||
centerX: bounds.left + bounds.width / 2,
|
||||
centerY: bounds.top + bounds.height / 2,
|
||||
left: bounds.left,
|
||||
right: bounds.right,
|
||||
top: bounds.top,
|
||||
bottom: bounds.bottom,
|
||||
}
|
||||
})
|
||||
|
||||
// Now that we've calculated the position of the children, we no longer need
|
||||
// the ephemeral div
|
||||
if (ephemeralDiv) {
|
||||
node.removeChild(ephemeralDiv)
|
||||
element?.removeChild(ephemeralDiv)
|
||||
}
|
||||
|
||||
// Calculate the variance between each set of positions on the children
|
||||
const variances = Object.keys(childCoords[0])
|
||||
.filter(x => x !== "placeholder")
|
||||
const variances = Object.keys(childCoords[0] || {})
|
||||
.filter(key => key !== "placeholder")
|
||||
.map(key => {
|
||||
const coords = childCoords.map(x => x[key])
|
||||
const numericalKey = key as keyof Omit<ChildCoords, "placeholder">
|
||||
const coords = childCoords.map(x => x[numericalKey])
|
||||
return {
|
||||
variance: variance(coords),
|
||||
side: key,
|
||||
|
@ -189,13 +204,13 @@
|
|||
variances.sort((a, b) => {
|
||||
return a.variance < b.variance ? -1 : 1
|
||||
})
|
||||
const column = ["centerX", "left", "right"].includes(variances[0].side)
|
||||
const column = ["centerX", "left", "right"].includes(variances[0]?.side)
|
||||
|
||||
// Calculate breakpoints between child components so we can determine the
|
||||
// index to drop the component in.
|
||||
// We want to ignore the placeholder from this calculation as it should not
|
||||
// be considered a real child of the parent.
|
||||
let breakpoints = childCoords
|
||||
const breakpoints = childCoords
|
||||
.filter(x => !x.placeholder)
|
||||
.map(x => {
|
||||
return column ? x.centerY : x.centerX
|
||||
|
@ -213,38 +228,39 @@
|
|||
})
|
||||
}, ThrottleRate)
|
||||
|
||||
const handleEvent = e => {
|
||||
const handleEvent = (e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
processEvent(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
// Callback when on top of a component.
|
||||
const onDragOver = e => {
|
||||
if (!source || !target) {
|
||||
// Callback when on top of a component
|
||||
const onDragOver = (e: DragEvent) => {
|
||||
if (!source || !target || gridScreen) {
|
||||
return
|
||||
}
|
||||
handleEvent(e)
|
||||
}
|
||||
|
||||
// Callback when entering a potential drop target
|
||||
const onDragEnter = e => {
|
||||
if (!source) {
|
||||
const onDragEnter = async (e: DragEvent) => {
|
||||
if (!source || gridScreen || !(e.target instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find the next valid component to consider dropping over, ignoring nested
|
||||
// block components
|
||||
const component = e.target?.closest?.(
|
||||
`.component:not(.block):not(.${source.id})`
|
||||
)
|
||||
if (component && component.classList.contains("droppable")) {
|
||||
let comp = e.target.closest?.(`.component:not(.block):not(.${source.id})`)
|
||||
if (!(comp instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
if (comp?.classList.contains("droppable")) {
|
||||
dndStore.actions.updateTarget({
|
||||
id: component.dataset.id,
|
||||
parent: component.dataset.parent,
|
||||
node: getDOMNode(component.dataset.id),
|
||||
empty: component.classList.contains("empty"),
|
||||
acceptsChildren: component.classList.contains("parent"),
|
||||
id: comp.dataset.id!,
|
||||
parent: comp.dataset.parent!,
|
||||
element: getDOMElement(comp.dataset.id!),
|
||||
empty: comp.classList.contains("empty"),
|
||||
acceptsChildren: comp.classList.contains("parent"),
|
||||
})
|
||||
handleEvent(e)
|
||||
}
|
||||
|
@ -257,12 +273,13 @@
|
|||
}
|
||||
|
||||
// Check if we're adding a new component rather than moving one
|
||||
if (source.newComponentType) {
|
||||
if (source.isNew) {
|
||||
dropping = true
|
||||
builderStore.actions.dropNewComponent(
|
||||
source.newComponentType,
|
||||
source.type,
|
||||
drop.parent,
|
||||
drop.index
|
||||
drop.index,
|
||||
$dndStore.meta?.props
|
||||
)
|
||||
dropping = false
|
||||
stopDragging()
|
||||
|
@ -271,7 +288,7 @@
|
|||
|
||||
// Convert parent + index into target + mode
|
||||
let legacyDropTarget, legacyDropMode
|
||||
const parent = findComponentById(
|
||||
const parent: Component | null = findComponentById(
|
||||
get(screenStore).activeScreen?.props,
|
||||
drop.parent
|
||||
)
|
||||
|
@ -333,14 +350,3 @@
|
|||
document.removeEventListener("drop", onDrop, false)
|
||||
})
|
||||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={$dndParent}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex={920}
|
||||
prefix="Inside"
|
||||
/>
|
||||
|
||||
{#if $dndIsDragging}
|
||||
<DNDPlaceholderOverlay />
|
||||
{/if}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
|
||||
let left, top, height, width
|
||||
|
||||
const updatePosition = () => {
|
||||
let node = document.getElementsByClassName(DNDPlaceholderID)[0]
|
||||
const insideGrid = node?.dataset.insideGrid === "true"
|
||||
if (!insideGrid) {
|
||||
node = document.getElementsByClassName(`${DNDPlaceholderID}-dom`)[0]
|
||||
}
|
||||
if (!node) {
|
||||
height = 0
|
||||
width = 0
|
||||
} else {
|
||||
const bounds = node.getBoundingClientRect()
|
||||
left = bounds.left
|
||||
top = bounds.top
|
||||
height = bounds.height
|
||||
width = bounds.width
|
||||
}
|
||||
}
|
||||
const debouncedUpdate = Utils.domDebounce(updatePosition)
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(debouncedUpdate, 100)
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if left != null && top != null && width && height}
|
||||
<div
|
||||
class="overlay"
|
||||
style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
z-index: 800;
|
||||
background: hsl(160, 64%, 90%);
|
||||
border-radius: 4px;
|
||||
transition: all 130ms ease-out;
|
||||
border: 2px solid var(--spectrum-global-color-static-green-500);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
isGridScreen,
|
||||
dndParent,
|
||||
dndSource,
|
||||
dndIsDragging,
|
||||
dndStore,
|
||||
} from "@/stores"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
|
||||
// On grid screens, don't draw the indicator until we've dragged over the
|
||||
// screen. When this happens, the dndSource props will be set as we will have
|
||||
// attached grid metadata styles.
|
||||
$: waitingForGrid = $isGridScreen && !$dndStore.meta?.props
|
||||
</script>
|
||||
|
||||
{#if $dndIsDragging}
|
||||
{#if !$isGridScreen && $dndParent}
|
||||
<IndicatorSet
|
||||
componentId={$dndParent}
|
||||
color="var(--spectrum-global-color-static-green-400)"
|
||||
zIndex={920}
|
||||
prefix="Inside"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !waitingForGrid}
|
||||
<IndicatorSet
|
||||
componentId={DNDPlaceholderID}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex={930}
|
||||
allowResizeAnchors={false}
|
||||
background="hsl(160, 64%, 90%)"
|
||||
animate={!$isGridScreen}
|
||||
text={$dndSource?.name}
|
||||
icon={$dndSource?.icon}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
|
@ -1,15 +1,50 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy, getContext } from "svelte"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
import {
|
||||
builderStore,
|
||||
componentStore,
|
||||
dndIsDragging,
|
||||
dndStore,
|
||||
dndSource,
|
||||
isGridScreen,
|
||||
} from "@/stores"
|
||||
import { Utils, memo } from "@budibase/frontend-core"
|
||||
import { GridRowHeight } from "constants"
|
||||
import { DNDPlaceholderID, GridRowHeight } from "@/constants"
|
||||
import {
|
||||
isGridEvent,
|
||||
GridParams,
|
||||
getGridVar,
|
||||
Devices,
|
||||
GridDragModes,
|
||||
} from "utils/grid"
|
||||
GridDragMode,
|
||||
} from "@/utils/grid"
|
||||
|
||||
type GridDragSide =
|
||||
| "top"
|
||||
| "right"
|
||||
| "bottom"
|
||||
| "left"
|
||||
| "top-left"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-right"
|
||||
|
||||
interface GridDragInfo {
|
||||
mode: GridDragMode
|
||||
side?: GridDragSide
|
||||
domTarget?: HTMLElement
|
||||
domComponent: HTMLElement
|
||||
domGrid: HTMLElement
|
||||
id: string
|
||||
gridId: string
|
||||
grid: {
|
||||
startX: number
|
||||
startY: number
|
||||
rowStart: number
|
||||
rowEnd: number
|
||||
colStart: number
|
||||
colEnd: number
|
||||
}
|
||||
}
|
||||
|
||||
const context = getContext("context")
|
||||
|
||||
|
@ -18,11 +53,12 @@
|
|||
ghost.src =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
|
||||
let dragInfo
|
||||
let styles = memo()
|
||||
let scrollElement: HTMLElement
|
||||
let dragInfo: GridDragInfo | undefined
|
||||
let styles = memo<Record<string, number> | undefined>()
|
||||
|
||||
// Grid CSS variables
|
||||
$: device = $context.device.mobile ? Devices.Mobile : Devices.Desktop
|
||||
$: device = $context.device?.mobile ? Devices.Mobile : Devices.Desktop
|
||||
$: vars = {
|
||||
colStart: getGridVar(device, GridParams.ColStart),
|
||||
colEnd: getGridVar(device, GridParams.ColEnd),
|
||||
|
@ -35,27 +71,54 @@
|
|||
|
||||
// Set ephemeral styles
|
||||
$: instance = componentStore.actions.getComponentInstance(id)
|
||||
$: $instance?.setEphemeralStyles($styles)
|
||||
$: applyStyles($instance, $styles)
|
||||
|
||||
// Reset when not dragging new components
|
||||
$: !$dndIsDragging && stopDragging()
|
||||
|
||||
const scrollOffset = () => scrollElement?.scrollTop || 0
|
||||
|
||||
const applyStyles = async (
|
||||
instance: any,
|
||||
styles: Record<string, number> | undefined
|
||||
) => {
|
||||
instance?.setEphemeralStyles(styles)
|
||||
|
||||
// If dragging a new component on to a grid screen, tick to allow the
|
||||
// real component to render in the new position before updating the DND
|
||||
// store, preventing the green DND overlay from being out of position
|
||||
if ($dndSource?.isNew && styles) {
|
||||
dndStore.actions.updateNewComponentProps({
|
||||
_styles: {
|
||||
normal: styles,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sugar for a combination of both min and max
|
||||
const minMax = (value, min, max) => Math.min(max, Math.max(min, value))
|
||||
const minMax = (value: number, min: number, max: number) =>
|
||||
Math.min(max, Math.max(min, value))
|
||||
|
||||
const processEvent = Utils.domDebounce((mouseX, mouseY) => {
|
||||
const processEvent = Utils.domDebounce((mouseX: number, mouseY: number) => {
|
||||
if (!dragInfo?.grid) {
|
||||
return
|
||||
}
|
||||
const { mode, side, grid, domGrid } = dragInfo
|
||||
const { mode, grid, domGrid } = dragInfo
|
||||
const { startX, startY, rowStart, rowEnd, colStart, colEnd } = grid
|
||||
if (!domGrid) {
|
||||
return
|
||||
}
|
||||
const cols = parseInt(domGrid.dataset.cols)
|
||||
const colSize = parseInt(domGrid.dataset.colSize)
|
||||
const cols = parseInt(domGrid.dataset.cols || "")
|
||||
const colSize = parseInt(domGrid.dataset.colSize || "")
|
||||
if (isNaN(cols) || isNaN(colSize)) {
|
||||
throw "DOM grid missing required dataset attributes"
|
||||
}
|
||||
const diffX = mouseX - startX
|
||||
let deltaX = Math.round(diffX / colSize)
|
||||
const diffY = mouseY - startY
|
||||
const diffY = mouseY - startY + scrollOffset()
|
||||
let deltaY = Math.round(diffY / GridRowHeight)
|
||||
if (mode === GridDragModes.Move) {
|
||||
if (mode === GridDragMode.Move) {
|
||||
deltaX = minMax(deltaX, 1 - colStart, cols + 1 - colEnd)
|
||||
deltaY = Math.max(deltaY, 1 - rowStart)
|
||||
const newStyles = {
|
||||
|
@ -65,8 +128,9 @@
|
|||
[vars.rowEnd]: rowEnd + deltaY,
|
||||
}
|
||||
styles.set(newStyles)
|
||||
} else if (mode === GridDragModes.Resize) {
|
||||
let newStyles = {}
|
||||
} else if (mode === GridDragMode.Resize) {
|
||||
const { side } = dragInfo
|
||||
let newStyles: Record<string, number> = {}
|
||||
if (side === "right") {
|
||||
newStyles[vars.colEnd] = Math.max(colEnd + deltaX, colStart + 1)
|
||||
} else if (side === "left") {
|
||||
|
@ -92,14 +156,50 @@
|
|||
}
|
||||
})
|
||||
|
||||
const handleEvent = e => {
|
||||
const handleEvent = (e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
processEvent(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
// Callback when dragging a new component over the preview iframe in a valid
|
||||
// position for the first time
|
||||
const startDraggingPlaceholder = () => {
|
||||
const domComponent = document.getElementsByClassName(DNDPlaceholderID)[0]
|
||||
const domGrid = domComponent?.closest(".grid")
|
||||
if (
|
||||
!(domComponent instanceof HTMLElement) ||
|
||||
!(domGrid instanceof HTMLElement)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const styles = getComputedStyle(domComponent)
|
||||
const bounds = domComponent.getBoundingClientRect()
|
||||
|
||||
// Show as active
|
||||
domComponent.classList.add("dragging")
|
||||
domGrid.classList.add("highlight")
|
||||
|
||||
// Update state
|
||||
dragInfo = {
|
||||
domComponent,
|
||||
domGrid,
|
||||
id: DNDPlaceholderID,
|
||||
gridId: domGrid.parentElement!.dataset.id!,
|
||||
mode: GridDragMode.Move,
|
||||
grid: {
|
||||
startX: bounds.left + bounds.width / 2,
|
||||
startY: bounds.top + bounds.height / 2 + scrollOffset(),
|
||||
rowStart: parseInt(styles.gridRowStart),
|
||||
rowEnd: parseInt(styles.gridRowEnd),
|
||||
colStart: parseInt(styles.gridColumnStart),
|
||||
colEnd: parseInt(styles.gridColumnEnd),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
const onDragStart = (e: DragEvent) => {
|
||||
if (!isGridEvent(e)) {
|
||||
return
|
||||
}
|
||||
|
@ -108,27 +208,30 @@
|
|||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
|
||||
// Extract state
|
||||
let mode, id, side
|
||||
let mode: GridDragMode, id: string, side
|
||||
if (e.target.dataset.indicator === "true") {
|
||||
mode = e.target.dataset.dragMode
|
||||
id = e.target.dataset.id
|
||||
side = e.target.dataset.side
|
||||
mode = e.target.dataset.dragMode as GridDragMode
|
||||
id = e.target.dataset.id!
|
||||
side = e.target.dataset.side as GridDragSide
|
||||
} else {
|
||||
// Handle move
|
||||
mode = GridDragModes.Move
|
||||
const component = e.target.closest(".component")
|
||||
id = component.dataset.id
|
||||
mode = GridDragMode.Move
|
||||
const component = e.target.closest(".component") as HTMLElement
|
||||
id = component.dataset.id!
|
||||
}
|
||||
|
||||
// If holding ctrl/cmd then leave behind a duplicate of this component
|
||||
if (mode === GridDragModes.Move && (e.ctrlKey || e.metaKey)) {
|
||||
if (mode === GridDragMode.Move && (e.ctrlKey || e.metaKey)) {
|
||||
builderStore.actions.duplicateComponent(id, "above", false)
|
||||
}
|
||||
|
||||
// Find grid parent and read from DOM
|
||||
const domComponent = document.getElementsByClassName(id)[0]
|
||||
const domGrid = domComponent?.closest(".grid")
|
||||
if (!domGrid) {
|
||||
if (
|
||||
!(domComponent instanceof HTMLElement) ||
|
||||
!(domGrid instanceof HTMLElement)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const styles = getComputedStyle(domComponent)
|
||||
|
@ -144,25 +247,29 @@
|
|||
domComponent,
|
||||
domGrid,
|
||||
id,
|
||||
gridId: domGrid.parentNode.dataset.id,
|
||||
gridId: domGrid.parentElement!.dataset.id!,
|
||||
mode,
|
||||
side,
|
||||
grid: {
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
rowStart: parseInt(styles["grid-row-start"]),
|
||||
rowEnd: parseInt(styles["grid-row-end"]),
|
||||
colStart: parseInt(styles["grid-column-start"]),
|
||||
colEnd: parseInt(styles["grid-column-end"]),
|
||||
startY: e.clientY + scrollOffset(),
|
||||
rowStart: parseInt(styles.gridRowStart),
|
||||
rowEnd: parseInt(styles.gridRowEnd),
|
||||
colStart: parseInt(styles.gridColumnStart),
|
||||
colEnd: parseInt(styles.gridColumnEnd),
|
||||
},
|
||||
}
|
||||
|
||||
// Add event handler to clear all drag state when dragging ends
|
||||
dragInfo.domTarget.addEventListener("dragend", stopDragging)
|
||||
dragInfo.domTarget!.addEventListener("dragend", stopDragging, false)
|
||||
}
|
||||
|
||||
const onDragOver = e => {
|
||||
const onDragOver = (e: DragEvent) => {
|
||||
if (!dragInfo) {
|
||||
// Check if we're dragging a new component
|
||||
if ($dndIsDragging && $dndSource?.isNew && $isGridScreen) {
|
||||
startDraggingPlaceholder()
|
||||
}
|
||||
return
|
||||
}
|
||||
handleEvent(e)
|
||||
|
@ -178,7 +285,7 @@
|
|||
// Reset DOM
|
||||
domComponent.classList.remove("dragging")
|
||||
domGrid.classList.remove("highlight")
|
||||
domTarget.removeEventListener("dragend", stopDragging)
|
||||
domTarget?.removeEventListener("dragend", stopDragging)
|
||||
|
||||
// Save changes
|
||||
if ($styles) {
|
||||
|
@ -186,17 +293,22 @@
|
|||
}
|
||||
|
||||
// Reset state
|
||||
dragInfo = null
|
||||
styles.set(null)
|
||||
dragInfo = undefined
|
||||
styles.set(undefined)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
scrollElement = document.getElementsByClassName(
|
||||
"screen-wrapper"
|
||||
)[0] as HTMLElement
|
||||
document.addEventListener("dragstart", onDragStart, false)
|
||||
document.addEventListener("dragover", onDragOver, false)
|
||||
document.addEventListener("scroll", processEvent)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
document.removeEventListener("dragstart", onDragStart, false)
|
||||
document.removeEventListener("dragover", onDragOver, false)
|
||||
document.removeEventListener("scroll", processEvent)
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
export let style
|
||||
export let value
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import { dndIsDragging, hoverStore, builderStore } from "stores"
|
||||
import { dndIsDragging, hoverStore, builderStore } from "@/stores"
|
||||
|
||||
$: componentId = $hoverStore.hoveredComponentId
|
||||
$: selectedComponentId = $builderStore.selectedComponentId
|
||||
$: selected = componentId === selectedComponentId
|
||||
|
||||
const onMouseOver = e => {
|
||||
const onMouseOver = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// Ignore if dragging
|
||||
if (e.buttons > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let newId
|
||||
if (e.target.classList.contains("anchor")) {
|
||||
if (target.classList.contains("anchor")) {
|
||||
// Handle resize anchors
|
||||
newId = e.target.dataset.id
|
||||
newId = target.dataset.id
|
||||
} else {
|
||||
// Handle normal components
|
||||
const element = e.target.closest(".interactive.component:not(.root)")
|
||||
newId = element?.dataset?.id
|
||||
const element = target.closest(".interactive.component:not(.root)")
|
||||
if (element instanceof HTMLElement) {
|
||||
newId = element.dataset?.id
|
||||
}
|
||||
}
|
||||
|
||||
if (newId !== componentId) {
|
||||
|
@ -43,9 +47,11 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={$dndIsDragging ? null : componentId}
|
||||
color="var(--spectrum-global-color-static-blue-200)"
|
||||
zIndex={selected ? 890 : 910}
|
||||
allowResizeAnchors
|
||||
/>
|
||||
{#if !$dndIsDragging && componentId}
|
||||
<IndicatorSet
|
||||
{componentId}
|
||||
color="var(--spectrum-global-color-static-blue-200)"
|
||||
zIndex={selected ? 890 : 910}
|
||||
allowResizeAnchors
|
||||
/>
|
||||
{/if}}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { GridDragModes } from "utils/grid"
|
||||
import { GridDragMode } from "@/utils/grid"
|
||||
|
||||
export let top
|
||||
export let left
|
||||
export let width
|
||||
export let height
|
||||
export let text
|
||||
export let icon
|
||||
export let color
|
||||
export let zIndex
|
||||
export let componentId
|
||||
export let top: number
|
||||
export let left: number
|
||||
export let width: number
|
||||
export let height: number
|
||||
export let text: string | undefined
|
||||
export let icon: string | undefined
|
||||
export let color: string
|
||||
export let zIndex: number
|
||||
export let componentId: string
|
||||
export let line = false
|
||||
export let alignRight = false
|
||||
export let showResizeAnchors = false
|
||||
export let background: string | undefined
|
||||
export let animate = false
|
||||
|
||||
const AnchorSides = [
|
||||
"right",
|
||||
|
@ -33,10 +35,12 @@
|
|||
class="indicator"
|
||||
class:flipped
|
||||
class:line
|
||||
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
|
||||
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex}; --bg: {background ||
|
||||
'none'};"
|
||||
class:withText={!!text}
|
||||
class:vCompact={height < 40}
|
||||
class:hCompact={width < 40}
|
||||
class:animate
|
||||
>
|
||||
{#if text || icon}
|
||||
<div
|
||||
|
@ -46,7 +50,7 @@
|
|||
class:right={alignRight}
|
||||
draggable="true"
|
||||
data-indicator="true"
|
||||
data-drag-mode={GridDragModes.Move}
|
||||
data-drag-mode={GridDragMode.Move}
|
||||
data-id={componentId}
|
||||
>
|
||||
{#if icon}
|
||||
|
@ -65,7 +69,7 @@
|
|||
class="anchor {side}"
|
||||
draggable="true"
|
||||
data-indicator="true"
|
||||
data-drag-mode={GridDragModes.Resize}
|
||||
data-drag-mode={GridDragMode.Resize}
|
||||
data-side={side}
|
||||
data-id={componentId}
|
||||
>
|
||||
|
@ -84,6 +88,7 @@
|
|||
border: 2px solid var(--color);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
background: var(--bg);
|
||||
}
|
||||
.indicator.withText {
|
||||
border-top-left-radius: 0;
|
||||
|
@ -94,6 +99,9 @@
|
|||
.indicator.line {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
.indicator.animate {
|
||||
transition: all 130ms ease-out;
|
||||
}
|
||||
|
||||
/* Label styles */
|
||||
.label {
|
||||
|
|
|
@ -1,42 +1,68 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import Indicator from "./Indicator.svelte"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
import { memo, Utils } from "@budibase/frontend-core"
|
||||
|
||||
export let componentId = null
|
||||
export let color = null
|
||||
export let zIndex = 900
|
||||
export let prefix = null
|
||||
export let allowResizeAnchors = false
|
||||
export let componentId: string
|
||||
export let color: string
|
||||
export let zIndex: number = 900
|
||||
export let prefix: string | undefined = undefined
|
||||
export let allowResizeAnchors: boolean = false
|
||||
export let background: string | undefined = undefined
|
||||
export let animate: boolean = false
|
||||
export let text: string | undefined = undefined
|
||||
export let icon: string | undefined = undefined
|
||||
|
||||
interface IndicatorState {
|
||||
visible: boolean
|
||||
insideModal: boolean
|
||||
insideSidePanel: boolean
|
||||
top: number
|
||||
left: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
interface IndicatorSetState {
|
||||
// Cached props
|
||||
componentId: string
|
||||
color: string
|
||||
zIndex: number
|
||||
prefix?: string
|
||||
allowResizeAnchors: boolean
|
||||
|
||||
// Computed state
|
||||
indicators: IndicatorState[]
|
||||
text?: string
|
||||
icon?: string
|
||||
insideGrid: boolean
|
||||
error: boolean
|
||||
}
|
||||
|
||||
// Offset = 6 (clip-root padding) - 1 (half the border thickness)
|
||||
const config = memo($$props)
|
||||
const errorColor = "var(--spectrum-global-color-static-red-600)"
|
||||
const mutationObserver = new MutationObserver(() => debouncedUpdate())
|
||||
const defaultState = () => ({
|
||||
// Cached props
|
||||
const defaultState = (): IndicatorSetState => ({
|
||||
componentId,
|
||||
color,
|
||||
zIndex,
|
||||
prefix,
|
||||
allowResizeAnchors,
|
||||
|
||||
// Computed state
|
||||
indicators: [],
|
||||
text: null,
|
||||
icon: null,
|
||||
text,
|
||||
icon,
|
||||
insideGrid: false,
|
||||
error: false,
|
||||
})
|
||||
|
||||
let interval
|
||||
let interval: ReturnType<typeof setInterval>
|
||||
let state = defaultState()
|
||||
let observingMutations = false
|
||||
let updating = false
|
||||
let intersectionObservers = []
|
||||
let intersectionObservers: IntersectionObserver[] = []
|
||||
let callbackCount = 0
|
||||
let nextState
|
||||
let nextState: ReturnType<typeof defaultState>
|
||||
|
||||
$: componentId, reset()
|
||||
$: visibleIndicators = state.indicators.filter(x => x.visible)
|
||||
|
@ -58,27 +84,34 @@
|
|||
updating = false
|
||||
}
|
||||
|
||||
const observeMutations = element => {
|
||||
mutationObserver.observe(element, {
|
||||
const getElements = (className: string): HTMLElement[] => {
|
||||
return [...document.getElementsByClassName(className)]
|
||||
.filter(el => el instanceof HTMLElement)
|
||||
.slice(0, 100)
|
||||
}
|
||||
|
||||
const observeMutations = (node: Node) => {
|
||||
mutationObserver.observe(node, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
})
|
||||
observingMutations = true
|
||||
}
|
||||
|
||||
const createIntersectionCallback = idx => entries => {
|
||||
if (callbackCount >= intersectionObservers.length) {
|
||||
return
|
||||
const createIntersectionCallback =
|
||||
(idx: number) => (entries: IntersectionObserverEntry[]) => {
|
||||
if (callbackCount >= intersectionObservers.length) {
|
||||
return
|
||||
}
|
||||
nextState.indicators[idx].visible =
|
||||
nextState.indicators[idx].insideModal ||
|
||||
nextState.indicators[idx].insideSidePanel ||
|
||||
entries[0].isIntersecting
|
||||
if (++callbackCount === intersectionObservers.length) {
|
||||
state = nextState
|
||||
updating = false
|
||||
}
|
||||
}
|
||||
nextState.indicators[idx].visible =
|
||||
nextState.indicators[idx].insideModal ||
|
||||
nextState.indicators[idx].insideSidePanel ||
|
||||
entries[0].isIntersecting
|
||||
if (++callbackCount === intersectionObservers.length) {
|
||||
state = nextState
|
||||
updating = false
|
||||
}
|
||||
}
|
||||
|
||||
const updatePosition = () => {
|
||||
if (updating) {
|
||||
|
@ -86,11 +119,7 @@
|
|||
}
|
||||
|
||||
// Sanity check
|
||||
if (!componentId) {
|
||||
state = defaultState()
|
||||
return
|
||||
}
|
||||
let elements = document.getElementsByClassName(componentId)
|
||||
let elements = getElements(componentId)
|
||||
if (!elements.length) {
|
||||
state = defaultState()
|
||||
return
|
||||
|
@ -108,17 +137,19 @@
|
|||
}
|
||||
|
||||
// Check if we're inside a grid
|
||||
if (allowResizeAnchors) {
|
||||
nextState.insideGrid = elements[0]?.dataset.insideGrid === "true"
|
||||
}
|
||||
nextState.insideGrid = elements[0]?.dataset.insideGrid === "true"
|
||||
|
||||
// Get text to display
|
||||
nextState.text = elements[0].dataset.name
|
||||
if (nextState.prefix) {
|
||||
nextState.text = `${nextState.prefix} ${nextState.text}`
|
||||
// Get text and icon to display
|
||||
if (!text) {
|
||||
nextState.text = elements[0].dataset.name
|
||||
if (nextState.prefix) {
|
||||
nextState.text = `${nextState.prefix} ${nextState.text}`
|
||||
}
|
||||
}
|
||||
if (elements[0].dataset.icon) {
|
||||
nextState.icon = elements[0].dataset.icon
|
||||
if (!icon) {
|
||||
if (elements[0].dataset.icon) {
|
||||
nextState.icon = elements[0].dataset.icon
|
||||
}
|
||||
}
|
||||
nextState.error = elements[0].classList.contains("error")
|
||||
|
||||
|
@ -129,11 +160,8 @@
|
|||
// Extract valid children
|
||||
// Sanity limit of active indicators
|
||||
if (!nextState.insideGrid) {
|
||||
elements = document.getElementsByClassName(`${componentId}-dom`)
|
||||
elements = getElements(`${componentId}-dom`)
|
||||
}
|
||||
elements = Array.from(elements)
|
||||
.filter(x => x != null)
|
||||
.slice(0, 100)
|
||||
const multi = elements.length > 1
|
||||
|
||||
// If there aren't any nodes then reset
|
||||
|
@ -143,15 +171,20 @@
|
|||
}
|
||||
|
||||
const device = document.getElementById("app-root")
|
||||
if (!device) {
|
||||
throw "app-root node not found"
|
||||
}
|
||||
const deviceBounds = device.getBoundingClientRect()
|
||||
nextState.indicators = elements.map((element, idx) => {
|
||||
const elBounds = element.getBoundingClientRect()
|
||||
let indicator = {
|
||||
let indicator: IndicatorState = {
|
||||
top: Math.round(elBounds.top + scrollY - deviceBounds.top + offset),
|
||||
left: Math.round(elBounds.left + scrollX - deviceBounds.left + offset),
|
||||
width: Math.round(elBounds.width + 2),
|
||||
height: Math.round(elBounds.height + 2),
|
||||
visible: true,
|
||||
insideModal: false,
|
||||
insideSidePanel: false,
|
||||
}
|
||||
|
||||
// If observing more than one node then we need to use an intersection
|
||||
|
@ -199,11 +232,13 @@
|
|||
left={indicator.left}
|
||||
width={indicator.width}
|
||||
height={indicator.height}
|
||||
text={idx === 0 ? state.text : null}
|
||||
icon={idx === 0 ? state.icon : null}
|
||||
text={idx === 0 ? state.text : undefined}
|
||||
icon={idx === 0 ? state.icon : undefined}
|
||||
showResizeAnchors={state.allowResizeAnchors && state.insideGrid}
|
||||
color={state.error ? errorColor : state.color}
|
||||
componentId={state.componentId}
|
||||
zIndex={state.zIndex}
|
||||
{background}
|
||||
{animate}
|
||||
/>
|
||||
{/each}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
onMount(() => {
|
||||
if (get(builderStore).inBuilder) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { builderStore } from "stores"
|
||||
<script lang="ts">
|
||||
import { dndIsDragging, builderStore } from "@/stores"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
|
||||
$: color = $builderStore.editMode
|
||||
|
@ -7,9 +7,11 @@
|
|||
: "var(--spectrum-global-color-static-blue-600)"
|
||||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={$builderStore.selectedComponentId}
|
||||
{color}
|
||||
zIndex={900}
|
||||
allowResizeAnchors
|
||||
/>
|
||||
{#if !$dndIsDragging && $builderStore.selectedComponentId}
|
||||
<IndicatorSet
|
||||
componentId={$builderStore.selectedComponentId}
|
||||
{color}
|
||||
zIndex={900}
|
||||
allowResizeAnchors
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
import GridStylesButton from "./GridStylesButton.svelte"
|
||||
import SettingsColorPicker from "./SettingsColorPicker.svelte"
|
||||
import SettingsPicker from "./SettingsPicker.svelte"
|
||||
import { builderStore, componentStore, dndIsDragging } from "stores"
|
||||
import { builderStore, componentStore, dndIsDragging } from "@/stores"
|
||||
import { Utils, shouldDisplaySetting } from "@budibase/frontend-core"
|
||||
import { getGridVar, GridParams, Devices } from "utils/grid"
|
||||
import { getGridVar, GridParams, Devices } from "@/utils/grid"
|
||||
|
||||
const context = getContext("context")
|
||||
const verticalOffset = 36
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let prop
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { ColorPicker } from "@budibase/bbui"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
export let prop
|
||||
export let component
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Select } from "@budibase/bbui"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
export let prop
|
||||
export let options
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
interface Window {
|
||||
"##BUDIBASE_APP_ID##": string
|
||||
"##BUDIBASE_IN_BUILDER##": string
|
||||
MIGRATING_APP: boolean
|
||||
}
|
|
@ -10,6 +10,7 @@ import {
|
|||
eventStore,
|
||||
hoverStore,
|
||||
stateStore,
|
||||
routeStore,
|
||||
} from "./stores"
|
||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||
import { get } from "svelte/store"
|
||||
|
@ -108,6 +109,9 @@ const loadBudibase = async () => {
|
|||
} else if (type === "builder-state") {
|
||||
const [[key, value]] = Object.entries(data)
|
||||
stateStore.actions.setValue(key, value)
|
||||
} else if (type === "builder-url-test-data") {
|
||||
const { route, testValue } = data
|
||||
routeStore.actions.setTestUrlParams(route, testValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,84 @@
|
|||
import ClientApp from "./components/ClientApp.svelte"
|
||||
import UpdatingApp from "./components/UpdatingApp.svelte"
|
||||
import {
|
||||
builderStore,
|
||||
appStore,
|
||||
blockStore,
|
||||
componentStore,
|
||||
environmentStore,
|
||||
dndStore,
|
||||
eventStore,
|
||||
hoverStore,
|
||||
stateStore,
|
||||
routeStore,
|
||||
} from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import { initWebsocket } from "@/websocket"
|
||||
import { APIClient } from "@budibase/frontend-core"
|
||||
import type { ActionTypes } from "./constants"
|
||||
import type { ActionTypes } from "@/constants"
|
||||
import { Readable } from "svelte/store"
|
||||
import {
|
||||
Screen,
|
||||
Layout,
|
||||
Theme,
|
||||
AppCustomTheme,
|
||||
PreviewDevice,
|
||||
AppNavigation,
|
||||
Plugin,
|
||||
Snippet,
|
||||
UIComponentError,
|
||||
CustomComponent,
|
||||
} from "@budibase/types"
|
||||
|
||||
// Provide svelte and svelte/internal as globals for custom components
|
||||
import * as svelte from "svelte"
|
||||
// @ts-ignore
|
||||
import * as internal from "svelte/internal"
|
||||
window.svelte_internal = internal
|
||||
window.svelte = svelte
|
||||
|
||||
// Initialise spectrum icons
|
||||
// eslint-disable-next-line local-rules/no-budibase-imports
|
||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||
loadSpectrumIcons()
|
||||
|
||||
// Extend global window scope
|
||||
declare global {
|
||||
interface Window {
|
||||
// Data from builder
|
||||
"##BUDIBASE_APP_ID##"?: string
|
||||
"##BUDIBASE_IN_BUILDER##"?: true
|
||||
"##BUDIBASE_PREVIEW_LAYOUT##"?: Layout
|
||||
"##BUDIBASE_PREVIEW_SCREEN##"?: Screen
|
||||
"##BUDIBASE_SELECTED_COMPONENT_ID##"?: string
|
||||
"##BUDIBASE_PREVIEW_ID##"?: number
|
||||
"##BUDIBASE_PREVIEW_THEME##"?: Theme
|
||||
"##BUDIBASE_PREVIEW_CUSTOM_THEME##"?: AppCustomTheme
|
||||
"##BUDIBASE_PREVIEW_DEVICE##"?: PreviewDevice
|
||||
"##BUDIBASE_APP_EMBEDDED##"?: string // This is a bool wrapped in a string
|
||||
"##BUDIBASE_PREVIEW_NAVIGATION##"?: AppNavigation
|
||||
"##BUDIBASE_HIDDEN_COMPONENT_IDS##"?: string[]
|
||||
"##BUDIBASE_USED_PLUGINS##"?: Plugin[]
|
||||
"##BUDIBASE_LOCATION##"?: {
|
||||
protocol: string
|
||||
hostname: string
|
||||
port: string
|
||||
}
|
||||
"##BUDIBASE_SNIPPETS##"?: Snippet[]
|
||||
"##BUDIBASE_COMPONENT_ERRORS##"?: Record<string, UIComponentError>[]
|
||||
"##BUDIBASE_CUSTOM_COMPONENTS##"?: CustomComponent[]
|
||||
|
||||
// Other flags
|
||||
MIGRATING_APP: boolean
|
||||
|
||||
// Client additions
|
||||
handleBuilderRuntimeEvent: (type: string, data: any) => void
|
||||
registerCustomComponent: typeof componentStore.actions.registerCustomComponent
|
||||
loadBudibase: typeof loadBudibase
|
||||
svelte: typeof svelte
|
||||
svelte_internal: typeof internal
|
||||
}
|
||||
}
|
||||
|
||||
export interface SDK {
|
||||
API: APIClient
|
||||
|
@ -28,4 +106,117 @@ export type Component = Readable<{
|
|||
errorState: boolean
|
||||
}>
|
||||
|
||||
export type Context = Readable<{}>
|
||||
export type Context = Readable<Record<string, any>>
|
||||
|
||||
let app: ClientApp
|
||||
|
||||
const loadBudibase = async () => {
|
||||
// Update builder store with any builder flags
|
||||
builderStore.set({
|
||||
...get(builderStore),
|
||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
|
||||
previewId: window["##BUDIBASE_PREVIEW_ID##"],
|
||||
theme: window["##BUDIBASE_PREVIEW_THEME##"],
|
||||
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
|
||||
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
|
||||
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
||||
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
||||
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
||||
location: window["##BUDIBASE_LOCATION##"],
|
||||
snippets: window["##BUDIBASE_SNIPPETS##"],
|
||||
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
|
||||
})
|
||||
|
||||
// Set app ID - this window flag is set by both the preview and the real
|
||||
// server rendered app HTML
|
||||
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
|
||||
|
||||
// Set the flag used to determine if the app is being loaded via an iframe
|
||||
appStore.actions.setAppEmbedded(
|
||||
window["##BUDIBASE_APP_EMBEDDED##"] === "true"
|
||||
)
|
||||
|
||||
if (window.MIGRATING_APP) {
|
||||
new UpdatingApp({
|
||||
target: window.document.body,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch environment info
|
||||
if (!get(environmentStore)?.loaded) {
|
||||
await environmentStore.actions.fetchEnvironment()
|
||||
}
|
||||
|
||||
// Register handler for runtime events from the builder
|
||||
window.handleBuilderRuntimeEvent = (type, data) => {
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
return
|
||||
}
|
||||
if (type === "event-completed") {
|
||||
eventStore.actions.resolveEvent(data)
|
||||
} else if (type === "eject-block") {
|
||||
const block = blockStore.actions.getBlock(data)
|
||||
block?.eject()
|
||||
} else if (type === "dragging-new-component") {
|
||||
const { dragging, component } = data
|
||||
if (dragging) {
|
||||
dndStore.actions.startDraggingNewComponent(component)
|
||||
} else {
|
||||
dndStore.actions.reset()
|
||||
}
|
||||
} else if (type === "request-context") {
|
||||
const { selectedComponentInstance, screenslotInstance } =
|
||||
get(componentStore)
|
||||
const instance = selectedComponentInstance || screenslotInstance
|
||||
const context = instance?.getDataContext()
|
||||
let stringifiedContext = null
|
||||
try {
|
||||
stringifiedContext = JSON.stringify(context)
|
||||
} catch (error) {
|
||||
// Ignore - invalid context
|
||||
}
|
||||
eventStore.actions.dispatchEvent("provide-context", {
|
||||
context: stringifiedContext,
|
||||
})
|
||||
} else if (type === "hover-component") {
|
||||
hoverStore.actions.hoverComponent(data, false)
|
||||
} else if (type === "builder-meta") {
|
||||
builderStore.actions.setMetadata(data)
|
||||
} else if (type === "builder-state") {
|
||||
const [[key, value]] = Object.entries(data)
|
||||
stateStore.actions.setValue(key, value)
|
||||
} else if (type === "builder-url-test-data") {
|
||||
const { route, testValue } = data
|
||||
routeStore.actions.setTestUrlParams(route, testValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Register any custom components
|
||||
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
|
||||
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
|
||||
componentStore.actions.registerCustomComponent(component)
|
||||
})
|
||||
}
|
||||
|
||||
// Make a callback available for custom component bundles to register
|
||||
// themselves at runtime
|
||||
window.registerCustomComponent =
|
||||
componentStore.actions.registerCustomComponent
|
||||
|
||||
// Initialise websocket
|
||||
initWebsocket()
|
||||
|
||||
// Create app if one hasn't been created yet
|
||||
if (!app) {
|
||||
app = new ClientApp({
|
||||
target: window.document.body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Attach to window so the HTML template can call this when it loads
|
||||
window.loadBudibase = loadBudibase
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import {
|
||||
authStore,
|
||||
notificationStore,
|
||||
|
@ -18,13 +18,13 @@ import {
|
|||
appStore,
|
||||
stateStore,
|
||||
createContextStore,
|
||||
} from "stores"
|
||||
import { styleable } from "utils/styleable"
|
||||
import { linkable } from "utils/linkable"
|
||||
import { getAction } from "utils/getAction"
|
||||
import Provider from "components/context/Provider.svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
} from "@/stores"
|
||||
import { styleable } from "@/utils/styleable"
|
||||
import { linkable } from "@/utils/linkable"
|
||||
import { getAction } from "@/utils/getAction"
|
||||
import Provider from "@/components/context/Provider.svelte"
|
||||
import Block from "@/components/Block.svelte"
|
||||
import BlockComponent from "@/components/BlockComponent.svelte"
|
||||
import { ActionTypes } from "./constants"
|
||||
import {
|
||||
fetchDatasourceSchema,
|
||||
|
@ -41,7 +41,7 @@ import {
|
|||
memo,
|
||||
derivedMemo,
|
||||
} from "@budibase/frontend-core"
|
||||
import { createValidatorFromConstraints } from "components/app/forms/validation"
|
||||
import { createValidatorFromConstraints } from "@/components/app/forms/validation"
|
||||
|
||||
export default {
|
||||
API,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { get, writable, derived } from "svelte/store"
|
||||
|
||||
const initialState = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
const createAuthStore = () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { devToolsStore } from "./devTools.js"
|
||||
import { eventStore } from "./events.js"
|
||||
|
||||
|
@ -77,11 +77,12 @@ const createBuilderStore = () => {
|
|||
mode,
|
||||
})
|
||||
},
|
||||
dropNewComponent: (component, parent, index) => {
|
||||
dropNewComponent: (component, parent, index, props) => {
|
||||
eventStore.actions.dispatchEvent("drop-new-component", {
|
||||
component,
|
||||
parent,
|
||||
index,
|
||||
props,
|
||||
})
|
||||
},
|
||||
setEditMode: enabled => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { FieldTypes } from "../constants"
|
||||
import { routeStore } from "./routes"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { derived } from "svelte/store"
|
||||
import { findComponentPathById } from "utils/components.js"
|
||||
import { dndParent } from "../dnd.js"
|
||||
import { findComponentPathById } from "@/utils/components.js"
|
||||
import { dndParent } from "../dnd.ts"
|
||||
import { screenStore } from "../screens.js"
|
||||
|
||||
export const dndComponentPath = derived(
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { derivedMemo } from "@budibase/frontend-core"
|
||||
|
||||
const createDndStore = () => {
|
||||
const initialState = {
|
||||
// Info about the dragged component
|
||||
source: null,
|
||||
|
||||
// Info about the target component being hovered over
|
||||
target: null,
|
||||
|
||||
// Info about where the component would be dropped
|
||||
drop: null,
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
const startDraggingExistingComponent = ({ id, parent, bounds, index }) => {
|
||||
store.set({
|
||||
...initialState,
|
||||
source: { id, parent, bounds, index },
|
||||
})
|
||||
}
|
||||
|
||||
const startDraggingNewComponent = ({ component, definition }) => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get size of new component so we can show a properly sized placeholder
|
||||
const width = definition?.size?.width || 128
|
||||
const height = definition?.size?.height || 64
|
||||
|
||||
store.set({
|
||||
...initialState,
|
||||
source: {
|
||||
id: null,
|
||||
parent: null,
|
||||
bounds: { height, width },
|
||||
index: null,
|
||||
newComponentType: component,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const updateTarget = ({ id, parent, node, empty, acceptsChildren }) => {
|
||||
store.update(state => {
|
||||
state.target = { id, parent, node, empty, acceptsChildren }
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const updateDrop = ({ parent, index }) => {
|
||||
store.update(state => {
|
||||
state.drop = { parent, index }
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
store.set(initialState)
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
startDraggingExistingComponent,
|
||||
startDraggingNewComponent,
|
||||
updateTarget,
|
||||
updateDrop,
|
||||
reset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const dndStore = createDndStore()
|
||||
|
||||
// The DND store is updated extremely frequently, so we can greatly improve
|
||||
// performance by deriving any state that needs to be externally observed.
|
||||
// By doing this and using primitives, we can avoid invalidating other stores
|
||||
// or components which depend on DND state unless values actually change.
|
||||
export const dndParent = derivedMemo(dndStore, x => x.drop?.parent)
|
||||
export const dndIndex = derivedMemo(dndStore, x => x.drop?.index)
|
||||
export const dndBounds = derivedMemo(dndStore, x => x.source?.bounds)
|
||||
export const dndIsDragging = derivedMemo(dndStore, x => !!x.source)
|
||||
export const dndIsNewComponent = derivedMemo(
|
||||
dndStore,
|
||||
x => x.source?.newComponentType != null
|
||||
)
|
|
@ -0,0 +1,147 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { derivedMemo } from "@budibase/frontend-core"
|
||||
import { screenStore, isGridScreen, componentStore } from "@/stores"
|
||||
import { ScreenslotID } from "@/constants"
|
||||
import { ComponentDefinition } from "@budibase/types"
|
||||
|
||||
interface DNDSource {
|
||||
id?: string
|
||||
parent?: string
|
||||
index?: number
|
||||
bounds: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
name?: string
|
||||
icon?: string
|
||||
type: string
|
||||
isNew: boolean
|
||||
}
|
||||
|
||||
interface DNDTarget {
|
||||
id: string
|
||||
parent: string
|
||||
empty: boolean
|
||||
acceptsChildren: boolean
|
||||
element?: HTMLElement
|
||||
}
|
||||
|
||||
interface DNDDrop {
|
||||
parent: string
|
||||
index: number
|
||||
}
|
||||
|
||||
interface DNDMeta {
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
interface DNDState {
|
||||
source?: DNDSource
|
||||
target?: DNDTarget
|
||||
drop?: DNDDrop
|
||||
meta?: DNDMeta
|
||||
}
|
||||
|
||||
const createDndStore = () => {
|
||||
const store = writable<DNDState>({})
|
||||
|
||||
const startDraggingExistingComponent = (source: Omit<DNDSource, "isNew">) => {
|
||||
store.set({
|
||||
source: {
|
||||
...source,
|
||||
isNew: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const startDraggingNewComponent = (type: string) => {
|
||||
// On grid screens, we already know exactly where to insert the component
|
||||
let target: DNDTarget | undefined = undefined
|
||||
let drop: DNDDrop | undefined = undefined
|
||||
if (get(isGridScreen)) {
|
||||
const screen = get(screenStore)?.activeScreen
|
||||
const id = screen.props._id
|
||||
target = {
|
||||
id,
|
||||
parent: ScreenslotID,
|
||||
empty: false,
|
||||
acceptsChildren: true,
|
||||
}
|
||||
drop = {
|
||||
parent: id,
|
||||
index: screen?.props?._children?.length,
|
||||
}
|
||||
}
|
||||
|
||||
// Get size of new component so we can show a properly sized placeholder
|
||||
const definition: ComponentDefinition =
|
||||
componentStore.actions.getComponentDefinition(type)
|
||||
const width = definition?.size?.width || 128
|
||||
const height = definition?.size?.height || 64
|
||||
|
||||
store.set({
|
||||
source: {
|
||||
bounds: { height, width },
|
||||
type,
|
||||
isNew: true,
|
||||
name: `New ${definition?.name || "component"}`,
|
||||
icon: definition?.icon || "Selection",
|
||||
},
|
||||
target,
|
||||
drop,
|
||||
})
|
||||
}
|
||||
|
||||
const updateTarget = (target: DNDTarget) => {
|
||||
store.update(state => {
|
||||
state.target = target
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const updateDrop = (drop: DNDDrop) => {
|
||||
store.update(state => {
|
||||
state.drop = drop
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
store.set({})
|
||||
}
|
||||
|
||||
const updateNewComponentProps = (props: Record<string, any>) => {
|
||||
store.update(state => {
|
||||
return {
|
||||
...state,
|
||||
meta: {
|
||||
...state.meta,
|
||||
props,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
startDraggingExistingComponent,
|
||||
startDraggingNewComponent,
|
||||
updateTarget,
|
||||
updateDrop,
|
||||
reset,
|
||||
updateNewComponentProps,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const dndStore = createDndStore()
|
||||
|
||||
// The DND store is updated extremely frequently, so we can greatly improve
|
||||
// performance by deriving any state that needs to be externally observed.
|
||||
// By doing this and using primitives, we can avoid invalidating other stores
|
||||
// or components which depend on DND state unless values actually change.
|
||||
export const dndParent = derivedMemo(dndStore, x => x.drop?.parent)
|
||||
export const dndIndex = derivedMemo(dndStore, x => x.drop?.index)
|
||||
export const dndSource = derivedMemo(dndStore, x => x.source)
|
||||
export const dndIsDragging = derivedMemo(dndStore, x => !!x.source)
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
const initialState = {
|
||||
|
|
|
@ -2,7 +2,7 @@ export { authStore } from "./auth"
|
|||
export { appStore } from "./app"
|
||||
export { notificationStore } from "./notification"
|
||||
export { routeStore } from "./routes"
|
||||
export { screenStore } from "./screens"
|
||||
export { screenStore, isGridScreen } from "./screens"
|
||||
export { builderStore } from "./builder"
|
||||
export { dataSourceStore } from "./dataSource"
|
||||
export { confirmationStore } from "./confirmation"
|
||||
|
@ -18,14 +18,7 @@ export { environmentStore } from "./environment"
|
|||
export { eventStore } from "./events"
|
||||
export { orgStore } from "./org"
|
||||
export { roleStore } from "./roles"
|
||||
export {
|
||||
dndStore,
|
||||
dndIndex,
|
||||
dndParent,
|
||||
dndBounds,
|
||||
dndIsNewComponent,
|
||||
dndIsDragging,
|
||||
} from "./dnd"
|
||||
export { dndStore, dndIndex, dndParent, dndIsDragging, dndSource } from "./dnd"
|
||||
export { sidePanelStore } from "./sidePanel"
|
||||
export { modalStore } from "./modal"
|
||||
export { hoverStore } from "./hover"
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { appStore } from "./app"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
import { currentRole } from "./derived"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { get, writable } from "svelte/store"
|
||||
import { push } from "svelte-spa-router"
|
||||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { peekStore } from "./peek"
|
||||
import { builderStore } from "./builder"
|
||||
|
||||
|
@ -119,7 +119,39 @@ const createRouteStore = () => {
|
|||
const base = window.location.href.split("#")[0]
|
||||
return `${base}#${relativeURL}`
|
||||
}
|
||||
const setTestUrlParams = (route: string, testValue: string) => {
|
||||
if (route === "/") {
|
||||
return
|
||||
}
|
||||
|
||||
const [pathPart, queryPart] = testValue.split("?")
|
||||
const routeSegments = route.split("/").filter(Boolean)
|
||||
|
||||
// If first segment happens to be a parameter (e.g. /:foo), include it
|
||||
const startIndex = routeSegments[0]?.startsWith(":") ? 0 : 1
|
||||
const segments = routeSegments.slice(startIndex)
|
||||
const testSegments = pathPart.split("/")
|
||||
|
||||
const params: Record<string, string> = {}
|
||||
segments.forEach((segment, index) => {
|
||||
if (segment.startsWith(":") && index < testSegments.length) {
|
||||
params[segment.slice(1)] = testSegments[index]
|
||||
}
|
||||
})
|
||||
|
||||
const queryParams: Record<string, string> = {}
|
||||
if (queryPart) {
|
||||
queryPart.split("&").forEach(param => {
|
||||
const [key, value] = param.split("=")
|
||||
if (key && value) {
|
||||
queryParams[key] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setQueryParams({ ...queryParams })
|
||||
store.update(state => ({ ...state, testUrlParams: params }))
|
||||
}
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
|
@ -130,6 +162,7 @@ const createRouteStore = () => {
|
|||
setQueryParams,
|
||||
setActiveRoute,
|
||||
setRouterLoaded,
|
||||
setTestUrlParams,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ import { routeStore } from "./routes"
|
|||
import { builderStore } from "./builder"
|
||||
import { appStore } from "./app"
|
||||
import { orgStore } from "./org"
|
||||
import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
||||
import { dndIndex, dndParent, dndSource } from "./dnd.ts"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "constants"
|
||||
import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "@/constants"
|
||||
|
||||
const createScreenStore = () => {
|
||||
const store = derived(
|
||||
|
@ -18,8 +18,7 @@ const createScreenStore = () => {
|
|||
orgStore,
|
||||
dndParent,
|
||||
dndIndex,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndSource,
|
||||
],
|
||||
([
|
||||
$appStore,
|
||||
|
@ -28,8 +27,7 @@ const createScreenStore = () => {
|
|||
$orgStore,
|
||||
$dndParent,
|
||||
$dndIndex,
|
||||
$dndIsNewComponent,
|
||||
$dndBounds,
|
||||
$dndSource,
|
||||
]) => {
|
||||
let activeLayout, activeScreen
|
||||
let screens
|
||||
|
@ -85,7 +83,7 @@ const createScreenStore = () => {
|
|||
|
||||
// Remove selected component from tree if we are moving an existing
|
||||
// component
|
||||
if (!$dndIsNewComponent && selectedParent) {
|
||||
if (!$dndSource.isNew && selectedParent) {
|
||||
selectedParent._children = selectedParent._children?.filter(
|
||||
x => x._id !== selectedComponentId
|
||||
)
|
||||
|
@ -97,11 +95,11 @@ const createScreenStore = () => {
|
|||
_id: DNDPlaceholderID,
|
||||
_styles: {
|
||||
normal: {
|
||||
width: `${$dndBounds?.width || 400}px`,
|
||||
height: `${$dndBounds?.height || 200}px`,
|
||||
width: `${$dndSource?.bounds?.width || 400}px`,
|
||||
height: `${$dndSource?.bounds?.height || 200}px`,
|
||||
opacity: 0,
|
||||
"--default-width": $dndBounds?.width || 400,
|
||||
"--default-height": $dndBounds?.height || 200,
|
||||
"--default-width": $dndSource?.bounds?.width || 400,
|
||||
"--default-height": $dndSource?.bounds?.height || 200,
|
||||
},
|
||||
},
|
||||
static: true,
|
||||
|
@ -198,3 +196,7 @@ const createScreenStore = () => {
|
|||
}
|
||||
|
||||
export const screenStore = createScreenStore()
|
||||
|
||||
export const isGridScreen = derived(screenStore, $screenStore => {
|
||||
return $screenStore.activeScreen?.props?.layout === "grid"
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
|
||||
export const getAPIKey = async () => {
|
||||
const { apiKey } = await API.fetchDeveloperInfo()
|
||||
|
|
|
@ -13,9 +13,9 @@ import {
|
|||
rowSelectionStore,
|
||||
sidePanelStore,
|
||||
modalStore,
|
||||
} from "stores"
|
||||
import { API } from "api"
|
||||
import { ActionTypes } from "constants"
|
||||
} from "@/stores"
|
||||
import { API } from "@/api"
|
||||
import { ActionTypes } from "@/constants"
|
||||
import { enrichDataBindings } from "./enrichDataBinding"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GridSpacing, GridRowHeight } from "@/constants"
|
||||
import { builderStore } from "stores"
|
||||
import { buildStyleString } from "utils/styleable.js"
|
||||
import { builderStore } from "@/stores"
|
||||
import { buildStyleString } from "@/utils/styleable.js"
|
||||
|
||||
interface GridMetadata {
|
||||
id: string
|
||||
|
@ -37,46 +37,54 @@ interface GridMetadata {
|
|||
*/
|
||||
|
||||
// Enum representing the different CSS variables we use for grid metadata
|
||||
export const GridParams = {
|
||||
HAlign: "h-align",
|
||||
VAlign: "v-align",
|
||||
ColStart: "col-start",
|
||||
ColEnd: "col-end",
|
||||
RowStart: "row-start",
|
||||
RowEnd: "row-end",
|
||||
export enum GridParams {
|
||||
HAlign = "h-align",
|
||||
VAlign = "v-align",
|
||||
ColStart = "col-start",
|
||||
ColEnd = "col-end",
|
||||
RowStart = "row-start",
|
||||
RowEnd = "row-end",
|
||||
}
|
||||
|
||||
// Classes used in selectors inside grid containers to control child styles
|
||||
export const GridClasses = {
|
||||
DesktopFill: "grid-desktop-grow",
|
||||
MobileFill: "grid-mobile-grow",
|
||||
export enum GridClasses {
|
||||
DesktopFill = "grid-desktop-grow",
|
||||
MobileFill = "grid-mobile-grow",
|
||||
}
|
||||
|
||||
// Enum for device preview type, included in grid CSS variables
|
||||
export const Devices = {
|
||||
Desktop: "desktop",
|
||||
Mobile: "mobile",
|
||||
export enum Devices {
|
||||
Desktop = "desktop",
|
||||
Mobile = "mobile",
|
||||
}
|
||||
|
||||
export const GridDragModes = {
|
||||
Resize: "resize",
|
||||
Move: "move",
|
||||
export enum GridDragMode {
|
||||
Resize = "resize",
|
||||
Move = "move",
|
||||
}
|
||||
|
||||
// Builds a CSS variable name for a certain piece of grid metadata
|
||||
export const getGridVar = (device: string, param: string) =>
|
||||
`--grid-${device}-${param}`
|
||||
|
||||
export interface GridEvent extends DragEvent {
|
||||
target: HTMLElement
|
||||
dataTransfer: DataTransfer
|
||||
}
|
||||
|
||||
// Determines whether a JS event originated from immediately within a grid
|
||||
export const isGridEvent = (e: Event & { target: HTMLElement }): boolean => {
|
||||
export const isGridEvent = (e: Event): e is GridEvent => {
|
||||
if (!(e.target instanceof HTMLElement)) {
|
||||
return false
|
||||
}
|
||||
const componentParent = e.target.closest?.(".component")?.parentNode as
|
||||
| HTMLElement
|
||||
| undefined
|
||||
const gridDOMCandidate = componentParent?.closest(".component")
|
||||
?.childNodes[0] as HTMLElement | undefined
|
||||
return (
|
||||
e.target.dataset?.indicator === "true" ||
|
||||
// @ts-expect-error: api is not properly typed
|
||||
e.target
|
||||
.closest?.(".component")
|
||||
// @ts-expect-error
|
||||
?.parentNode.closest(".component")
|
||||
?.childNodes[0]?.classList?.contains("grid")
|
||||
!!gridDOMCandidate?.classList?.contains("grid")
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { get } from "svelte/store"
|
||||
import { link, LinkActionOpts } from "svelte-spa-router"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
export const linkable = (node: HTMLElement, href?: LinkActionOpts) => {
|
||||
if (get(builderStore).inBuilder) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { API } from "@/api"
|
||||
import { DataFetchMap, DataFetchType } from "@budibase/frontend-core"
|
||||
import { FieldType, TableSchema } from "@budibase/types"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { builderStore } from "stores"
|
||||
import { builderStore } from "@/stores"
|
||||
|
||||
/**
|
||||
* Helper to build a CSS string from a style object.
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
builderStore,
|
||||
environmentStore,
|
||||
notificationStore,
|
||||
} from "./stores/index.js"
|
||||
import { builderStore, environmentStore, notificationStore } from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ export default defineConfig(({ mode }) => {
|
|||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: "src/index.js",
|
||||
entry: "src/index.ts",
|
||||
formats: ["iife"],
|
||||
outDir: "dist",
|
||||
name: "budibase_client",
|
||||
|
@ -47,34 +47,6 @@ export default defineConfig(({ mode }) => {
|
|||
find: "manifest.json",
|
||||
replacement: path.resolve("./manifest.json"),
|
||||
},
|
||||
{
|
||||
find: "api",
|
||||
replacement: path.resolve("./src/api"),
|
||||
},
|
||||
{
|
||||
find: "components",
|
||||
replacement: path.resolve("./src/components"),
|
||||
},
|
||||
{
|
||||
find: "stores",
|
||||
replacement: path.resolve("./src/stores"),
|
||||
},
|
||||
{
|
||||
find: "utils",
|
||||
replacement: path.resolve("./src/utils"),
|
||||
},
|
||||
{
|
||||
find: "constants",
|
||||
replacement: path.resolve("./src/constants"),
|
||||
},
|
||||
{
|
||||
find: "@/constants",
|
||||
replacement: path.resolve("./src/constants"),
|
||||
},
|
||||
{
|
||||
find: "sdk",
|
||||
replacement: path.resolve("./src/sdk"),
|
||||
},
|
||||
{
|
||||
find: "@budibase/types",
|
||||
replacement: path.resolve("../types/src"),
|
||||
|
@ -87,6 +59,10 @@ export default defineConfig(({ mode }) => {
|
|||
find: "@budibase/bbui",
|
||||
replacement: path.resolve("../bbui/src"),
|
||||
},
|
||||
{
|
||||
find: "@",
|
||||
replacement: path.resolve(__dirname, "src"),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Readable, Writable } from "svelte/store"
|
||||
|
||||
declare module "./memo" {
|
||||
export function memo<T>(value: T): Writable<T>
|
||||
export function memo<T>(value?: T): Writable<T>
|
||||
|
||||
export function derivedMemo<TStore, TResult>(
|
||||
store: Readable<TStore>,
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
"jsonschema": "1.4.0",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"knex": "2.4.2",
|
||||
"koa": "2.13.4",
|
||||
"koa": "2.15.4",
|
||||
"koa-body": "4.2.0",
|
||||
"koa-compress": "4.0.1",
|
||||
"koa-send": "5.0.1",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue