commit
ff53acfb9f
|
@ -21,6 +21,10 @@ export const DEFINITIONS: MigrationDefinition[] = [
|
||||||
type: MigrationType.APP,
|
type: MigrationType.APP,
|
||||||
name: MigrationName.EVENT_APP_BACKFILL,
|
name: MigrationName.EVENT_APP_BACKFILL,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.APP,
|
||||||
|
name: MigrationName.TABLE_SETTINGS_LINKS_TO_ACTIONS,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: MigrationType.GLOBAL,
|
type: MigrationType.GLOBAL,
|
||||||
name: MigrationName.EVENT_GLOBAL_BACKFILL,
|
name: MigrationName.EVENT_GLOBAL_BACKFILL,
|
||||||
|
|
|
@ -1,18 +1,53 @@
|
||||||
export default function clickOutside(element, callbackFunction) {
|
const ignoredClasses = [".flatpickr-calendar", ".modal-container"]
|
||||||
function onClick(event) {
|
let clickHandlers = []
|
||||||
if (!element.contains(event.target)) {
|
|
||||||
callbackFunction(event)
|
/**
|
||||||
|
* Handle a body click event
|
||||||
|
*/
|
||||||
|
const handleClick = event => {
|
||||||
|
// Ignore click if needed
|
||||||
|
for (let className of ignoredClasses) {
|
||||||
|
if (event.target.closest(className)) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.addEventListener("click", onClick, true)
|
// Process handlers
|
||||||
|
clickHandlers.forEach(handler => {
|
||||||
|
if (!handler.element.contains(event.target)) {
|
||||||
|
handler.callback?.(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
document.documentElement.addEventListener("click", handleClick, true)
|
||||||
|
|
||||||
return {
|
/**
|
||||||
update(newCallbackFunction) {
|
* Adds or updates a click handler
|
||||||
callbackFunction = newCallbackFunction
|
*/
|
||||||
},
|
const updateHandler = (id, element, callback) => {
|
||||||
destroy() {
|
let existingHandler = clickHandlers.find(x => x.id === id)
|
||||||
document.body.removeEventListener("click", onClick, true)
|
if (!existingHandler) {
|
||||||
},
|
clickHandlers.push({ id, element, callback })
|
||||||
|
} else {
|
||||||
|
existingHandler.callback = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a click handler
|
||||||
|
*/
|
||||||
|
const removeHandler = id => {
|
||||||
|
clickHandlers = clickHandlers.filter(x => x.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Svelte action to apply a click outside handler for a certain element
|
||||||
|
*/
|
||||||
|
export default (element, callback) => {
|
||||||
|
const id = Math.random()
|
||||||
|
updateHandler(id, element, callback)
|
||||||
|
return {
|
||||||
|
update: newCallback => updateHandler(id, element, newCallback),
|
||||||
|
destroy: () => removeHandler(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,19 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="property-group-container">
|
<div class="property-group-container">
|
||||||
|
{#if name}
|
||||||
<div class="property-group-name" on:click={onHeaderClick}>
|
<div class="property-group-name" on:click={onHeaderClick}>
|
||||||
<div class="name">{name}</div>
|
<div class="name">{name}</div>
|
||||||
{#if collapsible}
|
{#if collapsible}
|
||||||
<Icon size="S" name={show ? "Remove" : "Add"} />
|
<Icon size="S" name={show ? "Remove" : "Add"} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="property-panel" class:show={show || !collapsible}>
|
{/if}
|
||||||
|
<div
|
||||||
|
class="property-panel"
|
||||||
|
class:show={show || !collapsible}
|
||||||
|
class:no-title={!name}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,6 +78,9 @@
|
||||||
padding: var(--spacing-s) var(--spacing-xl) var(--spacing-xl)
|
padding: var(--spacing-s) var(--spacing-xl) var(--spacing-xl)
|
||||||
var(--spacing-xl);
|
var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
.property-panel.no-title {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -23,6 +23,15 @@
|
||||||
let open = false
|
let open = false
|
||||||
let flatpickr, flatpickrOptions
|
let flatpickr, flatpickrOptions
|
||||||
|
|
||||||
|
// Another classic flatpickr issue. Errors were randomly being thrown due to
|
||||||
|
// flatpickr internal code. Making sure that "destroy" is a valid function
|
||||||
|
// fixes it. The sooner we remove flatpickr the better.
|
||||||
|
$: {
|
||||||
|
if (flatpickr && !flatpickr.destroy) {
|
||||||
|
flatpickr.destroy = () => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resolveTimeStamp = timestamp => {
|
const resolveTimeStamp = timestamp => {
|
||||||
let maskedDate = new Date(`0-${timestamp}`)
|
let maskedDate = new Date(`0-${timestamp}`)
|
||||||
|
|
||||||
|
@ -252,6 +261,7 @@
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
:global(.flatpickr-calendar) {
|
:global(.flatpickr-calendar) {
|
||||||
font-family: "Source Sans Pro", sans-serif;
|
font-family: "Source Sans Pro", sans-serif;
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
transition: color var(--spectrum-global-animation-duration-100, 130ms);
|
transition: color var(--spectrum-global-animation-duration-100, 130ms);
|
||||||
}
|
}
|
||||||
svg.hoverable:hover {
|
svg.hoverable:hover {
|
||||||
color: var(--spectrum-alias-icon-color-selected-hover);
|
color: var(--spectrum-alias-icon-color-selected-hover) !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,13 +275,14 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
{#key fields?.length}
|
||||||
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:wrapper--quiet={quiet}
|
class:wrapper--quiet={quiet}
|
||||||
class:wrapper--compact={compact}
|
class:wrapper--compact={compact}
|
||||||
bind:offsetHeight={height}
|
bind:offsetHeight={height}
|
||||||
style={`--row-height: ${rowHeight}px; --header-height: ${headerHeight}px;`}
|
style={`--row-height: ${rowHeight}px; --header-height: ${headerHeight}px;`}
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="loading" style={heightStyle}>
|
<div class="loading" style={heightStyle}>
|
||||||
<slot name="loadingIndicator">
|
<slot name="loadingIndicator">
|
||||||
|
@ -313,8 +314,8 @@
|
||||||
class:noBorderHeader={!showHeaderBorder}
|
class:noBorderHeader={!showHeaderBorder}
|
||||||
class:spectrum-Table-headCell--alignCenter={schema[field]
|
class:spectrum-Table-headCell--alignCenter={schema[field]
|
||||||
.align === "Center"}
|
.align === "Center"}
|
||||||
class:spectrum-Table-headCell--alignRight={schema[field].align ===
|
class:spectrum-Table-headCell--alignRight={schema[field]
|
||||||
"Right"}
|
.align === "Right"}
|
||||||
class:is-sortable={schema[field].sortable !== false}
|
class:is-sortable={schema[field].sortable !== false}
|
||||||
class:is-sorted-desc={sortColumn === field &&
|
class:is-sorted-desc={sortColumn === field &&
|
||||||
sortOrder === "Descending"}
|
sortOrder === "Descending"}
|
||||||
|
@ -425,7 +426,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/key}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Wrapper */
|
/* Wrapper */
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export const Events = {
|
export const Events = {
|
||||||
COMPONENT_CREATED: "component:created",
|
COMPONENT_CREATED: "component:created",
|
||||||
|
COMPONENT_UPDATED: "component:updated",
|
||||||
APP_VIEW_PUBLISHED: "app:view_published",
|
APP_VIEW_PUBLISHED: "app:view_published",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,13 +182,21 @@ export const makeComponentUnique = component => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace component ID
|
// Generate a full set of component ID replacements in this tree
|
||||||
|
const idReplacements = []
|
||||||
|
const generateIdReplacements = (component, replacements) => {
|
||||||
const oldId = component._id
|
const oldId = component._id
|
||||||
const newId = Helpers.uuid()
|
const newId = Helpers.uuid()
|
||||||
let definition = JSON.stringify(component)
|
replacements.push([oldId, newId])
|
||||||
|
component._children?.forEach(x => generateIdReplacements(x, replacements))
|
||||||
|
}
|
||||||
|
generateIdReplacements(component, idReplacements)
|
||||||
|
|
||||||
// Replace all instances of this ID in HBS bindings
|
// Replace all instances of this ID in HBS bindings
|
||||||
|
let definition = JSON.stringify(component)
|
||||||
|
idReplacements.forEach(([oldId, newId]) => {
|
||||||
definition = definition.replace(new RegExp(oldId, "g"), newId)
|
definition = definition.replace(new RegExp(oldId, "g"), newId)
|
||||||
|
})
|
||||||
|
|
||||||
// Replace all instances of this ID in JS bindings
|
// Replace all instances of this ID in JS bindings
|
||||||
const bindings = findHBSBlocks(definition)
|
const bindings = findHBSBlocks(definition)
|
||||||
|
@ -201,7 +209,9 @@ export const makeComponentUnique = component => {
|
||||||
let js = decodeJSBinding(sanitizedBinding)
|
let js = decodeJSBinding(sanitizedBinding)
|
||||||
if (js != null) {
|
if (js != null) {
|
||||||
// Replace ID inside JS binding
|
// Replace ID inside JS binding
|
||||||
|
idReplacements.forEach(([oldId, newId]) => {
|
||||||
js = js.replace(new RegExp(oldId, "g"), newId)
|
js = js.replace(new RegExp(oldId, "g"), newId)
|
||||||
|
})
|
||||||
|
|
||||||
// Create new valid JS binding
|
// Create new valid JS binding
|
||||||
let newBinding = encodeJSBinding(js)
|
let newBinding = encodeJSBinding(js)
|
||||||
|
@ -218,9 +228,5 @@ export const makeComponentUnique = component => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Recurse on all children
|
// Recurse on all children
|
||||||
component = JSON.parse(definition)
|
return JSON.parse(definition)
|
||||||
return {
|
|
||||||
...component,
|
|
||||||
_children: component._children?.map(makeComponentUnique),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
messagePassing: false,
|
messagePassing: false,
|
||||||
continueIfAction: false,
|
continueIfAction: false,
|
||||||
showNotificationAction: false,
|
showNotificationAction: false,
|
||||||
|
sidePanel: false,
|
||||||
},
|
},
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import newRowScreen from "./newRowScreen"
|
|
||||||
import rowDetailScreen from "./rowDetailScreen"
|
|
||||||
import rowListScreen from "./rowListScreen"
|
import rowListScreen from "./rowListScreen"
|
||||||
import createFromScratchScreen from "./createFromScratchScreen"
|
import createFromScratchScreen from "./createFromScratchScreen"
|
||||||
|
|
||||||
const allTemplates = tables => [
|
const allTemplates = tables => [...rowListScreen(tables)]
|
||||||
...newRowScreen(tables),
|
|
||||||
...rowDetailScreen(tables),
|
|
||||||
...rowListScreen(tables),
|
|
||||||
]
|
|
||||||
|
|
||||||
// Allows us to apply common behaviour to all create() functions
|
// Allows us to apply common behaviour to all create() functions
|
||||||
const createTemplateOverride = (frontendState, template) => () => {
|
const createTemplateOverride = (frontendState, template) => () => {
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
import sanitizeUrl from "./utils/sanitizeUrl"
|
|
||||||
import { Screen } from "./utils/Screen"
|
|
||||||
import { Component } from "./utils/Component"
|
|
||||||
import { makeBreadcrumbContainer } from "./utils/commonComponents"
|
|
||||||
import { getSchemaForDatasource } from "../../dataBinding"
|
|
||||||
|
|
||||||
export default function (tables) {
|
|
||||||
return tables.map(table => {
|
|
||||||
return {
|
|
||||||
name: `${table.name} - New`,
|
|
||||||
create: () => createScreen(table),
|
|
||||||
id: NEW_ROW_TEMPLATE,
|
|
||||||
table: table._id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const newRowUrl = table => sanitizeUrl(`/${table.name}/new/row`)
|
|
||||||
export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE"
|
|
||||||
|
|
||||||
const rowListUrl = table => sanitizeUrl(`/${table.name}`)
|
|
||||||
|
|
||||||
const getFields = schema => {
|
|
||||||
let columns = []
|
|
||||||
Object.entries(schema || {}).forEach(([field, fieldSchema]) => {
|
|
||||||
if (!field || !fieldSchema) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!fieldSchema?.autocolumn) {
|
|
||||||
columns.push(field)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return columns
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateFormBlock = table => {
|
|
||||||
const datasource = { type: "table", tableId: table._id }
|
|
||||||
const { schema } = getSchemaForDatasource(null, datasource, {
|
|
||||||
formSchema: true,
|
|
||||||
})
|
|
||||||
const formBlock = new Component("@budibase/standard-components/formblock")
|
|
||||||
formBlock
|
|
||||||
.customProps({
|
|
||||||
title: "New row",
|
|
||||||
actionType: "Create",
|
|
||||||
actionUrl: rowListUrl(table),
|
|
||||||
showDeleteButton: false,
|
|
||||||
showSaveButton: true,
|
|
||||||
fields: getFields(schema),
|
|
||||||
dataSource: {
|
|
||||||
label: table.name,
|
|
||||||
tableId: table._id,
|
|
||||||
type: "table",
|
|
||||||
},
|
|
||||||
labelPosition: "left",
|
|
||||||
size: "spectrum--medium",
|
|
||||||
})
|
|
||||||
.instanceName(`${table.name} - Form block`)
|
|
||||||
return formBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScreen = table => {
|
|
||||||
const formBlock = generateFormBlock(table)
|
|
||||||
const screen = new Screen()
|
|
||||||
.instanceName(`${table.name} - New`)
|
|
||||||
.route(newRowUrl(table))
|
|
||||||
|
|
||||||
return screen
|
|
||||||
.addChild(makeBreadcrumbContainer(table.name, "New row"))
|
|
||||||
.addChild(formBlock)
|
|
||||||
.json()
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
import sanitizeUrl from "./utils/sanitizeUrl"
|
|
||||||
import { Screen } from "./utils/Screen"
|
|
||||||
import { Component } from "./utils/Component"
|
|
||||||
import { makeBreadcrumbContainer } from "./utils/commonComponents"
|
|
||||||
import { getSchemaForDatasource } from "../../dataBinding"
|
|
||||||
|
|
||||||
export default function (tables) {
|
|
||||||
return tables.map(table => {
|
|
||||||
return {
|
|
||||||
name: `${table.name} - Detail`,
|
|
||||||
create: () => createScreen(table),
|
|
||||||
id: ROW_DETAIL_TEMPLATE,
|
|
||||||
table: table._id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE"
|
|
||||||
export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`)
|
|
||||||
|
|
||||||
const rowListUrl = table => sanitizeUrl(`/${table.name}`)
|
|
||||||
|
|
||||||
const getFields = schema => {
|
|
||||||
let columns = []
|
|
||||||
Object.entries(schema || {}).forEach(([field, fieldSchema]) => {
|
|
||||||
if (!field || !fieldSchema) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!fieldSchema?.autocolumn) {
|
|
||||||
columns.push(field)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return columns
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateFormBlock = table => {
|
|
||||||
const datasource = { type: "table", tableId: table._id }
|
|
||||||
const { schema } = getSchemaForDatasource(null, datasource, {
|
|
||||||
formSchema: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const formBlock = new Component("@budibase/standard-components/formblock")
|
|
||||||
formBlock
|
|
||||||
.customProps({
|
|
||||||
title: "Edit row",
|
|
||||||
actionType: "Update",
|
|
||||||
actionUrl: rowListUrl(table),
|
|
||||||
showDeleteButton: true,
|
|
||||||
showSaveButton: true,
|
|
||||||
fields: getFields(schema),
|
|
||||||
dataSource: {
|
|
||||||
label: table.name,
|
|
||||||
tableId: table._id,
|
|
||||||
type: "table",
|
|
||||||
},
|
|
||||||
labelPosition: "left",
|
|
||||||
size: "spectrum--medium",
|
|
||||||
})
|
|
||||||
.instanceName(`${table.name} - Form block`)
|
|
||||||
return formBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScreen = table => {
|
|
||||||
return new Screen()
|
|
||||||
.instanceName(`${table.name} - Detail`)
|
|
||||||
.route(rowDetailUrl(table))
|
|
||||||
.addChild(makeBreadcrumbContainer(table.name, "Edit row"))
|
|
||||||
.addChild(generateFormBlock(table))
|
|
||||||
.json()
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import sanitizeUrl from "./utils/sanitizeUrl"
|
import sanitizeUrl from "./utils/sanitizeUrl"
|
||||||
import { newRowUrl } from "./newRowScreen"
|
|
||||||
import { Screen } from "./utils/Screen"
|
import { Screen } from "./utils/Screen"
|
||||||
import { Component } from "./utils/Component"
|
import { Component } from "./utils/Component"
|
||||||
|
|
||||||
|
@ -21,12 +20,6 @@ const generateTableBlock = table => {
|
||||||
const tableBlock = new Component("@budibase/standard-components/tableblock")
|
const tableBlock = new Component("@budibase/standard-components/tableblock")
|
||||||
tableBlock
|
tableBlock
|
||||||
.customProps({
|
.customProps({
|
||||||
linkRows: true,
|
|
||||||
linkURL: `${rowListUrl(table)}/:id`,
|
|
||||||
showAutoColumns: false,
|
|
||||||
showTitleButton: true,
|
|
||||||
titleButtonText: "Create new",
|
|
||||||
titleButtonURL: newRowUrl(table),
|
|
||||||
title: table.name,
|
title: table.name,
|
||||||
dataSource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
|
@ -34,9 +27,14 @@ const generateTableBlock = table => {
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
},
|
},
|
||||||
|
sortOrder: "Ascending",
|
||||||
size: "spectrum--medium",
|
size: "spectrum--medium",
|
||||||
paginate: true,
|
paginate: true,
|
||||||
rowCount: 8,
|
rowCount: 8,
|
||||||
|
clickBehaviour: "details",
|
||||||
|
showTitleButton: true,
|
||||||
|
titleButtonText: "Create row",
|
||||||
|
titleButtonClickBehaviour: "new",
|
||||||
})
|
})
|
||||||
.instanceName(`${table.name} - Table block`)
|
.instanceName(`${table.name} - Table block`)
|
||||||
return tableBlock
|
return tableBlock
|
||||||
|
|
|
@ -1,137 +1,6 @@
|
||||||
import { Component } from "./Component"
|
import { Component } from "./Component"
|
||||||
import { rowListUrl } from "../rowListScreen"
|
|
||||||
import { getSchemaForDatasource } from "../../../dataBinding"
|
import { getSchemaForDatasource } from "../../../dataBinding"
|
||||||
|
|
||||||
export function spectrumColor(number) {
|
|
||||||
// Acorn throws a parsing error in this file if the word g-l-o-b-a-l is found
|
|
||||||
// (without dashes - I can't even type it in a comment).
|
|
||||||
// God knows why. It seems to think optional chaining further down the
|
|
||||||
// file is invalid if the word g-l-o-b-a-l is found - hence the reason this
|
|
||||||
// statement is split into parts.
|
|
||||||
return "var(--spectrum-glo" + `bal-color-gray-${number})`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeLinkComponent(tableName) {
|
|
||||||
return new Component("@budibase/standard-components/link")
|
|
||||||
.text(tableName)
|
|
||||||
.customProps({
|
|
||||||
url: `/${tableName.toLowerCase()}`,
|
|
||||||
openInNewTab: false,
|
|
||||||
color: spectrumColor(700),
|
|
||||||
size: "S",
|
|
||||||
align: "left",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeMainForm() {
|
|
||||||
return new Component("@budibase/standard-components/form")
|
|
||||||
.normalStyle({
|
|
||||||
width: "600px",
|
|
||||||
})
|
|
||||||
.instanceName("Form")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeBreadcrumbContainer(tableName, text) {
|
|
||||||
const link = makeLinkComponent(tableName).instanceName("Back Link")
|
|
||||||
|
|
||||||
const arrowText = new Component("@budibase/standard-components/text")
|
|
||||||
.type("none")
|
|
||||||
.normalStyle({
|
|
||||||
"margin-right": "4px",
|
|
||||||
"margin-left": "4px",
|
|
||||||
})
|
|
||||||
.text(">")
|
|
||||||
.instanceName("Arrow")
|
|
||||||
.customProps({
|
|
||||||
color: spectrumColor(700),
|
|
||||||
size: "S",
|
|
||||||
align: "left",
|
|
||||||
})
|
|
||||||
|
|
||||||
const identifierText = new Component("@budibase/standard-components/text")
|
|
||||||
.text(text)
|
|
||||||
.instanceName("Identifier")
|
|
||||||
.customProps({
|
|
||||||
color: spectrumColor(700),
|
|
||||||
size: "S",
|
|
||||||
align: "left",
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Component("@budibase/standard-components/container")
|
|
||||||
.customProps({
|
|
||||||
gap: "N",
|
|
||||||
direction: "row",
|
|
||||||
hAlign: "left",
|
|
||||||
vAlign: "middle",
|
|
||||||
size: "shrink",
|
|
||||||
})
|
|
||||||
.normalStyle({
|
|
||||||
width: "600px",
|
|
||||||
"margin-right": "auto",
|
|
||||||
"margin-left": "auto",
|
|
||||||
})
|
|
||||||
.instanceName("Breadcrumbs")
|
|
||||||
.addChild(link)
|
|
||||||
.addChild(arrowText)
|
|
||||||
.addChild(identifierText)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeSaveButton(table, formId) {
|
|
||||||
return new Component("@budibase/standard-components/button")
|
|
||||||
.text("Save")
|
|
||||||
.customProps({
|
|
||||||
type: "primary",
|
|
||||||
size: "M",
|
|
||||||
onClick: [
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Validate Form",
|
|
||||||
parameters: {
|
|
||||||
componentId: formId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameters: {
|
|
||||||
providerId: formId,
|
|
||||||
tableId: table._id,
|
|
||||||
},
|
|
||||||
"##eventHandlerType": "Save Row",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameters: {
|
|
||||||
url: rowListUrl(table),
|
|
||||||
},
|
|
||||||
"##eventHandlerType": "Navigate To",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.instanceName("Save Button")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeTitleContainer(title) {
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
|
||||||
.instanceName("Title")
|
|
||||||
.text(title)
|
|
||||||
.customProps({
|
|
||||||
size: "M",
|
|
||||||
align: "left",
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Component("@budibase/standard-components/container")
|
|
||||||
.normalStyle({
|
|
||||||
"margin-top": "32px",
|
|
||||||
"margin-bottom": "32px",
|
|
||||||
})
|
|
||||||
.customProps({
|
|
||||||
direction: "row",
|
|
||||||
hAlign: "stretch",
|
|
||||||
vAlign: "middle",
|
|
||||||
size: "shrink",
|
|
||||||
gap: "M",
|
|
||||||
})
|
|
||||||
.instanceName("Title Container")
|
|
||||||
.addChild(heading)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldTypeToComponentMap = {
|
const fieldTypeToComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Checkbox, Select, Stepper } from "@budibase/bbui"
|
import { Checkbox, Select, RadioGroup, Stepper } from "@budibase/bbui"
|
||||||
import DataSourceSelect from "./controls/DataSourceSelect.svelte"
|
import DataSourceSelect from "./controls/DataSourceSelect.svelte"
|
||||||
import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte"
|
import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte"
|
||||||
import DataProviderSelect from "./controls/DataProviderSelect.svelte"
|
import DataProviderSelect from "./controls/DataProviderSelect.svelte"
|
||||||
|
@ -25,6 +25,7 @@ import BarButtonList from "./controls/BarButtonList.svelte"
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableCombobox,
|
text: DrawerBindableCombobox,
|
||||||
select: Select,
|
select: Select,
|
||||||
|
radio: RadioGroup,
|
||||||
dataSource: DataSourceSelect,
|
dataSource: DataSourceSelect,
|
||||||
"dataSource/s3": S3DataSourceSelect,
|
"dataSource/s3": S3DataSourceSelect,
|
||||||
dataProvider: DataProviderSelect,
|
dataProvider: DataProviderSelect,
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="root">This action doesn't require any settings.</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label } from "@budibase/bbui"
|
||||||
|
import { selectedScreen } from "builderStore"
|
||||||
|
import { findAllMatchingComponents } from "builderStore/componentUtils"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
|
||||||
|
$: sidePanelOptions = getSidePanelOptions($selectedScreen)
|
||||||
|
|
||||||
|
const getSidePanelOptions = screen => {
|
||||||
|
const sidePanelComponents = findAllMatchingComponents(
|
||||||
|
screen.props,
|
||||||
|
component => component._component.endsWith("/sidepanel")
|
||||||
|
)
|
||||||
|
return sidePanelComponents.map(sidePanel => ({
|
||||||
|
label: sidePanel._instanceName,
|
||||||
|
value: sidePanel._id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label small>Side Panel</Label>
|
||||||
|
<Select bind:value={parameters.id} options={sidePanelOptions} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -16,3 +16,5 @@ export { default as ExportData } from "./ExportData.svelte"
|
||||||
export { default as ContinueIf } from "./ContinueIf.svelte"
|
export { default as ContinueIf } from "./ContinueIf.svelte"
|
||||||
export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte"
|
export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte"
|
||||||
export { default as ShowNotification } from "./ShowNotification.svelte"
|
export { default as ShowNotification } from "./ShowNotification.svelte"
|
||||||
|
export { default as OpenSidePanel } from "./OpenSidePanel.svelte"
|
||||||
|
export { default as CloseSidePanel } from "./CloseSidePanel.svelte"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as ActionComponents from "./actions"
|
import * as ActionComponents from "./actions"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
// @ts-ignore
|
||||||
import ActionDefinitions from "./manifest.json"
|
import ActionDefinitions from "./manifest.json"
|
||||||
|
|
||||||
// Defines which actions are available to configure in the front end.
|
// Defines which actions are available to configure in the front end.
|
||||||
|
|
|
@ -116,6 +116,18 @@
|
||||||
"type": "application",
|
"type": "application",
|
||||||
"component": "ShowNotification",
|
"component": "ShowNotification",
|
||||||
"dependsOnFeature": "showNotificationAction"
|
"dependsOnFeature": "showNotificationAction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Open Side Panel",
|
||||||
|
"type": "application",
|
||||||
|
"component": "OpenSidePanel",
|
||||||
|
"dependsOnFeature": "sidePanel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Close Side Panel",
|
||||||
|
"type": "application",
|
||||||
|
"component": "CloseSidePanel",
|
||||||
|
"dependsOnFeature": "sidePanel"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script>
|
||||||
|
import { DetailSummary, Icon } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let componentDefinition
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DetailSummary collapsible={false}>
|
||||||
|
<div class="info">
|
||||||
|
<div class="title">
|
||||||
|
<Icon name="HelpOutline" />
|
||||||
|
{componentDefinition.name}
|
||||||
|
</div>
|
||||||
|
{componentDefinition.info}
|
||||||
|
</div>
|
||||||
|
</DetailSummary>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
padding: var(--spacing-m) var(--spacing-l) var(--spacing-l) var(--spacing-l);
|
||||||
|
background-color: var(--background-alt);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,6 +5,7 @@
|
||||||
import DesignSection from "./DesignSection.svelte"
|
import DesignSection from "./DesignSection.svelte"
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||||
|
import ComponentInfoSection from "./ComponentInfoSection.svelte"
|
||||||
import {
|
import {
|
||||||
getBindableProperties,
|
getBindableProperties,
|
||||||
getComponentBindableProperties,
|
getComponentBindableProperties,
|
||||||
|
@ -29,6 +30,9 @@
|
||||||
{#if $selectedComponent}
|
{#if $selectedComponent}
|
||||||
{#key $selectedComponent._id}
|
{#key $selectedComponent._id}
|
||||||
<Panel {title} icon={componentDefinition?.icon} borderLeft>
|
<Panel {title} icon={componentDefinition?.icon} borderLeft>
|
||||||
|
{#if componentDefinition.info}
|
||||||
|
<ComponentInfoSection {componentDefinition} />
|
||||||
|
{/if}
|
||||||
<ComponentSettingsSection
|
<ComponentSettingsSection
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte"
|
import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte"
|
||||||
import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte"
|
import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte"
|
||||||
import { getComponentForSetting } from "components/design/settings/componentSettings"
|
import { getComponentForSetting } from "components/design/settings/componentSettings"
|
||||||
|
import analytics, { Events } from "analytics"
|
||||||
|
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
@ -36,15 +37,26 @@
|
||||||
section.settings.forEach(setting => {
|
section.settings.forEach(setting => {
|
||||||
setting.visible = canRenderControl(instance, setting, isScreen)
|
setting.visible = canRenderControl(instance, setting, isScreen)
|
||||||
})
|
})
|
||||||
section.visible = section.settings.some(setting => setting.visible)
|
section.visible =
|
||||||
|
section.name === "General" ||
|
||||||
|
section.settings.some(setting => setting.visible)
|
||||||
})
|
})
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSetting = async (key, value) => {
|
const updateSetting = async (setting, value) => {
|
||||||
try {
|
try {
|
||||||
await store.actions.components.updateSetting(key, value)
|
await store.actions.components.updateSetting(setting.key, value)
|
||||||
|
|
||||||
|
// Send event if required
|
||||||
|
if (setting.sendEvents) {
|
||||||
|
analytics.captureEvent(Events.COMPONENT_UPDATED, {
|
||||||
|
name: componentInstance._component,
|
||||||
|
setting: setting.key,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error updating component prop")
|
notifications.error("Error updating component prop")
|
||||||
}
|
}
|
||||||
|
@ -111,7 +123,7 @@
|
||||||
label="Name"
|
label="Name"
|
||||||
key="_instanceName"
|
key="_instanceName"
|
||||||
value={componentInstance._instanceName}
|
value={componentInstance._instanceName}
|
||||||
onChange={val => updateSetting("_instanceName", val)}
|
onChange={val => updateSetting({ key: "_instanceName" }, val)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#each section.settings as setting (setting.key)}
|
{#each section.settings as setting (setting.key)}
|
||||||
|
@ -124,7 +136,7 @@
|
||||||
value={componentInstance[setting.key]}
|
value={componentInstance[setting.key]}
|
||||||
defaultValue={setting.defaultValue}
|
defaultValue={setting.defaultValue}
|
||||||
nested={setting.nested}
|
nested={setting.nested}
|
||||||
onChange={val => updateSetting(setting.key, val)}
|
onChange={val => updateSetting(setting, val)}
|
||||||
highlighted={$store.highlightedSettingKey === setting.key}
|
highlighted={$store.highlightedSettingKey === setting.key}
|
||||||
info={setting.info}
|
info={setting.info}
|
||||||
props={{
|
props={{
|
||||||
|
@ -148,9 +160,11 @@
|
||||||
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||||
<ResetFieldsButton {componentInstance} />
|
<ResetFieldsButton {componentInstance} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if idx === 0 && componentDefinition?.block}
|
|
||||||
<EjectBlockButton />
|
|
||||||
{/if}
|
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if componentDefinition?.block}
|
||||||
|
<DetailSummary name="Eject" collapsible={false}>
|
||||||
|
<EjectBlockButton />
|
||||||
|
</DetailSummary>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"children": [
|
"children": [
|
||||||
"container",
|
"container",
|
||||||
"section",
|
"section",
|
||||||
"grid"
|
"grid",
|
||||||
|
"sidepanel"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
<span data-cy="data-source-modal">
|
<span data-cy="data-source-modal">
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Create CRUD Screens"
|
title="Autogenerated screens"
|
||||||
confirmText="Confirm"
|
confirmText="Confirm"
|
||||||
cancelText="Back"
|
cancelText="Back"
|
||||||
onConfirm={confirmDatasourceSelection}
|
onConfirm={confirmDatasourceSelection}
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
size="L"
|
size="L"
|
||||||
>
|
>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Select which datasource you would like to use to create your screens
|
Select which datasources you would like to use to create your screens
|
||||||
</Body>
|
</Body>
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
{#each filteredSources as datasource}
|
{#each filteredSources as datasource}
|
||||||
|
|
|
@ -40,9 +40,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={"Create CRUD Screens"}
|
title="Autogenerated screens"
|
||||||
confirmText={"Done"}
|
confirmText="Done"
|
||||||
cancelText={"Back"}
|
cancelText="Back"
|
||||||
{onConfirm}
|
{onConfirm}
|
||||||
{onCancel}
|
{onCancel}
|
||||||
disabled={!!error}
|
disabled={!!error}
|
||||||
|
|
|
@ -127,9 +127,6 @@
|
||||||
|
|
||||||
// Handler for Datasource Screen Creation
|
// Handler for Datasource Screen Creation
|
||||||
const completeDatasourceScreenCreation = async () => {
|
const completeDatasourceScreenCreation = async () => {
|
||||||
// // Handle template selection
|
|
||||||
if (selectedTemplates?.length > 1) {
|
|
||||||
// Autoscreens, so create immediately
|
|
||||||
const screens = selectedTemplates.map(template => {
|
const screens = selectedTemplates.map(template => {
|
||||||
let screenTemplate = template.create()
|
let screenTemplate = template.create()
|
||||||
screenTemplate.datasource = template.datasource
|
screenTemplate.datasource = template.datasource
|
||||||
|
@ -138,7 +135,6 @@
|
||||||
})
|
})
|
||||||
await createScreens({ screens, screenAccessRole })
|
await createScreens({ screens, screenAccessRole })
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const confirmScreenBlank = async ({ screenUrl }) => {
|
const confirmScreenBlank = async ({ screenUrl }) => {
|
||||||
blankScreenUrl = screenUrl
|
blankScreenUrl = screenUrl
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout noPadding justifyItems="center">
|
<Layout noPadding justifyItems="center">
|
||||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<Heading>Sign in to {company}</Heading>
|
<Heading textAlign="center">Sign in to {company}</Heading>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<GoogleButton />
|
<GoogleButton />
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"messagePassing": true,
|
"messagePassing": true,
|
||||||
"rowSelection": true,
|
"rowSelection": true,
|
||||||
"continueIfAction": true,
|
"continueIfAction": true,
|
||||||
"showNotificationAction": true
|
"showNotificationAction": true,
|
||||||
|
"sidePanel": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -3669,7 +3670,7 @@
|
||||||
"Ascending",
|
"Ascending",
|
||||||
"Descending"
|
"Descending"
|
||||||
],
|
],
|
||||||
"defaultValue": "Descending"
|
"defaultValue": "Ascending"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -3736,12 +3737,6 @@
|
||||||
"key": "dataProvider",
|
"key": "dataProvider",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"label": "Row count",
|
|
||||||
"key": "rowCount",
|
|
||||||
"defaultValue": 8
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "columns",
|
"type": "columns",
|
||||||
"label": "Columns",
|
"label": "Columns",
|
||||||
|
@ -3765,6 +3760,12 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Row count",
|
||||||
|
"key": "rowCount",
|
||||||
|
"defaultValue": 8
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Quiet",
|
"label": "Quiet",
|
||||||
|
@ -3775,12 +3776,6 @@
|
||||||
"label": "Compact",
|
"label": "Compact",
|
||||||
"key": "compact"
|
"key": "compact"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Show auto columns",
|
|
||||||
"key": "showAutoColumns",
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Allow row selection",
|
"label": "Allow row selection",
|
||||||
|
@ -3788,30 +3783,20 @@
|
||||||
"defaultValue": false,
|
"defaultValue": false,
|
||||||
"info": "Row selection is only compatible with internal or SQL tables"
|
"info": "Row selection is only compatible with internal or SQL tables"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Link table rows",
|
|
||||||
"key": "linkRows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Open link screens in modal",
|
|
||||||
"key": "linkPeek"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "url",
|
|
||||||
"label": "Link screen",
|
|
||||||
"key": "linkURL"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Advanced",
|
"name": "On Row Click",
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "event",
|
||||||
"label": "ID column for linking (appended to URL)",
|
"key": "onClick",
|
||||||
"key": "linkColumn",
|
"context": [
|
||||||
"placeholder": "Default"
|
{
|
||||||
|
"label": "Clicked row",
|
||||||
|
"key": "row"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -4463,6 +4448,10 @@
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Table",
|
||||||
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataSource",
|
"type": "dataSource",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
|
@ -4470,20 +4459,16 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "searchfield",
|
"type": "columns",
|
||||||
"label": "Search Columns",
|
"label": "Table Columns",
|
||||||
"key": "searchColumns",
|
"key": "tableColumns",
|
||||||
"placeholder": "Choose search columns",
|
"dependsOn": "dataSource",
|
||||||
"info": "Only the first 5 search columns will be used"
|
"placeholder": "All columns",
|
||||||
},
|
"nested": true
|
||||||
{
|
|
||||||
"type": "filter",
|
|
||||||
"label": "Filtering",
|
|
||||||
"key": "filter"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field/sortable",
|
"type": "field/sortable",
|
||||||
"label": "Sort Column",
|
"label": "Sort By",
|
||||||
"key": "sortColumn"
|
"key": "sortColumn"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4494,7 +4479,7 @@
|
||||||
"Ascending",
|
"Ascending",
|
||||||
"Descending"
|
"Descending"
|
||||||
],
|
],
|
||||||
"defaultValue": "Descending"
|
"defaultValue": "Ascending"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -4512,16 +4497,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Paginate",
|
|
||||||
"key": "paginate",
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"section": true,
|
|
||||||
"name": "Table",
|
|
||||||
"settings": [
|
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"label": "Scroll Limit",
|
"label": "Scroll Limit",
|
||||||
|
@ -4529,16 +4504,14 @@
|
||||||
"defaultValue": 8
|
"defaultValue": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "columns",
|
"type": "boolean",
|
||||||
"label": "Table Columns",
|
"label": "Paginate",
|
||||||
"key": "tableColumns",
|
"key": "paginate",
|
||||||
"dependsOn": "dataSource",
|
"defaultValue": true
|
||||||
"placeholder": "All columns",
|
|
||||||
"nested": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Quiet table variant",
|
"label": "Quiet",
|
||||||
"key": "quiet"
|
"key": "quiet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4546,11 +4519,6 @@
|
||||||
"label": "Compact",
|
"label": "Compact",
|
||||||
"key": "compact"
|
"key": "compact"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Show auto columns",
|
|
||||||
"key": "showAutoColumns"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Allow row selection",
|
"label": "Allow row selection",
|
||||||
|
@ -4558,58 +4526,100 @@
|
||||||
"info": "Row selection is only compatible with internal or SQL tables"
|
"info": "Row selection is only compatible with internal or SQL tables"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "filter",
|
||||||
"label": "Link table rows",
|
"label": "Filtering",
|
||||||
"key": "linkRows"
|
"key": "filter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "searchfield",
|
||||||
"label": "Open link in modal",
|
"label": "Search Fields",
|
||||||
"key": "linkPeek"
|
"key": "searchColumns",
|
||||||
},
|
"placeholder": "Choose search fields",
|
||||||
{
|
"info": "Only the first 5 search fields will be used"
|
||||||
"type": "url",
|
|
||||||
"label": "Link screen",
|
|
||||||
"key": "linkURL"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Title button",
|
"name": "On row click",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "radio",
|
||||||
|
"key": "clickBehaviour",
|
||||||
|
"sendEvents": true,
|
||||||
|
"defaultValue": "actions",
|
||||||
|
"info": "Details side panel is only compatible with internal or SQL tables",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Run actions",
|
||||||
|
"value": "actions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Show details side panel",
|
||||||
|
"value": "details"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"key": "onClick",
|
||||||
|
"nested": true,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "clickBehaviour",
|
||||||
|
"value": "actions"
|
||||||
|
},
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Clicked row",
|
||||||
|
"key": "row"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Button",
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "showTitleButton",
|
"key": "showTitleButton",
|
||||||
"label": "Show link button",
|
"label": "Show button above table",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Open link in modal",
|
|
||||||
"key": "titleButtonPeek"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "titleButtonText",
|
"key": "titleButtonText",
|
||||||
"label": "Button text"
|
"label": "Text",
|
||||||
|
"defaultValue": "Create row",
|
||||||
|
"dependsOn": "showTitleButton"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "url",
|
"type": "radio",
|
||||||
"label": "Button link",
|
"key": "titleButtonClickBehaviour",
|
||||||
"key": "titleButtonURL"
|
"label": "On Click",
|
||||||
|
"dependsOn": "showTitleButton",
|
||||||
|
"defaultValue": "actions",
|
||||||
|
"info": "New row side panel is only compatible with internal or SQL tables",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Run actions",
|
||||||
|
"value": "actions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Show new row side panel",
|
||||||
|
"value": "new"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"type": "event",
|
||||||
"name": "Advanced",
|
"key": "onClickTitleButton",
|
||||||
"settings": [
|
"nested": true,
|
||||||
{
|
"dependsOn": {
|
||||||
"type": "field",
|
"setting": "titleButtonClickBehaviour",
|
||||||
"label": "ID column for linking (appended to URL)",
|
"value": "actions"
|
||||||
"key": "linkColumn",
|
}
|
||||||
"placeholder": "Default"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -5189,6 +5199,17 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"sidepanel": {
|
||||||
|
"name": "Side Panel",
|
||||||
|
"icon": "RailRight",
|
||||||
|
"hasChildren": true,
|
||||||
|
"illegalChildren": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"showEmptyState": false,
|
||||||
|
"draggable": false,
|
||||||
|
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action."
|
||||||
|
},
|
||||||
"rowexplorer": {
|
"rowexplorer": {
|
||||||
"block": true,
|
"block": true,
|
||||||
"name": "Row Explorer Block",
|
"name": "Row Explorer Block",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
export let props
|
export let props
|
||||||
export let styles
|
export let styles
|
||||||
export let context
|
export let context
|
||||||
|
export let name
|
||||||
export let order = 0
|
export let order = 0
|
||||||
export let containsSlot = false
|
export let containsSlot = false
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
_blockElementHasChildren: $$slots?.default ?? false,
|
_blockElementHasChildren: $$slots?.default ?? false,
|
||||||
_component: `@budibase/standard-components/${type}`,
|
_component: `@budibase/standard-components/${type}`,
|
||||||
_id: id,
|
_id: id,
|
||||||
_instanceName: type[0].toUpperCase() + type.slice(1),
|
_instanceName: name || type[0].toUpperCase() + type.slice(1),
|
||||||
_styles: {
|
_styles: {
|
||||||
...styles,
|
...styles,
|
||||||
normal: styles?.normal || {},
|
normal: styles?.normal || {},
|
||||||
|
|
|
@ -190,6 +190,7 @@
|
||||||
},
|
},
|
||||||
empty: emptyState,
|
empty: emptyState,
|
||||||
selected,
|
selected,
|
||||||
|
inSelectedPath,
|
||||||
name,
|
name,
|
||||||
editing,
|
editing,
|
||||||
type: instance._component,
|
type: instance._component,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { Heading, Icon } from "@budibase/bbui"
|
import { Heading, Icon, clickOutside } from "@budibase/bbui"
|
||||||
import { FieldTypes } from "constants"
|
import { FieldTypes } from "constants"
|
||||||
import active from "svelte-spa-router/active"
|
import active from "svelte-spa-router/active"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
builderStore,
|
builderStore,
|
||||||
currentRole,
|
currentRole,
|
||||||
environmentStore,
|
environmentStore,
|
||||||
|
sidePanelStore,
|
||||||
} = sdk
|
} = sdk
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
$context.device.width,
|
$context.device.width,
|
||||||
$context.device.height
|
$context.device.height
|
||||||
)
|
)
|
||||||
|
$: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open
|
||||||
|
|
||||||
// Scroll navigation into view if selected
|
// Scroll navigation into view if selected
|
||||||
$: {
|
$: {
|
||||||
|
@ -150,6 +152,7 @@
|
||||||
class:desktop={!mobile}
|
class:desktop={!mobile}
|
||||||
class:mobile={!!mobile}
|
class:mobile={!!mobile}
|
||||||
>
|
>
|
||||||
|
<div class="layout-body">
|
||||||
{#if typeClass !== "none"}
|
{#if typeClass !== "none"}
|
||||||
<div
|
<div
|
||||||
class="interactive component navigation"
|
class="interactive component navigation"
|
||||||
|
@ -244,19 +247,46 @@
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="side-panel-container"
|
||||||
|
class:open={$sidePanelStore.open}
|
||||||
|
use:clickOutside={autoCloseSidePanel ? sidePanelStore.actions.close : null}
|
||||||
|
class:builder={$builderStore.inBuilder}
|
||||||
|
>
|
||||||
|
<div class="side-panel-header">
|
||||||
|
<Icon
|
||||||
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
|
name="RailRightClose"
|
||||||
|
hoverable
|
||||||
|
on:click={sidePanelStore.actions.close}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Main components */
|
/* Main components */
|
||||||
|
.layout {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
z-index: 1;
|
||||||
|
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
.component {
|
.component {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
.layout {
|
.layout-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
height: 100%;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -316,6 +346,43 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#side-panel-container {
|
||||||
|
max-width: calc(100vw - 40px);
|
||||||
|
background: var(--spectrum-global-color-gray-50);
|
||||||
|
z-index: 3;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: transform 130ms ease-out;
|
||||||
|
position: absolute;
|
||||||
|
width: 400px;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#side-panel-container.builder {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#side-panel-container.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
#side-panel-container.builder.open {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
.side-panel-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
|
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
|
||||||
|
getContext("sdk")
|
||||||
|
|
||||||
|
// Automatically show and hide the side panel when inside the builder.
|
||||||
|
// For some unknown reason, svelte reactivity breaks if we reference the
|
||||||
|
// reactive variable "open" inside the following expression, or if we define
|
||||||
|
// "open" above this expression.
|
||||||
|
$: {
|
||||||
|
if ($builderStore.inBuilder) {
|
||||||
|
if (
|
||||||
|
$component.inSelectedPath &&
|
||||||
|
$sidePanelStore.contentId !== $component.id
|
||||||
|
) {
|
||||||
|
sidePanelStore.actions.open($component.id)
|
||||||
|
} else if (
|
||||||
|
!$component.inSelectedPath &&
|
||||||
|
$sidePanelStore.contentId === $component.id &&
|
||||||
|
!$dndIsDragging
|
||||||
|
) {
|
||||||
|
sidePanelStore.actions.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive visibility
|
||||||
|
$: open = $sidePanelStore.contentId === $component.id
|
||||||
|
|
||||||
|
const showInSidePanel = (el, visible) => {
|
||||||
|
const update = visible => {
|
||||||
|
const target = document.getElementById("side-panel-container")
|
||||||
|
const node = el
|
||||||
|
if (visible) {
|
||||||
|
if (!target.contains(node)) {
|
||||||
|
target.appendChild(node)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (target.contains(node)) {
|
||||||
|
target.removeChild(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply initial visibility
|
||||||
|
update(visible)
|
||||||
|
|
||||||
|
return { update }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
use:styleable={$component.styles}
|
||||||
|
use:showInSidePanel={open}
|
||||||
|
class="side-panel"
|
||||||
|
class:open
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.side-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.side-panel.open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.side-panel :global(.component > *) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { generate } from "shortid"
|
||||||
import Block from "components/Block.svelte"
|
import Block from "components/Block.svelte"
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
@ -13,50 +14,103 @@
|
||||||
export let sortOrder
|
export let sortOrder
|
||||||
export let paginate
|
export let paginate
|
||||||
export let tableColumns
|
export let tableColumns
|
||||||
export let showAutoColumns
|
|
||||||
export let rowCount
|
export let rowCount
|
||||||
export let quiet
|
export let quiet
|
||||||
export let compact
|
export let compact
|
||||||
export let size
|
export let size
|
||||||
export let allowSelectRows
|
export let allowSelectRows
|
||||||
export let linkRows
|
export let clickBehaviour
|
||||||
export let linkURL
|
export let onClick
|
||||||
export let linkColumn
|
|
||||||
export let linkPeek
|
|
||||||
export let showTitleButton
|
export let showTitleButton
|
||||||
export let titleButtonText
|
export let titleButtonText
|
||||||
export let titleButtonURL
|
export let titleButtonClickBehaviour
|
||||||
export let titleButtonPeek
|
export let onClickTitleButton
|
||||||
|
|
||||||
const { fetchDatasourceSchema } = getContext("sdk")
|
const { fetchDatasourceSchema, API } = getContext("sdk")
|
||||||
|
const stateKey = `ID_${generate()}`
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
let detailsFormBlockId
|
||||||
|
let detailsSidePanelId
|
||||||
|
let newRowSidePanelId
|
||||||
let schema
|
let schema
|
||||||
|
let primaryDisplay
|
||||||
let schemaLoaded = false
|
let schemaLoaded = false
|
||||||
|
|
||||||
$: fetchSchema(dataSource)
|
$: fetchSchema(dataSource)
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
$: titleButtonAction = [
|
$: editTitle = getEditTitle(detailsFormBlockId, primaryDisplay)
|
||||||
|
$: normalFields = getNormalFields(schema)
|
||||||
|
$: rowClickActions =
|
||||||
|
clickBehaviour === "actions" || dataSource?.type !== "table"
|
||||||
|
? onClick
|
||||||
|
: [
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Navigate To",
|
id: 0,
|
||||||
|
"##eventHandlerType": "Update State",
|
||||||
parameters: {
|
parameters: {
|
||||||
peek: titleButtonPeek,
|
key: stateKey,
|
||||||
url: titleButtonURL,
|
type: "set",
|
||||||
|
persist: false,
|
||||||
|
value: `{{ ${safe("eventContext")}.${safe("row")}._id }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
"##eventHandlerType": "Open Side Panel",
|
||||||
|
parameters: {
|
||||||
|
id: detailsSidePanelId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
$: buttonClickActions =
|
||||||
|
clickBehaviour === "actions" || dataSource?.type !== "table"
|
||||||
|
? onClickTitleButton
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
"##eventHandlerType": "Open Side Panel",
|
||||||
|
parameters: {
|
||||||
|
id: newRowSidePanelId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Load the datasource schema so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
const fetchSchema = async dataSource => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource?.type === "table") {
|
||||||
|
const definition = await API.fetchTableDefinition(dataSource?.tableId)
|
||||||
|
schema = definition.schema
|
||||||
|
primaryDisplay = definition.primaryDisplay
|
||||||
|
} else if (dataSource) {
|
||||||
schema = await fetchDatasourceSchema(dataSource, {
|
schema = await fetchDatasourceSchema(dataSource, {
|
||||||
enrichRelationships: true,
|
enrichRelationships: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
schemaLoaded = true
|
schemaLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNormalFields = schema => {
|
||||||
|
if (!schema) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Object.entries(schema)
|
||||||
|
.filter(entry => {
|
||||||
|
return !entry[1].autocolumn
|
||||||
|
})
|
||||||
|
.map(entry => entry[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEditTitle = (detailsFormBlockId, primaryDisplay) => {
|
||||||
|
if (!primaryDisplay || !detailsFormBlockId) {
|
||||||
|
return "Edit"
|
||||||
|
}
|
||||||
|
const prefix = safe(detailsFormBlockId + "-repeater")
|
||||||
|
const binding = `${prefix}.${safe(primaryDisplay)}`
|
||||||
|
return `{{#if ${binding}}}{{${binding}}}{{else}}Details{{/if}}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if schemaLoaded}
|
{#if schemaLoaded}
|
||||||
|
@ -129,7 +183,7 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="button"
|
type="button"
|
||||||
props={{
|
props={{
|
||||||
onClick: titleButtonAction,
|
onClick: buttonClickActions,
|
||||||
text: titleButtonText,
|
text: titleButtonText,
|
||||||
type: "cta",
|
type: "cta",
|
||||||
}}
|
}}
|
||||||
|
@ -145,7 +199,7 @@
|
||||||
props={{
|
props={{
|
||||||
dataSource,
|
dataSource,
|
||||||
filter: enrichedFilter,
|
filter: enrichedFilter,
|
||||||
sortColumn,
|
sortColumn: sortColumn || primaryDisplay,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
paginate,
|
paginate,
|
||||||
limit: rowCount,
|
limit: rowCount,
|
||||||
|
@ -158,19 +212,63 @@
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||||
columns: tableColumns,
|
columns: tableColumns,
|
||||||
showAutoColumns,
|
|
||||||
rowCount,
|
rowCount,
|
||||||
quiet,
|
quiet,
|
||||||
compact,
|
compact,
|
||||||
allowSelectRows,
|
allowSelectRows,
|
||||||
size,
|
size,
|
||||||
linkRows,
|
onClick: rowClickActions,
|
||||||
linkURL,
|
|
||||||
linkColumn,
|
|
||||||
linkPeek,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
{#if clickBehaviour === "details"}
|
||||||
|
<BlockComponent
|
||||||
|
name="Details side panel"
|
||||||
|
type="sidepanel"
|
||||||
|
bind:id={detailsSidePanelId}
|
||||||
|
context="details-side-panel"
|
||||||
|
order={2}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
name="Details form block"
|
||||||
|
type="formblock"
|
||||||
|
bind:id={detailsFormBlockId}
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
showSaveButton: true,
|
||||||
|
showDeleteButton: true,
|
||||||
|
actionType: "Update",
|
||||||
|
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||||
|
fields: normalFields,
|
||||||
|
title: editTitle,
|
||||||
|
labelPosition: "left",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
|
{#if showTitleButton && titleButtonClickBehaviour === "new"}
|
||||||
|
<BlockComponent
|
||||||
|
name="New row side panel"
|
||||||
|
type="sidepanel"
|
||||||
|
bind:id={newRowSidePanelId}
|
||||||
|
context="new-side-panel"
|
||||||
|
order={3}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
name="New row form block"
|
||||||
|
type="formblock"
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
showSaveButton: true,
|
||||||
|
showDeleteButton: false,
|
||||||
|
actionType: "Create",
|
||||||
|
fields: normalFields,
|
||||||
|
title: "Create Row",
|
||||||
|
labelPosition: "left",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
</Block>
|
</Block>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -43,6 +43,20 @@
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
// Clear a create form once submitted
|
||||||
|
...(actionType !== "Create"
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Clear Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Navigate To",
|
"##eventHandlerType": "Navigate To",
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -63,6 +77,9 @@
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Navigate To",
|
"##eventHandlerType": "Navigate To",
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
|
|
||||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||||
$: resetKey = Helpers.hashString(
|
$: resetKey = Helpers.hashString(
|
||||||
JSON.stringify(initialValues) + JSON.stringify(schema) + disabled
|
JSON.stringify(initialValues) + JSON.stringify(dataSource) + disabled
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,10 @@ export { default as tag } from "./Tag.svelte"
|
||||||
export { default as markdownviewer } from "./MarkdownViewer.svelte"
|
export { default as markdownviewer } from "./MarkdownViewer.svelte"
|
||||||
export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
|
export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
|
||||||
export { default as grid } from "./Grid.svelte"
|
export { default as grid } from "./Grid.svelte"
|
||||||
|
export { default as sidepanel } from "./SidePanel.svelte"
|
||||||
export * from "./charts"
|
export * from "./charts"
|
||||||
export * from "./forms"
|
export * from "./forms"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
|
|
||||||
export * from "./blocks"
|
export * from "./blocks"
|
||||||
export * from "./dynamic-filter"
|
export * from "./dynamic-filter"
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,16 @@
|
||||||
|
|
||||||
export let dataProvider
|
export let dataProvider
|
||||||
export let columns
|
export let columns
|
||||||
export let showAutoColumns
|
|
||||||
export let rowCount
|
export let rowCount
|
||||||
export let quiet
|
export let quiet
|
||||||
export let size
|
export let size
|
||||||
export let linkRows
|
|
||||||
export let linkURL
|
|
||||||
export let linkColumn
|
|
||||||
export let linkPeek
|
|
||||||
export let allowSelectRows
|
export let allowSelectRows
|
||||||
export let compact
|
export let compact
|
||||||
|
export let onClick
|
||||||
|
|
||||||
const loading = getContext("loading")
|
const loading = getContext("loading")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, getAction, ActionTypes, routeStore, rowSelectionStore } =
|
const { styleable, getAction, ActionTypes, rowSelectionStore } =
|
||||||
getContext("sdk")
|
getContext("sdk")
|
||||||
const customColumnKey = `custom-${Math.random()}`
|
const customColumnKey = `custom-${Math.random()}`
|
||||||
const customRenderers = [
|
const customRenderers = [
|
||||||
|
@ -29,18 +25,19 @@
|
||||||
component: SlotRenderer,
|
component: SlotRenderer,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
|
|
||||||
$: hasChildren = $component.children
|
$: hasChildren = $component.children
|
||||||
$: data = dataProvider?.rows || []
|
$: data = dataProvider?.rows || []
|
||||||
$: fullSchema = dataProvider?.schema ?? {}
|
$: fullSchema = dataProvider?.schema ?? {}
|
||||||
$: fields = getFields(fullSchema, columns, showAutoColumns)
|
$: fields = getFields(fullSchema, columns, false)
|
||||||
$: schema = getFilteredSchema(fullSchema, fields, hasChildren)
|
$: schema = getFilteredSchema(fullSchema, fields, hasChildren)
|
||||||
$: setSorting = getAction(
|
$: setSorting = getAction(
|
||||||
dataProvider?.id,
|
dataProvider?.id,
|
||||||
ActionTypes.SetDataProviderSorting
|
ActionTypes.SetDataProviderSorting
|
||||||
)
|
)
|
||||||
$: table = dataProvider?.datasource?.type === "table"
|
$: table = dataProvider?.datasource?.type === "table"
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
rowSelectionStore.actions.updateSelection(
|
rowSelectionStore.actions.updateSelection(
|
||||||
$component.id,
|
$component.id,
|
||||||
|
@ -118,17 +115,10 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = e => {
|
const handleClick = e => {
|
||||||
if (!linkRows || !linkURL) {
|
if (onClick) {
|
||||||
return
|
onClick({ row: e.detail })
|
||||||
}
|
}
|
||||||
const col = linkColumn || "_id"
|
|
||||||
const id = e.detail?.[col]
|
|
||||||
if (!id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const split = linkURL.split("/:")
|
|
||||||
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
@ -153,7 +143,7 @@
|
||||||
disableSorting
|
disableSorting
|
||||||
autoSortColumns={!columns?.length}
|
autoSortColumns={!columns?.length}
|
||||||
on:sort={onSort}
|
on:sort={onSort}
|
||||||
on:click={onClick}
|
on:click={handleClick}
|
||||||
>
|
>
|
||||||
<div class="skeleton" slot="loadingIndicator">
|
<div class="skeleton" slot="loadingIndicator">
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
|
|
|
@ -36,8 +36,7 @@
|
||||||
|
|
||||||
// Util to get the inner DOM node by a component ID
|
// Util to get the inner DOM node by a component ID
|
||||||
const getDOMNode = id => {
|
const getDOMNode = id => {
|
||||||
const component = document.getElementsByClassName(id)[0]
|
return document.getElementsByClassName(`${id}-dom`)[0]
|
||||||
return [...component.children][0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util to calculate the variance of a set of data
|
// Util to calculate the variance of a set of data
|
||||||
|
|
|
@ -43,7 +43,8 @@
|
||||||
if (callbackCount >= observers.length) {
|
if (callbackCount >= observers.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nextIndicators[idx].visible = entries[0].isIntersecting
|
nextIndicators[idx].visible =
|
||||||
|
nextIndicators[idx].isSidePanel || entries[0].isIntersecting
|
||||||
if (++callbackCount === observers.length) {
|
if (++callbackCount === observers.length) {
|
||||||
indicators = nextIndicators
|
indicators = nextIndicators
|
||||||
updating = false
|
updating = false
|
||||||
|
@ -91,8 +92,9 @@
|
||||||
|
|
||||||
// Extract valid children
|
// Extract valid children
|
||||||
// Sanity limit of 100 active indicators
|
// Sanity limit of 100 active indicators
|
||||||
const children = Array.from(parents)
|
const children = Array.from(
|
||||||
.map(parent => parent?.children?.[0])
|
document.getElementsByClassName(`${componentId}-dom`)
|
||||||
|
)
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
.slice(0, 100)
|
.slice(0, 100)
|
||||||
|
|
||||||
|
@ -121,6 +123,7 @@
|
||||||
width: elBounds.width + 4,
|
width: elBounds.width + 4,
|
||||||
height: elBounds.height + 4,
|
height: elBounds.height + 4,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
isSidePanel: child.classList.contains("side-panel"),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
componentStore,
|
componentStore,
|
||||||
currentRole,
|
currentRole,
|
||||||
environmentStore,
|
environmentStore,
|
||||||
|
sidePanelStore,
|
||||||
|
dndIsDragging,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { styleable } from "utils/styleable"
|
import { styleable } from "utils/styleable"
|
||||||
import { linkable } from "utils/linkable"
|
import { linkable } from "utils/linkable"
|
||||||
|
@ -30,6 +32,8 @@ export default {
|
||||||
uploadStore,
|
uploadStore,
|
||||||
componentStore,
|
componentStore,
|
||||||
environmentStore,
|
environmentStore,
|
||||||
|
sidePanelStore,
|
||||||
|
dndIsDragging,
|
||||||
currentRole,
|
currentRole,
|
||||||
styleable,
|
styleable,
|
||||||
linkable,
|
linkable,
|
||||||
|
|
|
@ -24,6 +24,7 @@ export {
|
||||||
dndIsNewComponent,
|
dndIsNewComponent,
|
||||||
dndIsDragging,
|
dndIsDragging,
|
||||||
} from "./dnd"
|
} from "./dnd"
|
||||||
|
export { sidePanelStore } from "./sidePanel.js"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { writable, derived } from "svelte/store"
|
||||||
|
|
||||||
|
export const createSidePanelStore = () => {
|
||||||
|
const initialState = {
|
||||||
|
contentId: null,
|
||||||
|
}
|
||||||
|
const store = writable(initialState)
|
||||||
|
const derivedStore = derived(store, $store => {
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
open: $store.contentId != null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = id => {
|
||||||
|
store.update(state => {
|
||||||
|
state.contentId = id
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const close = () => {
|
||||||
|
store.update(state => {
|
||||||
|
state.contentId = null
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: derivedStore.subscribe,
|
||||||
|
actions: {
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sidePanelStore = createSidePanelStore()
|
|
@ -10,6 +10,7 @@ import {
|
||||||
dataSourceStore,
|
dataSourceStore,
|
||||||
uploadStore,
|
uploadStore,
|
||||||
rowSelectionStore,
|
rowSelectionStore,
|
||||||
|
sidePanelStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { ActionTypes } from "constants"
|
import { ActionTypes } from "constants"
|
||||||
|
@ -312,6 +313,17 @@ const showNotificationHandler = action => {
|
||||||
notificationStore.actions[type]?.(message, autoDismiss)
|
notificationStore.actions[type]?.(message, autoDismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OpenSidePanelHandler = action => {
|
||||||
|
const { id } = action.parameters
|
||||||
|
if (id) {
|
||||||
|
sidePanelStore.actions.open(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CloseSidePanelHandler = () => {
|
||||||
|
sidePanelStore.actions.close()
|
||||||
|
}
|
||||||
|
|
||||||
const handlerMap = {
|
const handlerMap = {
|
||||||
["Save Row"]: saveRowHandler,
|
["Save Row"]: saveRowHandler,
|
||||||
["Duplicate Row"]: duplicateRowHandler,
|
["Duplicate Row"]: duplicateRowHandler,
|
||||||
|
@ -331,6 +343,8 @@ const handlerMap = {
|
||||||
["Export Data"]: exportDataHandler,
|
["Export Data"]: exportDataHandler,
|
||||||
["Continue if / Stop if"]: continueIfHandler,
|
["Continue if / Stop if"]: continueIfHandler,
|
||||||
["Show Notification"]: showNotificationHandler,
|
["Show Notification"]: showNotificationHandler,
|
||||||
|
["Open Side Panel"]: OpenSidePanelHandler,
|
||||||
|
["Close Side Panel"]: CloseSidePanelHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmTextMap = {
|
const confirmTextMap = {
|
||||||
|
|
|
@ -25,6 +25,8 @@ export const styleable = (node, styles = {}) => {
|
||||||
|
|
||||||
// Creates event listeners and applies initial styles
|
// Creates event listeners and applies initial styles
|
||||||
const setupStyles = (newStyles = {}) => {
|
const setupStyles = (newStyles = {}) => {
|
||||||
|
node.classList.add(`${newStyles.id}-dom`)
|
||||||
|
|
||||||
let baseStyles = {}
|
let baseStyles = {}
|
||||||
if (newStyles.empty) {
|
if (newStyles.empty) {
|
||||||
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-400)"
|
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-400)"
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default class DataFetch {
|
||||||
* Fetches a fresh set of data from the server, resetting pagination
|
* Fetches a fresh set of data from the server, resetting pagination
|
||||||
*/
|
*/
|
||||||
async getInitialData() {
|
async getInitialData() {
|
||||||
const { datasource, filter, sortColumn, paginate } = this.options
|
const { datasource, filter, paginate } = this.options
|
||||||
|
|
||||||
// Fetch datasource definition and determine feature flags
|
// Fetch datasource definition and determine feature flags
|
||||||
const definition = await this.getDefinition(datasource)
|
const definition = await this.getDefinition(datasource)
|
||||||
|
@ -135,6 +135,17 @@ export default class DataFetch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no sort order, default to descending
|
||||||
|
if (!this.options.sortOrder) {
|
||||||
|
this.options.sortOrder = "ascending"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no sort column, use the first field in the schema
|
||||||
|
if (!this.options.sortColumn) {
|
||||||
|
this.options.sortColumn = Object.keys(schema)[0]
|
||||||
|
}
|
||||||
|
const { sortColumn } = this.options
|
||||||
|
|
||||||
// Determine what sort type to use
|
// Determine what sort type to use
|
||||||
let sortType = "string"
|
let sortType = "string"
|
||||||
if (sortColumn) {
|
if (sortColumn) {
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { getScreenParams } from "../../db/utils"
|
||||||
|
import { Screen } from "@budibase/types"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
/**
|
||||||
|
* Date:
|
||||||
|
* November 2022
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Update table settings to use actions instead of links. We do not remove the
|
||||||
|
* legacy values here as we cannot guarantee that their apps are up-t-date.
|
||||||
|
* It is safe to simply save both the new and old structure in the definition.
|
||||||
|
*
|
||||||
|
* Migration 1:
|
||||||
|
* Legacy "linkRows", "linkURL", "linkPeek" and "linkColumn" settings on tables
|
||||||
|
* and table blocks are migrated into a "Navigate To" action under the new
|
||||||
|
* "onClick" setting.
|
||||||
|
*
|
||||||
|
* Migration 2:
|
||||||
|
* Legacy "titleButtonURL" and "titleButtonPeek" settings on table blocks are
|
||||||
|
* migrated into a "Navigate To" action under the new "onClickTitleButton"
|
||||||
|
* setting.
|
||||||
|
*/
|
||||||
|
export const run = async (appDb: any) => {
|
||||||
|
// Get all app screens
|
||||||
|
let screens: Screen[]
|
||||||
|
try {
|
||||||
|
screens = (
|
||||||
|
await appDb.allDocs(
|
||||||
|
getScreenParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map((row: any) => row.doc)
|
||||||
|
} catch (e) {
|
||||||
|
// sometimes the metadata document doesn't exist
|
||||||
|
// exit early instead of failing the migration
|
||||||
|
console.error("Error retrieving app metadata. Skipping", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively update any relevant components and mutate the screen docs
|
||||||
|
for (let screen of screens) {
|
||||||
|
const changed = migrateTableSettings(screen.props)
|
||||||
|
|
||||||
|
// Save screen if we updated it
|
||||||
|
if (changed) {
|
||||||
|
await appDb.put(screen)
|
||||||
|
console.log(
|
||||||
|
`Screen ${screen.routing?.route} contained table settings which were migrated`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively searches and mutates a screen doc to migrate table component
|
||||||
|
// and table block settings
|
||||||
|
const migrateTableSettings = (component: any) => {
|
||||||
|
let changed = false
|
||||||
|
if (!component) {
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration 1: migrate table row click settings
|
||||||
|
if (
|
||||||
|
component._component.endsWith("/table") ||
|
||||||
|
component._component.endsWith("/tableblock")
|
||||||
|
) {
|
||||||
|
const { linkRows, linkURL, linkPeek, linkColumn, onClick } = component
|
||||||
|
if (linkRows && !onClick) {
|
||||||
|
const column = linkColumn || "_id"
|
||||||
|
const action = convertLinkSettingToAction(linkURL, !!linkPeek, column)
|
||||||
|
if (action) {
|
||||||
|
changed = true
|
||||||
|
component.onClick = action
|
||||||
|
if (component._component.endsWith("/tableblock")) {
|
||||||
|
component.clickBehaviour = "actions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration 2: migrate table block title button settings
|
||||||
|
if (component._component.endsWith("/tableblock")) {
|
||||||
|
const {
|
||||||
|
showTitleButton,
|
||||||
|
titleButtonURL,
|
||||||
|
titleButtonPeek,
|
||||||
|
onClickTitleButton,
|
||||||
|
} = component
|
||||||
|
if (showTitleButton && !onClickTitleButton) {
|
||||||
|
const action = convertLinkSettingToAction(
|
||||||
|
titleButtonURL,
|
||||||
|
!!titleButtonPeek
|
||||||
|
)
|
||||||
|
if (action) {
|
||||||
|
changed = true
|
||||||
|
component.onClickTitleButton = action
|
||||||
|
component.titleButtonClickBehaviour = "actions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse down the tree as needed
|
||||||
|
component._children?.forEach((child: any) => {
|
||||||
|
const childChanged = migrateTableSettings(child)
|
||||||
|
changed = changed || childChanged
|
||||||
|
})
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Util ti convert the legacy settings into a navigation action structure
|
||||||
|
const convertLinkSettingToAction = (
|
||||||
|
linkURL: string,
|
||||||
|
linkPeek: boolean,
|
||||||
|
linkColumn?: string
|
||||||
|
) => {
|
||||||
|
// Sanity check we have a URL
|
||||||
|
if (!linkURL) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default URL to the old URL setting
|
||||||
|
let url = linkURL
|
||||||
|
|
||||||
|
// If we enriched the old URL with a column, update the url
|
||||||
|
if (linkColumn && linkURL.includes("/:")) {
|
||||||
|
// Convert old link URL setting, which is a screen URL, into a valid
|
||||||
|
// binding using the new clicked row binding
|
||||||
|
const split = linkURL.split("/:")
|
||||||
|
const col = linkColumn || "_id"
|
||||||
|
const binding = `{{ ${safe("eventContext")}.${safe("row")}.${safe(col)} }}`
|
||||||
|
url = `${split[0]}/${binding}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create action structure
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url,
|
||||||
|
peek: linkPeek,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
import { App, Screen } from "@budibase/types"
|
||||||
|
|
||||||
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
|
import TestConfig from "../../../tests/utilities/TestConfiguration"
|
||||||
|
import { run as runMigration } from "../tableSettings"
|
||||||
|
|
||||||
|
describe("run", () => {
|
||||||
|
const config = new TestConfig(false)
|
||||||
|
let app: App
|
||||||
|
let screen: Screen
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
app = await config.createApp("testApp")
|
||||||
|
screen = await config.createScreen()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(config.end)
|
||||||
|
|
||||||
|
it("migrates table block row on click settings", async () => {
|
||||||
|
// Add legacy table block as first child
|
||||||
|
screen.props._children = [
|
||||||
|
{
|
||||||
|
_instanceName: "Table Block",
|
||||||
|
_styles: {},
|
||||||
|
_component: "@budibase/standard-components/tableblock",
|
||||||
|
_id: "foo",
|
||||||
|
linkRows: true,
|
||||||
|
linkURL: "/rows/:id",
|
||||||
|
linkPeek: true,
|
||||||
|
linkColumn: "name",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
await config.createScreen(screen)
|
||||||
|
|
||||||
|
// Run migration
|
||||||
|
screen = await dbCore.doWithDB(app.appId, async (db: any) => {
|
||||||
|
await runMigration(db)
|
||||||
|
return await db.get(screen._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify new "onClick" setting
|
||||||
|
const onClick = screen.props._children?.[0].onClick
|
||||||
|
expect(onClick).toBeDefined()
|
||||||
|
expect(onClick.length).toBe(1)
|
||||||
|
expect(onClick[0]["##eventHandlerType"]).toBe("Navigate To")
|
||||||
|
expect(onClick[0].parameters.url).toBe(
|
||||||
|
`/rows/{{ [eventContext].[row].[name] }}`
|
||||||
|
)
|
||||||
|
expect(onClick[0].parameters.peek).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("migrates table row on click settings", async () => {
|
||||||
|
// Add legacy table block as first child
|
||||||
|
screen.props._children = [
|
||||||
|
{
|
||||||
|
_instanceName: "Table",
|
||||||
|
_styles: {},
|
||||||
|
_component: "@budibase/standard-components/table",
|
||||||
|
_id: "foo",
|
||||||
|
linkRows: true,
|
||||||
|
linkURL: "/rows/:id",
|
||||||
|
linkPeek: true,
|
||||||
|
linkColumn: "name",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
await config.createScreen(screen)
|
||||||
|
|
||||||
|
// Run migration
|
||||||
|
screen = await dbCore.doWithDB(app.appId, async (db: any) => {
|
||||||
|
await runMigration(db)
|
||||||
|
return await db.get(screen._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify new "onClick" setting
|
||||||
|
const onClick = screen.props._children?.[0].onClick
|
||||||
|
expect(onClick).toBeDefined()
|
||||||
|
expect(onClick.length).toBe(1)
|
||||||
|
expect(onClick[0]["##eventHandlerType"]).toBe("Navigate To")
|
||||||
|
expect(onClick[0].parameters.url).toBe(
|
||||||
|
`/rows/{{ [eventContext].[row].[name] }}`
|
||||||
|
)
|
||||||
|
expect(onClick[0].parameters.peek).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("migrates table block title button settings", async () => {
|
||||||
|
// Add legacy table block as first child
|
||||||
|
screen.props._children = [
|
||||||
|
{
|
||||||
|
_instanceName: "Table Block",
|
||||||
|
_styles: {},
|
||||||
|
_component: "@budibase/standard-components/tableblock",
|
||||||
|
_id: "foo",
|
||||||
|
showTitleButton: true,
|
||||||
|
titleButtonURL: "/url",
|
||||||
|
titleButtonPeek: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
await config.createScreen(screen)
|
||||||
|
|
||||||
|
// Run migration
|
||||||
|
screen = await dbCore.doWithDB(app.appId, async (db: any) => {
|
||||||
|
await runMigration(db)
|
||||||
|
return await db.get(screen._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify new "onClickTitleButton" setting
|
||||||
|
const onClick = screen.props._children?.[0].onClickTitleButton
|
||||||
|
expect(onClick).toBeDefined()
|
||||||
|
expect(onClick.length).toBe(1)
|
||||||
|
expect(onClick[0]["##eventHandlerType"]).toBe("Navigate To")
|
||||||
|
expect(onClick[0].parameters.url).toBe("/url")
|
||||||
|
expect(onClick[0].parameters.peek).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("ignores components that have already been migrated", async () => {
|
||||||
|
// Add legacy table block as first child
|
||||||
|
screen.props._children = [
|
||||||
|
{
|
||||||
|
_instanceName: "Table Block",
|
||||||
|
_styles: {},
|
||||||
|
_component: "@budibase/standard-components/tableblock",
|
||||||
|
_id: "foo",
|
||||||
|
linkRows: true,
|
||||||
|
linkURL: "/rows/:id",
|
||||||
|
linkPeek: true,
|
||||||
|
linkColumn: "name",
|
||||||
|
onClick: "foo",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const initialDefinition = JSON.stringify(screen.props._children?.[0])
|
||||||
|
await config.createScreen(screen)
|
||||||
|
|
||||||
|
// Run migration
|
||||||
|
screen = await dbCore.doWithDB(app.appId, async (db: any) => {
|
||||||
|
await runMigration(db)
|
||||||
|
return await db.get(screen._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify new "onClick" setting
|
||||||
|
const newDefinition = JSON.stringify(screen.props._children?.[0])
|
||||||
|
expect(initialDefinition).toEqual(newDefinition)
|
||||||
|
})
|
||||||
|
})
|
|
@ -12,6 +12,7 @@ import env from "../environment"
|
||||||
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
||||||
import * as syncQuotas from "./functions/syncQuotas"
|
import * as syncQuotas from "./functions/syncQuotas"
|
||||||
import * as appUrls from "./functions/appUrls"
|
import * as appUrls from "./functions/appUrls"
|
||||||
|
import * as tableSettings from "./functions/tableSettings"
|
||||||
import * as backfill from "./functions/backfill"
|
import * as backfill from "./functions/backfill"
|
||||||
/**
|
/**
|
||||||
* Populate the migration function and additional configuration from
|
* Populate the migration function and additional configuration from
|
||||||
|
@ -73,6 +74,14 @@ export const buildMigrations = () => {
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case MigrationName.TABLE_SETTINGS_LINKS_TO_ACTIONS: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
|
appOpts: { dev: true },
|
||||||
|
fn: tableSettings.run,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface Component extends Document {
|
||||||
|
_instanceName: string
|
||||||
|
_styles: { [key: string]: any }
|
||||||
|
_component: string
|
||||||
|
_children?: Component[]
|
||||||
|
[key: string]: any
|
||||||
|
}
|
|
@ -13,3 +13,4 @@ export * from "./user"
|
||||||
export * from "./backup"
|
export * from "./backup"
|
||||||
export * from "./webhook"
|
export * from "./webhook"
|
||||||
export * from "./links"
|
export * from "./links"
|
||||||
|
export * from "./component"
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
import { Component } from "./component"
|
||||||
|
|
||||||
export interface ScreenProps extends Document {
|
export interface ScreenProps extends Component {
|
||||||
_instanceName: string
|
|
||||||
_styles: { [key: string]: any }
|
|
||||||
_component: string
|
|
||||||
_children: ScreenProps[]
|
|
||||||
size?: string
|
size?: string
|
||||||
gap?: string
|
gap?: string
|
||||||
direction?: string
|
direction?: string
|
||||||
|
|
|
@ -44,6 +44,7 @@ export enum MigrationName {
|
||||||
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
|
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
|
||||||
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
|
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
|
||||||
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
|
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
|
||||||
|
TABLE_SETTINGS_LINKS_TO_ACTIONS = "table_settings_links_to_actions",
|
||||||
// increment this number to re-activate this migration
|
// increment this number to re-activate this migration
|
||||||
SYNC_QUOTAS = "sync_quotas_1",
|
SYNC_QUOTAS = "sync_quotas_1",
|
||||||
}
|
}
|
||||||
|
|
100
yarn.lock
100
yarn.lock
|
@ -1011,45 +1011,45 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
||||||
integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
|
integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
|
||||||
|
|
||||||
"@typescript-eslint/parser@4.28.0":
|
"@typescript-eslint/parser@5.45.0":
|
||||||
version "4.28.0"
|
version "5.45.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.28.0.tgz#2404c16751a28616ef3abab77c8e51d680a12caa"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.0.tgz#b18a5f6b3cf1c2b3e399e9d2df4be40d6b0ddd0e"
|
||||||
integrity sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==
|
integrity sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "4.28.0"
|
"@typescript-eslint/scope-manager" "5.45.0"
|
||||||
"@typescript-eslint/types" "4.28.0"
|
"@typescript-eslint/types" "5.45.0"
|
||||||
"@typescript-eslint/typescript-estree" "4.28.0"
|
"@typescript-eslint/typescript-estree" "5.45.0"
|
||||||
debug "^4.3.1"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@4.28.0":
|
"@typescript-eslint/scope-manager@5.45.0":
|
||||||
version "4.28.0"
|
version "5.45.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz#6a3009d2ab64a30fc8a1e257a1a320067f36a0ce"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz#7a4ac1bfa9544bff3f620ab85947945938319a96"
|
||||||
integrity sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==
|
integrity sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "4.28.0"
|
"@typescript-eslint/types" "5.45.0"
|
||||||
"@typescript-eslint/visitor-keys" "4.28.0"
|
"@typescript-eslint/visitor-keys" "5.45.0"
|
||||||
|
|
||||||
"@typescript-eslint/types@4.28.0":
|
|
||||||
version "4.28.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.0.tgz#a33504e1ce7ac51fc39035f5fe6f15079d4dafb0"
|
|
||||||
integrity sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==
|
|
||||||
|
|
||||||
"@typescript-eslint/types@4.33.0":
|
"@typescript-eslint/types@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
||||||
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
|
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@4.28.0":
|
"@typescript-eslint/types@5.45.0":
|
||||||
version "4.28.0"
|
version "5.45.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz#e66d4e5aa2ede66fec8af434898fe61af10c71cf"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.0.tgz#794760b9037ee4154c09549ef5a96599621109c5"
|
||||||
integrity sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==
|
integrity sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree@5.45.0":
|
||||||
|
version "5.45.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz#f70a0d646d7f38c0dfd6936a5e171a77f1e5291d"
|
||||||
|
integrity sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "4.28.0"
|
"@typescript-eslint/types" "5.45.0"
|
||||||
"@typescript-eslint/visitor-keys" "4.28.0"
|
"@typescript-eslint/visitor-keys" "5.45.0"
|
||||||
debug "^4.3.1"
|
debug "^4.3.4"
|
||||||
globby "^11.0.3"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.3"
|
||||||
semver "^7.3.5"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@^4.33.0":
|
"@typescript-eslint/typescript-estree@^4.33.0":
|
||||||
|
@ -1065,14 +1065,6 @@
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@4.28.0":
|
|
||||||
version "4.28.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz#255c67c966ec294104169a6939d96f91c8a89434"
|
|
||||||
integrity sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==
|
|
||||||
dependencies:
|
|
||||||
"@typescript-eslint/types" "4.28.0"
|
|
||||||
eslint-visitor-keys "^2.0.0"
|
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@4.33.0":
|
"@typescript-eslint/visitor-keys@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
|
||||||
|
@ -1081,6 +1073,14 @@
|
||||||
"@typescript-eslint/types" "4.33.0"
|
"@typescript-eslint/types" "4.33.0"
|
||||||
eslint-visitor-keys "^2.0.0"
|
eslint-visitor-keys "^2.0.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys@5.45.0":
|
||||||
|
version "5.45.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz#e0d160e9e7fdb7f8da697a5b78e7a14a22a70528"
|
||||||
|
integrity sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "5.45.0"
|
||||||
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
JSONStream@^1.0.4, JSONStream@^1.3.4:
|
JSONStream@^1.0.4, JSONStream@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||||
|
@ -2015,7 +2015,7 @@ debug@^3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
debug@^4.0.0, debug@^4.3.1, debug@^4.3.3:
|
debug@^4.0.0, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
@ -2446,6 +2446,11 @@ eslint-visitor-keys@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
|
||||||
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
|
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
|
||||||
|
|
||||||
|
eslint-visitor-keys@^3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||||
|
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||||
|
|
||||||
eslint@^7.28.0:
|
eslint@^7.28.0:
|
||||||
version "7.32.0"
|
version "7.32.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
||||||
|
@ -3081,7 +3086,7 @@ globals@^13.6.0, globals@^13.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest "^0.20.2"
|
type-fest "^0.20.2"
|
||||||
|
|
||||||
globby@^11.0.3:
|
globby@^11.0.3, globby@^11.1.0:
|
||||||
version "11.1.0"
|
version "11.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||||
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
||||||
|
@ -3606,7 +3611,7 @@ is-glob@^3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
|
@ -5843,6 +5848,13 @@ semver@^7.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^7.3.7:
|
||||||
|
version "7.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||||
|
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@~5.3.0:
|
semver@~5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||||
|
@ -6559,10 +6571,10 @@ typedarray@^0.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||||
|
|
||||||
typescript@4.5.5:
|
typescript@4.7.3:
|
||||||
version "4.5.5"
|
version "4.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
|
||||||
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
|
||||||
|
|
||||||
typescript@^3.9.10, typescript@^3.9.5, typescript@^3.9.7:
|
typescript@^3.9.10, typescript@^3.9.5, typescript@^3.9.7:
|
||||||
version "3.9.10"
|
version "3.9.10"
|
||||||
|
|
Loading…
Reference in New Issue