Add full featured component settings and bindings devtools tabs
This commit is contained in:
parent
969e6531d5
commit
755e12a24d
|
@ -3373,7 +3373,7 @@
|
|||
"label": "Rows",
|
||||
"key": "rows"
|
||||
},
|
||||
{
|
||||
{
|
||||
"label": "Extra Info",
|
||||
"key": "info"
|
||||
},
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
|
||||
{#if $devToolsStore.visible && !$builderStore.inBuilder}
|
||||
{#if $appStore.isDevApp && !$builderStore.inBuilder}
|
||||
<DevTools />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
propsAreSame,
|
||||
getSettingsDefinition,
|
||||
} from "utils/componentProps"
|
||||
import { builderStore, devToolsStore, componentStore } from "stores"
|
||||
import { builderStore, devToolsStore, componentStore, appStore } from "stores"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import Manifest from "manifest.json"
|
||||
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
||||
|
@ -349,15 +349,25 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
componentStore.actions.registerInstance(id, {
|
||||
getSettings: () => cachedSettings,
|
||||
getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }),
|
||||
getDataContext: () => get(context),
|
||||
})
|
||||
if (
|
||||
$appStore.isDevApp &&
|
||||
!componentStore.actions.isComponentRegistered(id)
|
||||
) {
|
||||
componentStore.actions.registerInstance(id, {
|
||||
getSettings: () => cachedSettings,
|
||||
getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }),
|
||||
getDataContext: () => get(context),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
componentStore.actions.unregisterInstance(id)
|
||||
if (
|
||||
$appStore.isDevApp &&
|
||||
componentStore.actions.isComponentRegistered(id)
|
||||
) {
|
||||
componentStore.actions.unregisterInstance(id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -3,34 +3,40 @@
|
|||
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>
|
||||
|
||||
<div class="devtools" class:mobile={$context.device.mobile}>
|
||||
<Layout noPadding gap="XS">
|
||||
<div class="header">
|
||||
<Heading size="XS">Budibase DevTools</Heading>
|
||||
<Icon
|
||||
hoverable
|
||||
name="Close"
|
||||
on:click={() => devToolsStore.actions.setVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
<Tabs selected="Application">
|
||||
<Tab title="Application">
|
||||
<div class="tab-content">
|
||||
<DevToolsStatsTab />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Components">
|
||||
<div class="tab-content">
|
||||
<DevToolsComponentTab />
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Layout>
|
||||
<div
|
||||
class="devtools"
|
||||
class:hidden={!$devToolsStore.visible}
|
||||
class:mobile={$context.device.mobile}
|
||||
>
|
||||
{#if $devToolsStore.visible}
|
||||
<Layout noPadding gap="XS">
|
||||
<div class="header">
|
||||
<Heading size="XS">Budibase DevTools</Heading>
|
||||
<Icon
|
||||
hoverable
|
||||
name="Close"
|
||||
on:click={() => devToolsStore.actions.setVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
<Tabs selected="Application">
|
||||
<Tab title="Application">
|
||||
<div class="tab-content">
|
||||
<DevToolsStatsTab />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Components">
|
||||
<div class="tab-content">
|
||||
<DevToolsComponentTab />
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Layout>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -39,6 +45,11 @@
|
|||
flex: 0 0 320px;
|
||||
border-left: 1px solid var(--spectrum-global-color-gray-300);
|
||||
overflow: auto;
|
||||
transition: margin-right 300ms ease;
|
||||
margin-right: 0;
|
||||
}
|
||||
.devtools.hidden {
|
||||
margin-right: -320px;
|
||||
}
|
||||
.devtools.mobile {
|
||||
display: none;
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<script>
|
||||
import { Layout, Select, Body } from "@budibase/bbui"
|
||||
import { componentStore } from "stores/index.js"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
|
||||
const ReadableBindingMap = {
|
||||
user: "Current user",
|
||||
state: "State",
|
||||
url: "URL",
|
||||
device: "Device",
|
||||
}
|
||||
|
||||
let category
|
||||
|
||||
$: selectedInstance = $componentStore.selectedComponentInstance
|
||||
$: context = selectedInstance?.getDataContext()
|
||||
$: bindingCategories = getContextProviders(context)
|
||||
$: bindings = Object.entries(context?.[category] || {})
|
||||
|
||||
const getContextProviders = context => {
|
||||
const filteredContext = { ...context }
|
||||
|
||||
// Remove some keys from context
|
||||
delete filteredContext.key
|
||||
delete filteredContext.closestComponentId
|
||||
delete filteredContext.user_RefreshDataSource
|
||||
|
||||
// Keep track of encountered IDs so we can find actions
|
||||
let actions = []
|
||||
let encounteredCategories = []
|
||||
|
||||
// Create readable bindings
|
||||
let categories = []
|
||||
Object.keys(filteredContext)
|
||||
.sort()
|
||||
.forEach(category => {
|
||||
let isAction = false
|
||||
for (let cat of encounteredCategories) {
|
||||
if (category.startsWith(`${cat}_`)) {
|
||||
isAction = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isAction) {
|
||||
actions.push(category)
|
||||
return
|
||||
}
|
||||
|
||||
// Mark category as encountered so we can find any matching actions
|
||||
encounteredCategories.push(category)
|
||||
|
||||
// Map any static categories to pretty names
|
||||
if (ReadableBindingMap[category]) {
|
||||
categories.push({
|
||||
label: ReadableBindingMap[category],
|
||||
value: category,
|
||||
})
|
||||
} else {
|
||||
const component = componentStore.actions.getComponentById(category)
|
||||
if (component) {
|
||||
categories.push({
|
||||
label: component._instanceName,
|
||||
value: category,
|
||||
})
|
||||
} else {
|
||||
// Check if its a block
|
||||
if (category.includes("-")) {
|
||||
const split = category.split("-")
|
||||
const potentialId = split[0]
|
||||
const component =
|
||||
componentStore.actions.getComponentById(potentialId)
|
||||
if (component) {
|
||||
categories.push({
|
||||
label: `${component._instanceName} (${split[1]})`,
|
||||
value: category,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we don't know
|
||||
categories.push({
|
||||
label: "Unknown - " + category,
|
||||
value: category,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return categories
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
<Body size="S">
|
||||
Choose a category to see the value of all its available bindings.
|
||||
</Body>
|
||||
<Select bind:value={category} label="Category" options={bindingCategories} />
|
||||
{#if bindings?.length}
|
||||
<Layout noPadding gap="XS">
|
||||
{#each bindings as binding}
|
||||
<DevToolsStat
|
||||
copyable
|
||||
label={binding[0]}
|
||||
value={JSON.stringify(binding[1])}
|
||||
/>
|
||||
{/each}
|
||||
</Layout>
|
||||
{:else if category}
|
||||
<Body size="XS">There aren't any bindings available in this category.</Body>
|
||||
{/if}
|
||||
</Layout>
|
|
@ -0,0 +1,30 @@
|
|||
<script>
|
||||
import { Layout, Toggle } from "@budibase/bbui"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
import { componentStore } from "stores/index.js"
|
||||
import { getSettingsDefinition } from "utils/componentProps.js"
|
||||
|
||||
let showEnrichedSettings = true
|
||||
|
||||
$: selectedInstance = $componentStore.selectedComponentInstance
|
||||
$: settingsDefinition = getSettingsDefinition(
|
||||
$componentStore.selectedComponentDefinition
|
||||
)
|
||||
$: rawSettings = selectedInstance?.getRawSettings()
|
||||
$: settings = selectedInstance?.getSettings()
|
||||
</script>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
<Toggle text="Show enriched settings" bind:value={showEnrichedSettings} />
|
||||
<Layout noPadding gap="XS">
|
||||
{#each settingsDefinition as setting}
|
||||
<DevToolsStat
|
||||
copyable
|
||||
label={setting.label}
|
||||
value={JSON.stringify(
|
||||
(showEnrichedSettings ? settings : rawSettings)?.[setting.key]
|
||||
)}
|
||||
/>
|
||||
{/each}
|
||||
</Layout>
|
||||
</Layout>
|
|
@ -1,29 +1,13 @@
|
|||
<script>
|
||||
import {
|
||||
Body,
|
||||
Layout,
|
||||
Heading,
|
||||
Button,
|
||||
TextArea,
|
||||
Tabs,
|
||||
Tab,
|
||||
Toggle,
|
||||
} from "@budibase/bbui"
|
||||
import { Body, Layout, Heading, Button, Tabs, Tab } from "@budibase/bbui"
|
||||
import { builderStore, devToolsStore, componentStore } from "stores"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
import { getSettingsDefinition } from "utils/componentProps.js"
|
||||
|
||||
let showEnrichedSettings = true
|
||||
|
||||
$: selectedInstance = $componentStore.selectedComponentInstance
|
||||
$: settingsDefinition = getSettingsDefinition(
|
||||
$componentStore.selectedComponentDefinition
|
||||
)
|
||||
$: rawSettings = selectedInstance?.getRawSettings()
|
||||
$: settings = selectedInstance?.getSettings()
|
||||
import DevToolsComponentSettingsTab from "./DevToolsComponentSettingsTab.svelte"
|
||||
import DevToolsComponentContextTab from "./DevToolsComponentContextTab.svelte"
|
||||
|
||||
$: {
|
||||
if (!selectedInstance) {
|
||||
// Reset selection store if we can't find a matching instance
|
||||
if (!$componentStore.selectedComponentInstance) {
|
||||
builderStore.actions.selectComponent(null)
|
||||
}
|
||||
}
|
||||
|
@ -46,17 +30,19 @@
|
|||
</div>
|
||||
</Layout>
|
||||
{:else}
|
||||
<Layout noPadding>
|
||||
<Layout noPadding gap="S">
|
||||
<Heading size="XS">
|
||||
{$componentStore.selectedComponent?._instanceName}
|
||||
</Heading>
|
||||
<Layout noPadding gap="XS">
|
||||
<DevToolsStat
|
||||
label="Component"
|
||||
value={$componentStore.selectedComponent?._instanceName}
|
||||
/>
|
||||
<DevToolsStat
|
||||
label="Type"
|
||||
value={$componentStore.selectedComponentDefinition?.name}
|
||||
/>
|
||||
<DevToolsStat label="ID" value={$componentStore.selectedComponent?._id} />
|
||||
<DevToolsStat
|
||||
label="Component ID"
|
||||
value={$componentStore.selectedComponent?._id}
|
||||
/>
|
||||
</Layout>
|
||||
<div class="buttons">
|
||||
<Button
|
||||
|
@ -79,37 +65,12 @@
|
|||
<Tabs selected="Settings">
|
||||
<Tab title="Settings">
|
||||
<div class="tab-content">
|
||||
<Layout noPadding gap="S">
|
||||
<Toggle
|
||||
text="Show enriched settings"
|
||||
bind:value={showEnrichedSettings}
|
||||
/>
|
||||
<Layout noPadding gap="XS">
|
||||
{#each settingsDefinition as setting}
|
||||
<DevToolsStat
|
||||
label={setting.label}
|
||||
value={JSON.stringify(
|
||||
(showEnrichedSettings ? settings : rawSettings)?.[
|
||||
setting.key
|
||||
]
|
||||
)}
|
||||
/>
|
||||
{/each}
|
||||
</Layout>
|
||||
</Layout>
|
||||
<DevToolsComponentSettingsTab />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Context">
|
||||
<Tab title="Bindings">
|
||||
<div class="tab-content">
|
||||
<TextArea
|
||||
readonly
|
||||
label="Data context"
|
||||
value={JSON.stringify(
|
||||
selectedInstance?.getDataContext(),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
<DevToolsComponentContextTab />
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
|
|
@ -44,9 +44,9 @@
|
|||
quiet
|
||||
overBackground
|
||||
icon="Code"
|
||||
on:click={() => devToolsStore.actions.setVisible(true)}
|
||||
on:click={() => devToolsStore.actions.setVisible(!$devToolsStore.visible)}
|
||||
>
|
||||
Open DevTools
|
||||
{$devToolsStore.visible ? "Close" : "Open"} DevTools
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,39 @@
|
|||
<script>
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { notificationStore } from "stores"
|
||||
|
||||
export let label
|
||||
export let value
|
||||
export let copyable = false
|
||||
|
||||
$: prettyLabel = label == null ? "-" : label
|
||||
$: prettyValue = value == null ? "-" : value
|
||||
$: empty = value == null
|
||||
$: canCopy = copyable && !empty
|
||||
|
||||
const copyValue = async () => {
|
||||
try {
|
||||
await Helpers.copyToClipboard(value)
|
||||
notificationStore.actions.success("Copied to clipboard")
|
||||
} catch (error) {
|
||||
notificationStore.actions.error(
|
||||
"Failed to copy to clipboard. Check the dev console for the value."
|
||||
)
|
||||
console.warn("Failed to copy the value", value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-label">{label}</div>
|
||||
<div class="stat-value" title={value} on:click={() => console.log(value)}>
|
||||
{value == null ? " " : value}
|
||||
<div class="stat-label" title={prettyLabel}>{prettyLabel}</div>
|
||||
<div
|
||||
class="stat-value"
|
||||
class:copyable={canCopy}
|
||||
class:empty
|
||||
title={prettyValue}
|
||||
on:click={canCopy ? copyValue : null}
|
||||
>
|
||||
{prettyValue}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -23,7 +50,7 @@
|
|||
color: var(--spectrum-global-color-gray-600);
|
||||
text-transform: uppercase;
|
||||
flex: 0 0 auto;
|
||||
max-width: 140px;
|
||||
width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
@ -35,5 +62,14 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: color var(--spectrum-global-animation-duration-100, 130ms)
|
||||
ease-in-out;
|
||||
}
|
||||
.stat-value.empty {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
.stat-value.copyable:hover {
|
||||
color: var(--spectrum-global-color-blue-600);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Layout } from "@budibase/bbui"
|
||||
import { authStore, appStore, screenStore } from "stores"
|
||||
import { authStore, appStore, screenStore, componentStore } from "stores"
|
||||
import DevToolsStat from "./DevToolsStat.svelte"
|
||||
</script>
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
|||
label="Active screen"
|
||||
value={$screenStore.activeScreen?.routing.route}
|
||||
/>
|
||||
<DevToolsStat label="Components" value={$componentStore.mountedComponents} />
|
||||
<DevToolsStat label="User" value={$authStore.email} />
|
||||
<DevToolsStat label="Role" value={$authStore.roleId} />
|
||||
</Layout>
|
||||
|
|
|
@ -9,8 +9,8 @@ const createComponentStore = () => {
|
|||
const store = writable({})
|
||||
|
||||
const derivedStore = derived(
|
||||
[builderStore, devToolsStore, screenStore],
|
||||
([$builderState, $devToolsState, $screenState]) => {
|
||||
[store, builderStore, devToolsStore, screenStore],
|
||||
([$store, $builderState, $devToolsState, $screenState]) => {
|
||||
// Avoid any of this logic if we aren't in the builder preview
|
||||
if (!$builderState.inBuilder && !$devToolsState.visible) {
|
||||
return {}
|
||||
|
@ -34,10 +34,12 @@ const createComponentStore = () => {
|
|||
findComponentPathById(asset?.props, selectedComponentId) || []
|
||||
|
||||
return {
|
||||
selectedComponentInstance: get(store)[selectedComponentId],
|
||||
selectedComponentInstance: $store[selectedComponentId],
|
||||
selectedComponent: component,
|
||||
selectedComponentDefinition: definition,
|
||||
selectedComponentPath: path?.map(component => component._id),
|
||||
mountedComponents: Object.keys($store).length,
|
||||
currentAsset: asset,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -56,9 +58,23 @@ const createComponentStore = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const isComponentRegistered = id => {
|
||||
return get(store)[id] != null
|
||||
}
|
||||
|
||||
const getComponentById = id => {
|
||||
const asset = get(derivedStore).currentAsset
|
||||
return findComponentById(asset?.props, id)
|
||||
}
|
||||
|
||||
return {
|
||||
...derivedStore,
|
||||
actions: { registerInstance, unregisterInstance },
|
||||
actions: {
|
||||
registerInstance,
|
||||
unregisterInstance,
|
||||
isComponentRegistered,
|
||||
getComponentById,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue