Merge pull request #15362 from Budibase/ts/builder-helpers
Typing builder helpers
This commit is contained in:
commit
9ae483ca30
|
@ -28,7 +28,9 @@
|
|||
let loading = false
|
||||
let deleteConfirmationDialog
|
||||
|
||||
$: defaultName = getSequentialName($snippets, "MySnippet", x => x.name)
|
||||
$: defaultName = getSequentialName($snippets, "MySnippet", {
|
||||
getName: x => x.name,
|
||||
})
|
||||
$: key = snippet?.name
|
||||
$: name = snippet?.name || defaultName
|
||||
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
||||
|
|
|
@ -16,7 +16,10 @@ export {
|
|||
|
||||
export const AUTO_COLUMN_SUB_TYPES = AutoFieldSubType
|
||||
|
||||
export const AUTO_COLUMN_DISPLAY_NAMES = {
|
||||
export const AUTO_COLUMN_DISPLAY_NAMES: Record<
|
||||
keyof typeof AUTO_COLUMN_SUB_TYPES,
|
||||
string
|
||||
> = {
|
||||
AUTO_ID: "Auto ID",
|
||||
CREATED_BY: "Created By",
|
||||
CREATED_AT: "Created At",
|
||||
|
@ -209,13 +212,6 @@ export const Roles = {
|
|||
BUILDER: "BUILDER",
|
||||
}
|
||||
|
||||
export function isAutoColumnUserRelationship(subtype) {
|
||||
return (
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY ||
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.UPDATED_BY
|
||||
)
|
||||
}
|
||||
|
||||
export const PrettyRelationshipDefinitions = {
|
||||
MANY: "Many rows",
|
||||
ONE: "One row",
|
|
@ -10,13 +10,13 @@
|
|||
*
|
||||
* Repl
|
||||
*/
|
||||
export const duplicateName = (name, allNames) => {
|
||||
export const duplicateName = (name: string, allNames: string[]) => {
|
||||
const duplicatePattern = new RegExp(`\\s(\\d+)$`)
|
||||
const baseName = name.split(duplicatePattern)[0]
|
||||
const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`)
|
||||
|
||||
// get the sequence from matched names
|
||||
const sequence = []
|
||||
const sequence: number[] = []
|
||||
allNames.filter(n => {
|
||||
if (n === baseName) {
|
||||
return true
|
||||
|
@ -70,12 +70,18 @@ export const duplicateName = (name, allNames) => {
|
|||
* @param getName optional function to extract the name for an item, if not a
|
||||
* flat array of strings
|
||||
*/
|
||||
export const getSequentialName = (
|
||||
items,
|
||||
prefix,
|
||||
{ getName = x => x, numberFirstItem = false } = {}
|
||||
export const getSequentialName = <T extends any>(
|
||||
items: T[] | null,
|
||||
prefix: string | null,
|
||||
{
|
||||
getName,
|
||||
numberFirstItem,
|
||||
}: {
|
||||
getName?: (item: T) => string
|
||||
numberFirstItem?: boolean
|
||||
} = {}
|
||||
) => {
|
||||
if (!prefix?.length || !getName) {
|
||||
if (!prefix?.length) {
|
||||
return null
|
||||
}
|
||||
const trimmedPrefix = prefix.trim()
|
||||
|
@ -85,7 +91,7 @@ export const getSequentialName = (
|
|||
}
|
||||
let max = 0
|
||||
items.forEach(item => {
|
||||
const name = getName(item)
|
||||
const name = getName?.(item) ?? item
|
||||
if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) {
|
||||
return
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { FeatureFlag } from "@budibase/types"
|
||||
import { auth } from "../stores/portal"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export const isEnabled = featureFlag => {
|
||||
export const isEnabled = (featureFlag: FeatureFlag | `${FeatureFlag}`) => {
|
||||
const user = get(auth).user
|
||||
return !!user?.flags?.[featureFlag]
|
||||
}
|
|
@ -1,13 +1,21 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { API } from "@/api"
|
||||
|
||||
export default function (url) {
|
||||
const store = writable({ status: "LOADING", data: {}, error: {} })
|
||||
export default function (url: string) {
|
||||
const store = writable<{
|
||||
status: "LOADING" | "SUCCESS" | "ERROR"
|
||||
data: object
|
||||
error?: unknown
|
||||
}>({
|
||||
status: "LOADING",
|
||||
data: {},
|
||||
error: {},
|
||||
})
|
||||
|
||||
async function get() {
|
||||
store.update(u => ({ ...u, status: "LOADING" }))
|
||||
try {
|
||||
const data = await API.get({ url })
|
||||
const data = await API.get<object>({ url })
|
||||
store.set({ data, status: "SUCCESS" })
|
||||
} catch (e) {
|
||||
store.set({ data: {}, error: e, status: "ERROR" })
|
|
@ -1,46 +0,0 @@
|
|||
import { last, flow } from "lodash/fp"
|
||||
|
||||
export const buildStyle = styles => {
|
||||
let str = ""
|
||||
for (let s in styles) {
|
||||
if (styles[s]) {
|
||||
let key = convertCamel(s)
|
||||
str += `${key}: ${styles[s]}; `
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
export const convertCamel = str => {
|
||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||
}
|
||||
|
||||
export const pipe = (arg, funcs) => flow(funcs)(arg)
|
||||
|
||||
export const capitalise = s => {
|
||||
if (!s) {
|
||||
return s
|
||||
}
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||
}
|
||||
|
||||
export const lowercase = s => s.substring(0, 1).toLowerCase() + s.substring(1)
|
||||
|
||||
export const lowercaseExceptFirst = s =>
|
||||
s.charAt(0) + s.substring(1).toLowerCase()
|
||||
|
||||
export const get_name = s => (!s ? "" : last(s.split("/")))
|
||||
|
||||
export const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
||||
|
||||
export const isBuilderInputFocused = e => {
|
||||
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||
const inCodeEditor = document.activeElement?.classList?.contains("cm-content")
|
||||
if (
|
||||
(inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) &&
|
||||
e.key !== "Escape"
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import type { Many } from "lodash"
|
||||
import { last, flow } from "lodash/fp"
|
||||
|
||||
export const buildStyle = (styles: Record<string, any>) => {
|
||||
let str = ""
|
||||
for (let s in styles) {
|
||||
if (styles[s]) {
|
||||
let key = convertCamel(s)
|
||||
str += `${key}: ${styles[s]}; `
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
export const convertCamel = (str: string) => {
|
||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||
}
|
||||
|
||||
export const pipe = (arg: string, funcs: Many<(...args: any[]) => any>) =>
|
||||
flow(funcs)(arg)
|
||||
|
||||
export const capitalise = (s: string) => {
|
||||
if (!s) {
|
||||
return s
|
||||
}
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||
}
|
||||
|
||||
export const lowercase = (s: string) =>
|
||||
s.substring(0, 1).toLowerCase() + s.substring(1)
|
||||
|
||||
export const lowercaseExceptFirst = (s: string) =>
|
||||
s.charAt(0) + s.substring(1).toLowerCase()
|
||||
|
||||
export const get_name = (s: string) => (!s ? "" : last(s.split("/")))
|
||||
|
||||
export const get_capitalised_name = (name: string) =>
|
||||
pipe(name, [get_name, capitalise])
|
||||
|
||||
export const isBuilderInputFocused = (e: KeyboardEvent) => {
|
||||
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||
const inCodeEditor = document.activeElement?.classList?.contains("cm-content")
|
||||
if (
|
||||
(inCodeEditor || ["input", "textarea"].indexOf(activeTag!) !== -1) &&
|
||||
e.key !== "Escape"
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
function handleEnter(fnc) {
|
||||
return e => e.key === "Enter" && fnc()
|
||||
}
|
||||
|
||||
export const keyUtils = {
|
||||
handleEnter,
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
function handleEnter(fnc: () => void) {
|
||||
return (e: KeyboardEvent) => e.key === "Enter" && fnc()
|
||||
}
|
||||
|
||||
export const keyUtils = {
|
||||
handleEnter,
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
function defaultValue() {
|
||||
interface PaginationStore {
|
||||
nextPage: string | null | undefined
|
||||
page: string | null | undefined
|
||||
hasPrevPage: boolean
|
||||
hasNextPage: boolean
|
||||
loading: boolean
|
||||
pageNumber: number
|
||||
pages: string[]
|
||||
}
|
||||
|
||||
function defaultValue(): PaginationStore {
|
||||
return {
|
||||
nextPage: null,
|
||||
page: undefined,
|
||||
|
@ -29,13 +39,13 @@ export function createPaginationStore() {
|
|||
update(state => {
|
||||
state.pageNumber++
|
||||
state.page = state.nextPage
|
||||
state.pages.push(state.page)
|
||||
state.pages.push(state.page!)
|
||||
state.hasPrevPage = state.pageNumber > 1
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
function fetched(hasNextPage, nextPage) {
|
||||
function fetched(hasNextPage: boolean, nextPage: string) {
|
||||
update(state => {
|
||||
state.hasNextPage = hasNextPage
|
||||
state.nextPage = nextPage
|
|
@ -1,6 +1,6 @@
|
|||
import { PlanType } from "@budibase/types"
|
||||
|
||||
export function getFormattedPlanName(userPlanType) {
|
||||
export function getFormattedPlanName(userPlanType: PlanType) {
|
||||
let planName
|
||||
switch (userPlanType) {
|
||||
case PlanType.PRO:
|
||||
|
@ -29,6 +29,6 @@ export function getFormattedPlanName(userPlanType) {
|
|||
return `${planName} Plan`
|
||||
}
|
||||
|
||||
export function isPremiumOrAbove(userPlanType) {
|
||||
export function isPremiumOrAbove(userPlanType: PlanType) {
|
||||
return ![PlanType.PRO, PlanType.TEAM, PlanType.FREE].includes(userPlanType)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export default function (url) {
|
||||
export default function (url: string) {
|
||||
return url
|
||||
.split("/")
|
||||
.map(part => {
|
|
@ -1,72 +0,0 @@
|
|||
import { FieldType } from "@budibase/types"
|
||||
import { ActionStepID } from "@/constants/backend/automations"
|
||||
import { TableNames } from "@/constants"
|
||||
import {
|
||||
AUTO_COLUMN_DISPLAY_NAMES,
|
||||
AUTO_COLUMN_SUB_TYPES,
|
||||
FIELDS,
|
||||
isAutoColumnUserRelationship,
|
||||
} from "@/constants/backend"
|
||||
|
||||
export function getAutoColumnInformation(enabled = true) {
|
||||
let info = {}
|
||||
for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) {
|
||||
// Because it's possible to replicate the functionality of CREATED_AT and
|
||||
// CREATED_BY columns with user column default values, we disable their creation
|
||||
if (
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT ||
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] }
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
export function buildAutoColumn(tableName, name, subtype) {
|
||||
let type, constraints
|
||||
switch (subtype) {
|
||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_BY:
|
||||
case AUTO_COLUMN_SUB_TYPES.CREATED_BY:
|
||||
type = FieldType.LINK
|
||||
constraints = FIELDS.LINK.constraints
|
||||
break
|
||||
case AUTO_COLUMN_SUB_TYPES.AUTO_ID:
|
||||
type = FieldType.NUMBER
|
||||
constraints = FIELDS.NUMBER.constraints
|
||||
break
|
||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_AT:
|
||||
case AUTO_COLUMN_SUB_TYPES.CREATED_AT:
|
||||
type = FieldType.DATETIME
|
||||
constraints = FIELDS.DATETIME.constraints
|
||||
break
|
||||
default:
|
||||
type = FieldType.STRING
|
||||
constraints = FIELDS.STRING.constraints
|
||||
break
|
||||
}
|
||||
if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) {
|
||||
throw "Cannot build auto column with supplied subtype"
|
||||
}
|
||||
const base = {
|
||||
name,
|
||||
type,
|
||||
subtype,
|
||||
icon: "ri-magic-line",
|
||||
autocolumn: true,
|
||||
constraints,
|
||||
}
|
||||
if (isAutoColumnUserRelationship(subtype)) {
|
||||
base.tableId = TableNames.USERS
|
||||
base.fieldName = `${tableName}-${name}`
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
export function checkForCollectStep(automation) {
|
||||
return automation.definition.steps.some(
|
||||
step => step.stepId === ActionStepID.COLLECT
|
||||
)
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
AutoFieldSubType,
|
||||
Automation,
|
||||
DateFieldMetadata,
|
||||
FieldType,
|
||||
NumberFieldMetadata,
|
||||
RelationshipFieldMetadata,
|
||||
RelationshipType,
|
||||
} from "@budibase/types"
|
||||
import { ActionStepID } from "@/constants/backend/automations"
|
||||
import { TableNames } from "@/constants"
|
||||
import {
|
||||
AUTO_COLUMN_DISPLAY_NAMES,
|
||||
AUTO_COLUMN_SUB_TYPES,
|
||||
FIELDS,
|
||||
} from "@/constants/backend"
|
||||
import { utils } from "@budibase/shared-core"
|
||||
|
||||
type AutoColumnInformation = Partial<
|
||||
Record<AutoFieldSubType, { enabled: boolean; name: string }>
|
||||
>
|
||||
|
||||
export function getAutoColumnInformation(
|
||||
enabled = true
|
||||
): AutoColumnInformation {
|
||||
const info: AutoColumnInformation = {}
|
||||
for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) {
|
||||
// Because it's possible to replicate the functionality of CREATED_AT and
|
||||
// CREATED_BY columns with user column default values, we disable their creation
|
||||
if (
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT ||
|
||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const typedKey = key as keyof typeof AUTO_COLUMN_SUB_TYPES
|
||||
info[subtype] = {
|
||||
enabled,
|
||||
name: AUTO_COLUMN_DISPLAY_NAMES[typedKey],
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
export function buildAutoColumn(
|
||||
tableName: string,
|
||||
name: string,
|
||||
subtype: AutoFieldSubType
|
||||
): RelationshipFieldMetadata | NumberFieldMetadata | DateFieldMetadata {
|
||||
const base = {
|
||||
name,
|
||||
icon: "ri-magic-line",
|
||||
autocolumn: true,
|
||||
}
|
||||
|
||||
switch (subtype) {
|
||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_BY:
|
||||
case AUTO_COLUMN_SUB_TYPES.CREATED_BY:
|
||||
return {
|
||||
...base,
|
||||
type: FieldType.LINK,
|
||||
subtype,
|
||||
constraints: FIELDS.LINK.constraints,
|
||||
tableId: TableNames.USERS,
|
||||
fieldName: `${tableName}-${name}`,
|
||||
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||
}
|
||||
|
||||
case AUTO_COLUMN_SUB_TYPES.AUTO_ID:
|
||||
return {
|
||||
...base,
|
||||
type: FieldType.NUMBER,
|
||||
subtype,
|
||||
constraints: FIELDS.NUMBER.constraints,
|
||||
}
|
||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_AT:
|
||||
case AUTO_COLUMN_SUB_TYPES.CREATED_AT:
|
||||
return {
|
||||
...base,
|
||||
type: FieldType.DATETIME,
|
||||
subtype,
|
||||
constraints: FIELDS.DATETIME.constraints,
|
||||
}
|
||||
|
||||
default:
|
||||
throw utils.unreachable(subtype, {
|
||||
message: "Cannot build auto column with supplied subtype",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function checkForCollectStep(automation: Automation) {
|
||||
return automation.definition.steps.some(
|
||||
step => step.stepId === ActionStepID.COLLECT
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export const suppressWarnings = warnings => {
|
||||
export const suppressWarnings = (warnings: string[]) => {
|
||||
if (!warnings?.length) {
|
||||
return
|
||||
}
|
|
@ -62,7 +62,7 @@ export class RowActionStore extends BudiStore<RowActionState> {
|
|||
const existingRowActions = get(this)[tableId] || []
|
||||
name = getSequentialName(existingRowActions, "New row action ", {
|
||||
getName: x => x.name,
|
||||
})
|
||||
})!
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
|
|
Loading…
Reference in New Issue