Merge branch 'master' into automation-duplicate-name
This commit is contained in:
commit
19bc30ae21
|
@ -51,6 +51,7 @@
|
|||
}
|
||||
input.hide-arrows {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
input[type="time"]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
input::-webkit-slider-thumb {
|
||||
|
|
|
@ -124,8 +124,6 @@
|
|||
.spectrum-Tabs-selectionIndicator.emphasized {
|
||||
background-color: var(--spectrum-global-color-blue-400);
|
||||
}
|
||||
.spectrum-Tabs--horizontal .spectrum-Tabs-selectionIndicator {
|
||||
}
|
||||
.noHorizPadding {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
.spectrum-Tooltip-label {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
"@sveltejs/vite-plugin-svelte": "1.4.0",
|
||||
"@testing-library/jest-dom": "6.4.2",
|
||||
"@testing-library/svelte": "^4.1.0",
|
||||
"@types/shortid": "^2.2.0",
|
||||
"babel-jest": "^29.6.2",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "29.7.0",
|
||||
|
|
|
@ -1507,7 +1507,12 @@ export const updateReferencesInObject = ({
|
|||
|
||||
// Migrate references
|
||||
// Switch all bindings to reference their ids
|
||||
export const migrateReferencesInObject = ({ obj, label = "steps", steps }) => {
|
||||
export const migrateReferencesInObject = ({
|
||||
obj,
|
||||
label = "steps",
|
||||
steps,
|
||||
originalIndex,
|
||||
}) => {
|
||||
const stepIndexRegex = new RegExp(`{{\\s*${label}\\.(\\d+)\\.`, "g")
|
||||
const updateActionStep = (str, index, replaceWith) =>
|
||||
str.replace(`{{ ${label}.${index}.`, `{{ ${label}.${replaceWith}.`)
|
||||
|
@ -1528,6 +1533,7 @@ export const migrateReferencesInObject = ({ obj, label = "steps", steps }) => {
|
|||
migrateReferencesInObject({
|
||||
obj: obj[key],
|
||||
steps,
|
||||
originalIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,54 @@
|
|||
import { API } from "api"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import {
|
||||
App,
|
||||
AppFeatures,
|
||||
AppIcon,
|
||||
AutomationSettings,
|
||||
Plugin,
|
||||
} from "@budibase/types"
|
||||
|
||||
export const INITIAL_APP_META_STATE = {
|
||||
interface ClientFeatures {
|
||||
spectrumThemes: boolean
|
||||
intelligentLoading: boolean
|
||||
deviceAwareness: boolean
|
||||
state: boolean
|
||||
rowSelection: boolean
|
||||
customThemes: boolean
|
||||
devicePreview: boolean
|
||||
messagePassing: boolean
|
||||
continueIfAction: boolean
|
||||
showNotificationAction: boolean
|
||||
sidePanel: boolean
|
||||
}
|
||||
|
||||
interface TypeSupportPresets {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface AppMetaState {
|
||||
appId: string
|
||||
name: string
|
||||
url: string
|
||||
libraries: string[]
|
||||
clientFeatures: ClientFeatures
|
||||
typeSupportPresets: TypeSupportPresets
|
||||
features: AppFeatures
|
||||
clientLibPath: string
|
||||
hasLock: boolean
|
||||
appInstance: { _id: string } | null
|
||||
initialised: boolean
|
||||
hasAppPackage: boolean
|
||||
usedPlugins: Plugin[] | null
|
||||
automations: AutomationSettings
|
||||
routes: { [key: string]: any }
|
||||
version?: string
|
||||
revertableVersion?: string
|
||||
upgradableVersion?: string
|
||||
icon?: AppIcon
|
||||
}
|
||||
|
||||
export const INITIAL_APP_META_STATE: AppMetaState = {
|
||||
appId: "",
|
||||
name: "",
|
||||
url: "",
|
||||
|
@ -34,23 +81,27 @@ export const INITIAL_APP_META_STATE = {
|
|||
routes: {},
|
||||
}
|
||||
|
||||
export class AppMetaStore extends BudiStore {
|
||||
export class AppMetaStore extends BudiStore<AppMetaState> {
|
||||
constructor() {
|
||||
super(INITIAL_APP_META_STATE)
|
||||
}
|
||||
|
||||
reset() {
|
||||
reset(): void {
|
||||
this.store.set({ ...INITIAL_APP_META_STATE })
|
||||
}
|
||||
|
||||
syncAppPackage(pkg) {
|
||||
syncAppPackage(pkg: {
|
||||
application: App
|
||||
clientLibPath: string
|
||||
hasLock: boolean
|
||||
}): void {
|
||||
const { application: app, clientLibPath, hasLock } = pkg
|
||||
|
||||
this.update(state => ({
|
||||
...state,
|
||||
name: app.name,
|
||||
appId: app.appId,
|
||||
url: app.url,
|
||||
url: app.url || "",
|
||||
hasLock,
|
||||
clientLibPath,
|
||||
libraries: app.componentLibraries,
|
||||
|
@ -58,8 +109,8 @@ export class AppMetaStore extends BudiStore {
|
|||
appInstance: app.instance,
|
||||
revertableVersion: app.revertableVersion,
|
||||
upgradableVersion: app.upgradableVersion,
|
||||
usedPlugins: app.usedPlugins,
|
||||
icon: app.icon || {},
|
||||
usedPlugins: app.usedPlugins || null,
|
||||
icon: app.icon,
|
||||
features: {
|
||||
...INITIAL_APP_META_STATE.features,
|
||||
...app.features,
|
||||
|
@ -70,7 +121,7 @@ export class AppMetaStore extends BudiStore {
|
|||
}))
|
||||
}
|
||||
|
||||
syncClientFeatures(features) {
|
||||
syncClientFeatures(features: Partial<ClientFeatures>): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
clientFeatures: {
|
||||
|
@ -80,14 +131,14 @@ export class AppMetaStore extends BudiStore {
|
|||
}))
|
||||
}
|
||||
|
||||
syncClientTypeSupportPresets(typeSupportPresets) {
|
||||
syncClientTypeSupportPresets(typeSupportPresets: TypeSupportPresets): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
typeSupportPresets,
|
||||
}))
|
||||
}
|
||||
|
||||
async syncAppRoutes() {
|
||||
async syncAppRoutes(): Promise<void> {
|
||||
const resp = await API.fetchAppRoutes()
|
||||
this.update(state => ({
|
||||
...state,
|
||||
|
@ -96,7 +147,7 @@ export class AppMetaStore extends BudiStore {
|
|||
}
|
||||
|
||||
// Returned from socket
|
||||
syncMetadata(metadata) {
|
||||
syncMetadata(metadata: { name: string; url: string; icon?: AppIcon }): void {
|
||||
const { name, url, icon } = metadata
|
||||
this.update(state => ({
|
||||
...state,
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,28 @@
|
|||
import { get } from "svelte/store"
|
||||
import { createBuilderWebsocket } from "./websocket.js"
|
||||
import { Socket } from "socket.io-client"
|
||||
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||
import { BudiStore } from "../BudiStore.js"
|
||||
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
||||
import { App } from "@budibase/types"
|
||||
|
||||
export const INITIAL_BUILDER_STATE = {
|
||||
interface BuilderState {
|
||||
previousTopNavPath: Record<string, string>
|
||||
highlightedSetting: {
|
||||
key: string
|
||||
type: "info" | string
|
||||
} | null
|
||||
propertyFocus: string | null
|
||||
builderSidePanel: boolean
|
||||
onboarding: boolean
|
||||
tourNodes: Record<string, HTMLElement> | null
|
||||
tourKey: string | null
|
||||
tourStepKey: string | null
|
||||
hoveredComponentId: string | null
|
||||
websocket?: Socket
|
||||
}
|
||||
|
||||
export const INITIAL_BUILDER_STATE: BuilderState = {
|
||||
previousTopNavPath: {},
|
||||
highlightedSetting: null,
|
||||
propertyFocus: null,
|
||||
|
@ -16,7 +34,9 @@ export const INITIAL_BUILDER_STATE = {
|
|||
hoveredComponentId: null,
|
||||
}
|
||||
|
||||
export class BuilderStore extends BudiStore {
|
||||
export class BuilderStore extends BudiStore<BuilderState> {
|
||||
websocket?: Socket
|
||||
|
||||
constructor() {
|
||||
super({ ...INITIAL_BUILDER_STATE })
|
||||
|
||||
|
@ -32,11 +52,9 @@ export class BuilderStore extends BudiStore {
|
|||
this.registerTourNode = this.registerTourNode.bind(this)
|
||||
this.destroyTourNode = this.destroyTourNode.bind(this)
|
||||
this.startBuilderOnboarding = this.startBuilderOnboarding.bind(this)
|
||||
|
||||
this.websocket
|
||||
}
|
||||
|
||||
init(app) {
|
||||
init(app: App): void {
|
||||
if (!app?.appId) {
|
||||
console.error("BuilderStore: No appId supplied for websocket")
|
||||
return
|
||||
|
@ -46,45 +64,46 @@ export class BuilderStore extends BudiStore {
|
|||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.store.set(this.store.get())
|
||||
refresh(): void {
|
||||
const currentState = get(this.store)
|
||||
this.store.set(currentState)
|
||||
}
|
||||
|
||||
reset() {
|
||||
reset(): void {
|
||||
this.store.set({ ...INITIAL_BUILDER_STATE })
|
||||
this.websocket?.disconnect()
|
||||
this.websocket = null
|
||||
this.websocket = undefined
|
||||
}
|
||||
|
||||
highlightSetting(key, type) {
|
||||
highlightSetting(key?: string, type?: string): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
highlightedSetting: key ? { key, type: type || "info" } : null,
|
||||
}))
|
||||
}
|
||||
|
||||
propertyFocus(key) {
|
||||
propertyFocus(key: string | null): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
propertyFocus: key,
|
||||
}))
|
||||
}
|
||||
|
||||
showBuilderSidePanel() {
|
||||
showBuilderSidePanel(): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
builderSidePanel: true,
|
||||
}))
|
||||
}
|
||||
|
||||
hideBuilderSidePanel() {
|
||||
hideBuilderSidePanel(): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
builderSidePanel: false,
|
||||
}))
|
||||
}
|
||||
|
||||
setPreviousTopNavPath(route, url) {
|
||||
setPreviousTopNavPath(route: string, url: string): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
previousTopNavPath: {
|
||||
|
@ -94,13 +113,13 @@ export class BuilderStore extends BudiStore {
|
|||
}))
|
||||
}
|
||||
|
||||
selectResource(id) {
|
||||
this.websocket.emit(BuilderSocketEvent.SelectResource, {
|
||||
selectResource(id: string): void {
|
||||
this.websocket?.emit(BuilderSocketEvent.SelectResource, {
|
||||
resourceId: id,
|
||||
})
|
||||
}
|
||||
|
||||
registerTourNode(tourStepKey, node) {
|
||||
registerTourNode(tourStepKey: string, node: HTMLElement): void {
|
||||
this.update(state => {
|
||||
const update = {
|
||||
...state,
|
||||
|
@ -113,7 +132,7 @@ export class BuilderStore extends BudiStore {
|
|||
})
|
||||
}
|
||||
|
||||
destroyTourNode(tourStepKey) {
|
||||
destroyTourNode(tourStepKey: string): void {
|
||||
const store = get(this.store)
|
||||
if (store.tourNodes?.[tourStepKey]) {
|
||||
const nodes = { ...store.tourNodes }
|
||||
|
@ -125,7 +144,7 @@ export class BuilderStore extends BudiStore {
|
|||
}
|
||||
}
|
||||
|
||||
startBuilderOnboarding() {
|
||||
startBuilderOnboarding(): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
onboarding: true,
|
||||
|
@ -133,19 +152,19 @@ export class BuilderStore extends BudiStore {
|
|||
}))
|
||||
}
|
||||
|
||||
endBuilderOnboarding() {
|
||||
endBuilderOnboarding(): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
onboarding: false,
|
||||
}))
|
||||
}
|
||||
|
||||
setTour(tourKey) {
|
||||
setTour(tourKey?: string | null): void {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
tourStepKey: null,
|
||||
tourNodes: null,
|
||||
tourKey: tourKey,
|
||||
tourKey: tourKey || null,
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
export const INITIAL_CONTEXT_MENU_STATE = {
|
||||
id: null,
|
||||
items: [],
|
||||
position: { x: 0, y: 0 },
|
||||
visible: false,
|
||||
}
|
||||
|
||||
export function createViewsStore() {
|
||||
const store = writable({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
|
||||
const open = (id, items, position) => {
|
||||
store.set({ id, items, position, visible: true })
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
open,
|
||||
close,
|
||||
}
|
||||
}
|
||||
|
||||
export const contextMenuStore = createViewsStore()
|
|
@ -0,0 +1,46 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
interface Position {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
label: string
|
||||
icon?: string
|
||||
action: () => void
|
||||
}
|
||||
|
||||
interface ContextMenuState {
|
||||
id: string | null
|
||||
items: MenuItem[]
|
||||
position: Position
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
export const INITIAL_CONTEXT_MENU_STATE: ContextMenuState = {
|
||||
id: null,
|
||||
items: [],
|
||||
position: { x: 0, y: 0 },
|
||||
visible: false,
|
||||
}
|
||||
|
||||
export function createViewsStore() {
|
||||
const store = writable<ContextMenuState>({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
|
||||
const open = (id: string, items: MenuItem[], position: Position): void => {
|
||||
store.set({ id, items, position, visible: true })
|
||||
}
|
||||
|
||||
const close = (): void => {
|
||||
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
open,
|
||||
close,
|
||||
}
|
||||
}
|
||||
|
||||
export const contextMenuStore = createViewsStore()
|
|
@ -1,11 +1,12 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { writable, type Writable } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { DeploymentProgressResponse } from "@budibase/types"
|
||||
|
||||
export const createDeploymentStore = () => {
|
||||
let store = writable([])
|
||||
let store: Writable<DeploymentProgressResponse[]> = writable([])
|
||||
|
||||
const load = async () => {
|
||||
const load = async (): Promise<void> => {
|
||||
try {
|
||||
store.set(await API.getAppDeployments())
|
||||
} catch (err) {
|
|
@ -65,7 +65,7 @@ describe("Builder store", () => {
|
|||
ctx.test.builderStore.reset()
|
||||
expect(disconnected).toBe(true)
|
||||
expect(ctx.test.store).toStrictEqual(INITIAL_BUILDER_STATE)
|
||||
expect(ctx.test.builderStore.websocket).toBeNull()
|
||||
expect(ctx.test.builderStore.websocket).toBeUndefined()
|
||||
})
|
||||
|
||||
it("Attempt to emit a resource select event to the websocket on select", ctx => {
|
||||
|
|
|
@ -22,6 +22,7 @@ export const createLicensingStore = () => {
|
|||
backupsEnabled: false,
|
||||
brandingEnabled: false,
|
||||
scimEnabled: false,
|
||||
environmentVariablesEnabled: false,
|
||||
budibaseAIEnabled: false,
|
||||
customAIConfigsEnabled: false,
|
||||
auditLogsEnabled: false,
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "./src/index.ts",
|
||||
"scripts": {
|
||||
"check:types": "yarn svelte-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "*",
|
||||
"@budibase/shared-core": "*",
|
||||
|
@ -13,5 +16,8 @@
|
|||
"lodash": "4.17.21",
|
||||
"shortid": "2.2.15",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte-check": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
.value {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--content-lines);
|
||||
line-clamp: var(--content-lines);
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 20px;
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
.value {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--content-lines);
|
||||
line-clamp: var(--content-lines);
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 20px;
|
||||
|
|
|
@ -74,12 +74,14 @@
|
|||
.value {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--content-lines);
|
||||
line-clamp: var(--content-lines);
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 20px;
|
||||
}
|
||||
.number .value {
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
}
|
||||
input {
|
||||
flex: 1 1 auto;
|
||||
|
@ -110,5 +112,6 @@
|
|||
}
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,54 @@
|
|||
import { derived, get } from "svelte/store"
|
||||
import { derived, get, Readable, Writable } from "svelte/store"
|
||||
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
||||
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
||||
import { cloneDeep } from "lodash"
|
||||
import { ViewV2Type } from "@budibase/types"
|
||||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveTableRequest,
|
||||
UIDatasource,
|
||||
UIFieldMutation,
|
||||
UIFieldSchema,
|
||||
UpdateViewRequest,
|
||||
ViewV2Type,
|
||||
} from "@budibase/types"
|
||||
import { Store as StoreContext } from "."
|
||||
import { DatasourceActions } from "./datasources"
|
||||
|
||||
export const createStores = () => {
|
||||
interface DatasourceStore {
|
||||
definition: Writable<UIDatasource>
|
||||
schemaMutations: Writable<Record<string, UIFieldMutation>>
|
||||
subSchemaMutations: Writable<Record<string, Record<string, UIFieldMutation>>>
|
||||
}
|
||||
|
||||
interface DerivedDatasourceStore {
|
||||
schema: Readable<Record<string, UIFieldSchema> | null>
|
||||
enrichedSchema: Readable<Record<string, UIFieldSchema> | null>
|
||||
hasBudibaseIdentifiers: Readable<boolean>
|
||||
}
|
||||
|
||||
interface ActionDatasourceStore {
|
||||
datasource: DatasourceStore["definition"] & {
|
||||
actions: DatasourceActions & {
|
||||
refreshDefinition: () => Promise<void>
|
||||
changePrimaryDisplay: (column: string) => Promise<void>
|
||||
addSchemaMutation: (field: string, mutation: UIFieldMutation) => void
|
||||
addSubSchemaMutation: (
|
||||
field: string,
|
||||
fromField: string,
|
||||
mutation: UIFieldMutation
|
||||
) => void
|
||||
saveSchemaMutations: () => Promise<void>
|
||||
resetSchemaMutations: () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = DatasourceStore &
|
||||
DerivedDatasourceStore &
|
||||
ActionDatasourceStore
|
||||
|
||||
export const createStores = (): DatasourceStore => {
|
||||
const definition = memo(null)
|
||||
const schemaMutations = memo({})
|
||||
const subSchemaMutations = memo({})
|
||||
|
@ -16,7 +60,7 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
export const deriveStores = (context: StoreContext): DerivedDatasourceStore => {
|
||||
const {
|
||||
API,
|
||||
definition,
|
||||
|
@ -27,7 +71,7 @@ export const deriveStores = context => {
|
|||
} = context
|
||||
|
||||
const schema = derived(definition, $definition => {
|
||||
let schema = getDatasourceSchema({
|
||||
let schema: Record<string, UIFieldSchema> = getDatasourceSchema({
|
||||
API,
|
||||
datasource: get(datasource),
|
||||
definition: $definition,
|
||||
|
@ -40,7 +84,7 @@ export const deriveStores = context => {
|
|||
// Certain datasources like queries use primitives.
|
||||
Object.keys(schema || {}).forEach(key => {
|
||||
if (typeof schema[key] !== "object") {
|
||||
schema[key] = { type: schema[key] }
|
||||
schema[key] = { name: key, type: schema[key] }
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -58,19 +102,18 @@ export const deriveStores = context => {
|
|||
|
||||
const schemaWithRelatedColumns = enrichSchemaWithRelColumns($schema)
|
||||
|
||||
const enrichedSchema = {}
|
||||
Object.keys(schemaWithRelatedColumns).forEach(field => {
|
||||
const enrichedSchema: Record<string, UIFieldSchema> = {}
|
||||
Object.keys(schemaWithRelatedColumns || {}).forEach(field => {
|
||||
enrichedSchema[field] = {
|
||||
...schemaWithRelatedColumns[field],
|
||||
...schemaWithRelatedColumns?.[field],
|
||||
...$schemaOverrides?.[field],
|
||||
...$schemaMutations[field],
|
||||
}
|
||||
|
||||
if ($subSchemaMutations[field]) {
|
||||
enrichedSchema[field].columns ??= {}
|
||||
for (const [fieldName, mutation] of Object.entries(
|
||||
$subSchemaMutations[field]
|
||||
)) {
|
||||
for (const fieldName of Object.keys($subSchemaMutations[field])) {
|
||||
const mutation = $subSchemaMutations[field][fieldName]
|
||||
enrichedSchema[field].columns[fieldName] = {
|
||||
...enrichedSchema[field].columns[fieldName],
|
||||
...mutation,
|
||||
|
@ -87,7 +130,7 @@ export const deriveStores = context => {
|
|||
([$datasource, $definition]) => {
|
||||
let type = $datasource?.type
|
||||
if (type === "provider") {
|
||||
type = $datasource.value?.datasource?.type
|
||||
type = ($datasource as any).value?.datasource?.type
|
||||
}
|
||||
// Handle calculation views
|
||||
if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) {
|
||||
|
@ -104,7 +147,7 @@ export const deriveStores = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
||||
const {
|
||||
API,
|
||||
datasource,
|
||||
|
@ -147,21 +190,23 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Saves the datasource definition
|
||||
const saveDefinition = async newDefinition => {
|
||||
const saveDefinition = async (
|
||||
newDefinition: SaveTableRequest | UpdateViewRequest
|
||||
) => {
|
||||
// Update local state
|
||||
const originalDefinition = get(definition)
|
||||
definition.set(newDefinition)
|
||||
definition.set(newDefinition as UIDatasource)
|
||||
|
||||
// Update server
|
||||
if (get(config).canSaveSchema) {
|
||||
try {
|
||||
await getAPI()?.actions.saveDefinition(newDefinition)
|
||||
await getAPI()?.actions.saveDefinition(newDefinition as never)
|
||||
|
||||
// Broadcast change so external state can be updated, as this change
|
||||
// will not be received by the builder websocket because we caused it
|
||||
// ourselves
|
||||
dispatch("updatedatasource", newDefinition)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
const msg = error?.message || error || "Unknown error"
|
||||
get(notifications).error(`Error saving schema: ${msg}`)
|
||||
|
||||
|
@ -172,7 +217,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Updates the datasources primary display column
|
||||
const changePrimaryDisplay = async column => {
|
||||
const changePrimaryDisplay = async (column: string) => {
|
||||
let newDefinition = cloneDeep(get(definition))
|
||||
|
||||
// Update primary display
|
||||
|
@ -183,12 +228,14 @@ export const createActions = context => {
|
|||
newDefinition.schema[column].constraints = {}
|
||||
}
|
||||
newDefinition.schema[column].constraints.presence = { allowEmpty: false }
|
||||
delete newDefinition.schema[column].default
|
||||
return await saveDefinition(newDefinition)
|
||||
if ("default" in newDefinition.schema[column]) {
|
||||
delete newDefinition.schema[column].default
|
||||
}
|
||||
return await saveDefinition(newDefinition as any)
|
||||
}
|
||||
|
||||
// Adds a schema mutation for a single field
|
||||
const addSchemaMutation = (field, mutation) => {
|
||||
const addSchemaMutation = (field: string, mutation: UIFieldMutation) => {
|
||||
if (!field || !mutation) {
|
||||
return
|
||||
}
|
||||
|
@ -204,7 +251,11 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Adds a nested schema mutation for a single field
|
||||
const addSubSchemaMutation = (field, fromField, mutation) => {
|
||||
const addSubSchemaMutation = (
|
||||
field: string,
|
||||
fromField: string,
|
||||
mutation: UIFieldMutation
|
||||
) => {
|
||||
if (!field || !fromField || !mutation) {
|
||||
return
|
||||
}
|
||||
|
@ -231,8 +282,8 @@ export const createActions = context => {
|
|||
const $definition = get(definition)
|
||||
const $schemaMutations = get(schemaMutations)
|
||||
const $subSchemaMutations = get(subSchemaMutations)
|
||||
const $schema = get(schema)
|
||||
let newSchema = {}
|
||||
const $schema = get(schema) || {}
|
||||
let newSchema: Record<string, UIFieldSchema> = {}
|
||||
|
||||
// Build new updated datasource schema
|
||||
Object.keys($schema).forEach(column => {
|
||||
|
@ -242,9 +293,8 @@ export const createActions = context => {
|
|||
}
|
||||
if ($subSchemaMutations[column]) {
|
||||
newSchema[column].columns ??= {}
|
||||
for (const [fieldName, mutation] of Object.entries(
|
||||
$subSchemaMutations[column]
|
||||
)) {
|
||||
for (const fieldName of Object.keys($subSchemaMutations[column])) {
|
||||
const mutation = $subSchemaMutations[column][fieldName]
|
||||
newSchema[column].columns[fieldName] = {
|
||||
...newSchema[column].columns[fieldName],
|
||||
...mutation,
|
||||
|
@ -257,7 +307,7 @@ export const createActions = context => {
|
|||
await saveDefinition({
|
||||
...$definition,
|
||||
schema: newSchema,
|
||||
})
|
||||
} as any)
|
||||
resetSchemaMutations()
|
||||
}
|
||||
|
||||
|
@ -267,32 +317,32 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Adds a row to the datasource
|
||||
const addRow = async row => {
|
||||
const addRow = async (row: SaveRowRequest) => {
|
||||
return await getAPI()?.actions.addRow(row)
|
||||
}
|
||||
|
||||
// Updates an existing row in the datasource
|
||||
const updateRow = async row => {
|
||||
const updateRow = async (row: SaveRowRequest) => {
|
||||
return await getAPI()?.actions.updateRow(row)
|
||||
}
|
||||
|
||||
// Deletes rows from the datasource
|
||||
const deleteRows = async rows => {
|
||||
const deleteRows = async (rows: Row[]) => {
|
||||
return await getAPI()?.actions.deleteRows(rows)
|
||||
}
|
||||
|
||||
// Gets a single row from a datasource
|
||||
const getRow = async id => {
|
||||
const getRow = async (id: string) => {
|
||||
return await getAPI()?.actions.getRow(id)
|
||||
}
|
||||
|
||||
// Checks if a certain datasource config is valid
|
||||
const isDatasourceValid = datasource => {
|
||||
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||
return getAPI()?.actions.isDatasourceValid(datasource)
|
||||
}
|
||||
|
||||
// Checks if this datasource can use a specific column by name
|
||||
const canUseColumn = name => {
|
||||
const canUseColumn = (name: string) => {
|
||||
return getAPI()?.actions.canUseColumn(name)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveTableRequest,
|
||||
UIDatasource,
|
||||
UpdateViewRequest,
|
||||
} from "@budibase/types"
|
||||
|
||||
interface DatasourceBaseActions<
|
||||
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
|
||||
> {
|
||||
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||
updateRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||
deleteRows: (rows: Row[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row | void>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean | void
|
||||
canUseColumn: (name: string) => boolean | void
|
||||
}
|
||||
|
||||
export interface DatasourceTableActions
|
||||
extends DatasourceBaseActions<SaveTableRequest> {}
|
||||
|
||||
export interface DatasourceViewActions
|
||||
extends DatasourceBaseActions<UpdateViewRequest> {}
|
||||
|
||||
export interface DatasourceNonPlusActions
|
||||
extends DatasourceBaseActions<never> {}
|
||||
|
||||
export type DatasourceActions =
|
||||
| DatasourceTableActions & DatasourceViewActions & DatasourceNonPlusActions
|
|
@ -1,18 +1,11 @@
|
|||
import { SortOrder, UIDatasource } from "@budibase/types"
|
||||
import { get } from "svelte/store"
|
||||
import { Store as StoreContext } from ".."
|
||||
import { DatasourceNonPlusActions } from "."
|
||||
|
||||
interface NonPlusActions {
|
||||
nonPlus: {
|
||||
actions: {
|
||||
saveDefinition: () => Promise<void>
|
||||
addRow: () => Promise<void>
|
||||
updateRow: () => Promise<void>
|
||||
deleteRows: () => Promise<void>
|
||||
getRow: () => Promise<void>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
actions: DatasourceNonPlusActions
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveRowResponse,
|
||||
SaveTableRequest,
|
||||
SortOrder,
|
||||
UIDatasource,
|
||||
} from "@budibase/types"
|
||||
import { get } from "svelte/store"
|
||||
import { Store as StoreContext } from ".."
|
||||
import { DatasourceTableActions } from "."
|
||||
|
||||
const SuppressErrors = true
|
||||
|
||||
interface TableActions {
|
||||
table: {
|
||||
actions: {
|
||||
saveDefinition: (newDefinition: SaveTableRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<SaveRowResponse>
|
||||
updateRow: (row: SaveRowRequest) => Promise<SaveRowResponse>
|
||||
deleteRows: (rows: (string | Row)[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
actions: DatasourceTableActions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +34,7 @@ export const createActions = (context: StoreContext): TableActions => {
|
|||
return await API.saveRow(row, SuppressErrors)
|
||||
}
|
||||
|
||||
const deleteRows = async (rows: (string | Row)[]) => {
|
||||
const deleteRows = async (rows: Row[]) => {
|
||||
await API.deleteRows(get(datasource).tableId, rows)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,23 +4,17 @@ import {
|
|||
SaveRowRequest,
|
||||
SortOrder,
|
||||
UIDatasource,
|
||||
UIView,
|
||||
UpdateViewRequest,
|
||||
} from "@budibase/types"
|
||||
import { Store as StoreContext } from ".."
|
||||
import { DatasourceViewActions } from "."
|
||||
|
||||
const SuppressErrors = true
|
||||
|
||||
interface ViewActions {
|
||||
viewV2: {
|
||||
actions: {
|
||||
saveDefinition: (newDefinition: UpdateViewRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<Row>
|
||||
updateRow: (row: SaveRowRequest) => Promise<Row>
|
||||
deleteRows: (rows: (string | Row)[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
actions: DatasourceViewActions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +40,7 @@ export const createActions = (context: StoreContext): ViewActions => {
|
|||
}
|
||||
}
|
||||
|
||||
const deleteRows = async (rows: (string | Row)[]) => {
|
||||
const deleteRows = async (rows: Row[]) => {
|
||||
await API.deleteRows(get(datasource).id, rows)
|
||||
}
|
||||
|
||||
|
@ -154,7 +148,7 @@ export const initialise = (context: StoreContext) => {
|
|||
unsubscribers.push(
|
||||
sort.subscribe(async $sort => {
|
||||
// Ensure we're updating the correct view
|
||||
const $view = get(definition)
|
||||
const $view = get(definition) as UIView
|
||||
if ($view?.id !== $datasource.id) {
|
||||
return
|
||||
}
|
||||
|
@ -205,7 +199,7 @@ export const initialise = (context: StoreContext) => {
|
|||
await datasource.actions.saveDefinition({
|
||||
...$view,
|
||||
queryUI: $filter,
|
||||
})
|
||||
} as never as UpdateViewRequest)
|
||||
|
||||
// Refresh data since view definition changed
|
||||
await rows.actions.refreshData()
|
||||
|
|
|
@ -59,11 +59,12 @@ export type Store = BaseStore &
|
|||
Columns.Store &
|
||||
Table.Store &
|
||||
ViewV2.Store &
|
||||
NonPlus.Store & {
|
||||
NonPlus.Store &
|
||||
Datasource.Store &
|
||||
Validation.Store &
|
||||
Users.Store &
|
||||
Menu.Store & {
|
||||
// TODO while typing the rest of stores
|
||||
datasource: Writable<any> & { actions: any }
|
||||
definition: Writable<any>
|
||||
enrichedSchema: any
|
||||
fetch: Writable<any>
|
||||
filter: Writable<any>
|
||||
inlineFilters: Writable<any>
|
||||
|
@ -75,6 +76,16 @@ export type Store = BaseStore &
|
|||
rows: Writable<any> & { actions: any }
|
||||
subscribe: any
|
||||
config: Writable<any>
|
||||
dispatch: (event: string, data: any) => any
|
||||
notifications: Writable<any>
|
||||
schemaOverrides: Writable<any>
|
||||
focusedCellId: Writable<any>
|
||||
previousFocusedRowId: Writable<string>
|
||||
gridID: string
|
||||
selectedRows: Writable<any>
|
||||
selectedRowCount: Writable<any>
|
||||
selectedCellMap: Writable<any>
|
||||
selectedCellCount: Writable<any>
|
||||
}
|
||||
|
||||
export const attachStores = (context: Store): Store => {
|
||||
|
@ -106,5 +117,5 @@ export const attachStores = (context: Store): Store => {
|
|||
}
|
||||
}
|
||||
|
||||
return context
|
||||
return context as Store
|
||||
}
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { writable, get, Writable } from "svelte/store"
|
||||
|
||||
import { Store as StoreContext } from "."
|
||||
import { parseCellID } from "../lib/utils"
|
||||
|
||||
interface MenuStoreData {
|
||||
left: number
|
||||
top: number
|
||||
visible: boolean
|
||||
multiRowMode: boolean
|
||||
multiCellMode: boolean
|
||||
}
|
||||
|
||||
interface MenuStore {
|
||||
menu: Writable<MenuStoreData>
|
||||
}
|
||||
|
||||
export type Store = MenuStore
|
||||
|
||||
export const createStores = () => {
|
||||
const menu = writable({
|
||||
const menu = writable<MenuStoreData>({
|
||||
left: 0,
|
||||
top: 0,
|
||||
visible: false,
|
||||
|
@ -14,7 +30,7 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext) => {
|
||||
const {
|
||||
menu,
|
||||
focusedCellId,
|
||||
|
@ -25,7 +41,7 @@ export const createActions = context => {
|
|||
selectedCellCount,
|
||||
} = context
|
||||
|
||||
const open = (cellId, e) => {
|
||||
const open = (cellId: string, e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -37,7 +53,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Compute bounds of cell relative to outer data node
|
||||
const targetBounds = e.target.getBoundingClientRect()
|
||||
const targetBounds = (e.target as HTMLElement).getBoundingClientRect()
|
||||
const dataBounds = dataNode.getBoundingClientRect()
|
||||
|
||||
// Check if there are multiple rows selected, and if this is one of them
|
|
@ -1,11 +1,38 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||
import { helpers } from "@budibase/shared-core"
|
||||
import { Store as StoreContext } from "."
|
||||
import { UIUser } from "@budibase/types"
|
||||
|
||||
export const createStores = () => {
|
||||
const users = writable([])
|
||||
interface UIEnrichedUser extends UIUser {
|
||||
color: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface UsersStore {
|
||||
users: Writable<UIUser[]>
|
||||
}
|
||||
|
||||
interface DerivedUsersStore {
|
||||
userCellMap: Readable<Record<string, UIUser>>
|
||||
}
|
||||
|
||||
interface ActionUserStore {
|
||||
users: UsersStore["users"] &
|
||||
Readable<UIEnrichedUser[]> & {
|
||||
actions: {
|
||||
updateUser: (user: UIUser) => void
|
||||
removeUser: (sessionId: string) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = DerivedUsersStore & ActionUserStore
|
||||
|
||||
export const createStores = (): UsersStore => {
|
||||
const users = writable<UIUser[]>([])
|
||||
|
||||
const enrichedUsers = derived(users, $users => {
|
||||
return $users.map(user => ({
|
||||
return $users.map<UIEnrichedUser>(user => ({
|
||||
...user,
|
||||
color: helpers.getUserColor(user),
|
||||
label: helpers.getUserLabel(user),
|
||||
|
@ -20,7 +47,7 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
export const deriveStores = (context: StoreContext): DerivedUsersStore => {
|
||||
const { users, focusedCellId } = context
|
||||
|
||||
// Generate a lookup map of cell ID to the user that has it selected, to make
|
||||
|
@ -28,7 +55,7 @@ export const deriveStores = context => {
|
|||
const userCellMap = derived(
|
||||
[users, focusedCellId],
|
||||
([$users, $focusedCellId]) => {
|
||||
let map = {}
|
||||
let map: Record<string, UIUser> = {}
|
||||
$users.forEach(user => {
|
||||
const cellId = user.gridMetadata?.focusedCellId
|
||||
if (cellId && cellId !== $focusedCellId) {
|
||||
|
@ -44,10 +71,10 @@ export const deriveStores = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext): ActionUserStore => {
|
||||
const { users } = context
|
||||
|
||||
const updateUser = user => {
|
||||
const updateUser = (user: UIUser) => {
|
||||
const $users = get(users)
|
||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||
users.set([...$users, user])
|
||||
|
@ -60,7 +87,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
const removeUser = sessionId => {
|
||||
const removeUser = (sessionId: string) => {
|
||||
users.update(state => {
|
||||
return state.filter(x => x.sessionId !== sessionId)
|
||||
})
|
|
@ -1,10 +1,21 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||
import { Store as StoreContext } from "."
|
||||
import { parseCellID } from "../lib/utils"
|
||||
|
||||
interface ValidationStore {
|
||||
validation: Writable<Record<string, string>>
|
||||
}
|
||||
|
||||
interface DerivedValidationStore {
|
||||
validationRowLookupMap: Readable<Record<string, string[]>>
|
||||
}
|
||||
|
||||
export type Store = ValidationStore & DerivedValidationStore
|
||||
|
||||
// Normally we would break out actions into the explicit "createActions"
|
||||
// function, but for validation all these actions are pure so can go into
|
||||
// "createStores" instead to make dependency ordering simpler
|
||||
export const createStores = () => {
|
||||
export const createStores = (): ValidationStore => {
|
||||
const validation = writable({})
|
||||
|
||||
return {
|
||||
|
@ -12,12 +23,12 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
export const deriveStores = (context: StoreContext): DerivedValidationStore => {
|
||||
const { validation } = context
|
||||
|
||||
// Derive which rows have errors so that we can use that info later
|
||||
const validationRowLookupMap = derived(validation, $validation => {
|
||||
let map = {}
|
||||
const map: Record<string, string[]> = {}
|
||||
Object.entries($validation).forEach(([key, error]) => {
|
||||
// Extract row ID from all errored cell IDs
|
||||
if (error) {
|
||||
|
@ -36,10 +47,10 @@ export const deriveStores = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext) => {
|
||||
const { validation, focusedCellId, validationRowLookupMap } = context
|
||||
|
||||
const setError = (cellId, error) => {
|
||||
const setError = (cellId: string | undefined, error: string) => {
|
||||
if (!cellId) {
|
||||
return
|
||||
}
|
||||
|
@ -49,11 +60,11 @@ export const createActions = context => {
|
|||
}))
|
||||
}
|
||||
|
||||
const rowHasErrors = rowId => {
|
||||
const rowHasErrors = (rowId: string) => {
|
||||
return get(validationRowLookupMap)[rowId]?.length > 0
|
||||
}
|
||||
|
||||
const focusFirstRowError = rowId => {
|
||||
const focusFirstRowError = (rowId: string) => {
|
||||
const errorCells = get(validationRowLookupMap)[rowId]
|
||||
const cellId = errorCells?.[0]
|
||||
if (cellId) {
|
||||
|
@ -73,7 +84,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
||||
|
||||
// Remove validation errors when changing rows
|
|
@ -1,103 +0,0 @@
|
|||
import { FieldType, RelationshipType } from "@budibase/types"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
const columnTypeManyTypeOverrides = {
|
||||
[FieldType.DATETIME]: FieldType.STRING,
|
||||
[FieldType.BOOLEAN]: FieldType.STRING,
|
||||
[FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
|
||||
}
|
||||
|
||||
const columnTypeManyParser = {
|
||||
[FieldType.DATETIME]: (value, field) => {
|
||||
function parseDate(value) {
|
||||
const { timeOnly, dateOnly, ignoreTimezones } = field || {}
|
||||
const enableTime = !dateOnly
|
||||
const parsedValue = Helpers.parseDate(value, {
|
||||
timeOnly,
|
||||
enableTime,
|
||||
ignoreTimezones,
|
||||
})
|
||||
const parsed = Helpers.getDateDisplayValue(parsedValue, {
|
||||
enableTime,
|
||||
timeOnly,
|
||||
})
|
||||
return parsed
|
||||
}
|
||||
|
||||
return value.map(v => parseDate(v))
|
||||
},
|
||||
[FieldType.BOOLEAN]: value => value.map(v => !!v),
|
||||
[FieldType.BB_REFERENCE_SINGLE]: value => [
|
||||
...new Map(value.map(i => [i._id, i])).values(),
|
||||
],
|
||||
[FieldType.BB_REFERENCE]: value => [
|
||||
...new Map(value.map(i => [i._id, i])).values(),
|
||||
],
|
||||
[FieldType.ARRAY]: value => Array.from(new Set(value)),
|
||||
}
|
||||
|
||||
export function enrichSchemaWithRelColumns(schema) {
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
const result = Object.keys(schema).reduce((result, fieldName) => {
|
||||
const field = schema[fieldName]
|
||||
result[fieldName] = field
|
||||
|
||||
if (field.visible !== false && field.columns) {
|
||||
const fromSingle =
|
||||
field?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||
|
||||
for (const relColumn of Object.keys(field.columns)) {
|
||||
const relField = field.columns[relColumn]
|
||||
if (!relField.visible) {
|
||||
continue
|
||||
}
|
||||
const name = `${field.name}.${relColumn}`
|
||||
result[name] = {
|
||||
...relField,
|
||||
name,
|
||||
related: { field: fieldName, subField: relColumn },
|
||||
cellRenderType:
|
||||
(!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
|
||||
relField.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function getRelatedTableValues(row, field, fromField) {
|
||||
const fromSingle =
|
||||
fromField?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||
|
||||
let result = ""
|
||||
|
||||
if (fromSingle) {
|
||||
result = row[field.related.field]?.[0]?.[field.related.subField]
|
||||
} else {
|
||||
const parser = columnTypeManyParser[field.type] || (value => value)
|
||||
const value = row[field.related.field]
|
||||
?.flatMap(r => r[field.related.subField])
|
||||
?.filter(i => i !== undefined && i !== null)
|
||||
result = parser(value || [], field)
|
||||
if (
|
||||
[
|
||||
FieldType.STRING,
|
||||
FieldType.NUMBER,
|
||||
FieldType.BIGINT,
|
||||
FieldType.BOOLEAN,
|
||||
FieldType.DATETIME,
|
||||
FieldType.LONGFORM,
|
||||
FieldType.BARCODEQR,
|
||||
].includes(field.type)
|
||||
) {
|
||||
result = result?.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
import { Helpers } from "@budibase/bbui"
|
||||
import {
|
||||
FieldType,
|
||||
isRelationshipField,
|
||||
RelationshipType,
|
||||
Row,
|
||||
UIFieldSchema,
|
||||
} from "@budibase/types"
|
||||
|
||||
const columnTypeManyTypeOverrides: Partial<Record<FieldType, FieldType>> = {
|
||||
[FieldType.DATETIME]: FieldType.STRING,
|
||||
[FieldType.BOOLEAN]: FieldType.STRING,
|
||||
[FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
|
||||
}
|
||||
|
||||
const columnTypeManyParser = {
|
||||
[FieldType.DATETIME]: (
|
||||
value: any[],
|
||||
field: {
|
||||
timeOnly?: boolean
|
||||
dateOnly?: boolean
|
||||
}
|
||||
) => {
|
||||
function parseDate(value: any) {
|
||||
const { timeOnly, dateOnly } = field || {}
|
||||
const enableTime = !dateOnly
|
||||
const parsedValue = Helpers.parseDate(value, { enableTime })
|
||||
const parsed = Helpers.getDateDisplayValue(parsedValue, {
|
||||
enableTime,
|
||||
timeOnly,
|
||||
})
|
||||
return parsed
|
||||
}
|
||||
|
||||
return value.map(v => parseDate(v))
|
||||
},
|
||||
[FieldType.BOOLEAN]: (value: any[]) => value.map(v => !!v),
|
||||
[FieldType.BB_REFERENCE_SINGLE]: (value: any[]) => [
|
||||
...new Map(value.map(i => [i._id, i])).values(),
|
||||
],
|
||||
[FieldType.BB_REFERENCE]: (value: any[]) => [
|
||||
...new Map(value.map(i => [i._id, i])).values(),
|
||||
],
|
||||
[FieldType.ARRAY]: (value: any[]) => Array.from(new Set(value)),
|
||||
}
|
||||
|
||||
export function enrichSchemaWithRelColumns(
|
||||
schema: Record<string, UIFieldSchema>
|
||||
): Record<string, UIFieldSchema> | undefined {
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
const result = Object.keys(schema).reduce<Record<string, UIFieldSchema>>(
|
||||
(result, fieldName) => {
|
||||
const field = schema[fieldName]
|
||||
result[fieldName] = field
|
||||
|
||||
if (
|
||||
field.visible !== false &&
|
||||
isRelationshipField(field) &&
|
||||
field.columns
|
||||
) {
|
||||
const fromSingle =
|
||||
field?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||
|
||||
for (const relColumn of Object.keys(field.columns)) {
|
||||
const relField = field.columns[relColumn]
|
||||
if (!relField.visible) {
|
||||
continue
|
||||
}
|
||||
const name = `${field.name}.${relColumn}`
|
||||
result[name] = {
|
||||
...relField,
|
||||
type: relField.type as any, // TODO
|
||||
name,
|
||||
related: { field: fieldName, subField: relColumn },
|
||||
cellRenderType:
|
||||
(!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
|
||||
relField.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function getRelatedTableValues(
|
||||
row: Row,
|
||||
field: UIFieldSchema & { related: { field: string; subField: string } },
|
||||
fromField: UIFieldSchema
|
||||
) {
|
||||
const fromSingle =
|
||||
isRelationshipField(fromField) &&
|
||||
fromField?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||
|
||||
let result = ""
|
||||
|
||||
if (fromSingle) {
|
||||
result = row[field.related.field]?.[0]?.[field.related.subField]
|
||||
} else {
|
||||
const parser =
|
||||
columnTypeManyParser[field.type as keyof typeof columnTypeManyParser] ||
|
||||
((value: any) => value)
|
||||
const value = row[field.related.field]
|
||||
?.flatMap((r: Row) => r[field.related.subField])
|
||||
?.filter((i: any) => i !== undefined && i !== null)
|
||||
const parsed = parser(value || [], field as any)
|
||||
result = parsed as any
|
||||
if (
|
||||
[
|
||||
FieldType.STRING,
|
||||
FieldType.NUMBER,
|
||||
FieldType.BIGINT,
|
||||
FieldType.BOOLEAN,
|
||||
FieldType.DATETIME,
|
||||
FieldType.LONGFORM,
|
||||
FieldType.BARCODEQR,
|
||||
].includes(field.type)
|
||||
) {
|
||||
result = parsed?.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./dist",
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@budibase/types": ["../types/src"],
|
||||
"@budibase/shared-core": ["../shared-core/src"],
|
||||
"@budibase/bbui": ["../bbui/src"]
|
||||
}
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
|
|
@ -456,7 +456,7 @@ export function filterAutomation(appId: string, tableId?: string): Automation {
|
|||
icon: "Icon",
|
||||
id: "a",
|
||||
type: AutomationStepType.TRIGGER,
|
||||
event: "row:save",
|
||||
event: AutomationEventType.ROW_SAVE,
|
||||
stepId: AutomationTriggerStepId.ROW_SAVED,
|
||||
inputs: {
|
||||
tableId: tableId!,
|
||||
|
@ -498,7 +498,7 @@ export function updateRowAutomationWithFilters(
|
|||
icon: "Icon",
|
||||
id: "a",
|
||||
type: AutomationStepType.TRIGGER,
|
||||
event: "row:update",
|
||||
event: AutomationEventType.ROW_UPDATE,
|
||||
stepId: AutomationTriggerStepId.ROW_UPDATED,
|
||||
inputs: { tableId },
|
||||
schema: TRIGGER_DEFINITIONS.ROW_UPDATED.schema,
|
||||
|
@ -513,7 +513,7 @@ export function basicAutomationResults(
|
|||
return {
|
||||
automationId,
|
||||
status: AutomationStatus.SUCCESS,
|
||||
trigger: "trigger",
|
||||
trigger: "trigger" as any,
|
||||
steps: [
|
||||
{
|
||||
stepId: AutomationActionStepId.SERVER_LOG,
|
||||
|
|
|
@ -148,6 +148,7 @@ export interface Automation extends Document {
|
|||
|
||||
interface BaseIOStructure {
|
||||
type?: AutomationIOType
|
||||
subtype?: AutomationIOType
|
||||
customType?: AutomationCustomIOType
|
||||
title?: string
|
||||
description?: string
|
||||
|
@ -192,7 +193,7 @@ export enum AutomationStoppedReason {
|
|||
export interface AutomationResults {
|
||||
automationId?: string
|
||||
status?: AutomationStatus
|
||||
trigger?: any
|
||||
trigger?: AutomationTrigger
|
||||
steps: {
|
||||
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||
inputs: {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
AutomationFeature,
|
||||
InputOutputBlock,
|
||||
AutomationTriggerStepId,
|
||||
AutomationEventType,
|
||||
} from "./automation"
|
||||
import {
|
||||
CollectStepInputs,
|
||||
|
@ -142,6 +143,7 @@ export type ActionImplementations<T extends Hosting> = {
|
|||
export interface AutomationStepSchemaBase {
|
||||
name: string
|
||||
stepTitle?: string
|
||||
event?: AutomationEventType
|
||||
tagline: string
|
||||
icon: string
|
||||
description: string
|
||||
|
@ -344,7 +346,7 @@ export interface AutomationTriggerSchema<
|
|||
> extends AutomationStepSchemaBase {
|
||||
id: string
|
||||
type: AutomationStepType.TRIGGER
|
||||
event?: string
|
||||
event?: AutomationEventType
|
||||
cronJobId?: string
|
||||
stepId: TTrigger
|
||||
inputs: AutomationTriggerInputs<TTrigger> & Record<string, any> // The record union to be removed once the types are fixed
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export interface BranchPath {
|
||||
stepIdx: number
|
||||
branchIdx: number
|
||||
branchStepId: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface BlockDefinitions {
|
||||
TRIGGER: Record<string, any>
|
||||
CREATABLE_TRIGGER: Record<string, any>
|
||||
ACTION: Record<string, any>
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
export interface UIDatasource {
|
||||
import { UITable, UIView } from "@budibase/types"
|
||||
|
||||
export type UIDatasource = (UITable | UIView) & {
|
||||
type: string
|
||||
id: string
|
||||
tableId: string
|
||||
}
|
||||
|
||||
export interface UIFieldMutation {
|
||||
visible?: boolean
|
||||
readonly?: boolean
|
||||
width?: number
|
||||
}
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
export * from "./columns"
|
||||
export * from "./datasource"
|
||||
export * from "./table"
|
||||
export * from "./view"
|
||||
export * from "./user"
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
BasicViewFieldMetadata,
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
RelationSchemaField,
|
||||
SortOrder,
|
||||
Table,
|
||||
UISearchFilter,
|
||||
} from "@budibase/types"
|
||||
|
||||
export interface UITable extends Omit<Table, "type"> {
|
||||
name: string
|
||||
id: string
|
||||
type: string
|
||||
tableId: string
|
||||
primaryDisplay?: string
|
||||
sort?: {
|
||||
field: string
|
||||
order: SortOrder
|
||||
}
|
||||
queryUI: UISearchFilter
|
||||
schema: Record<string, UIFieldSchema>
|
||||
}
|
||||
|
||||
export type UIFieldSchema = FieldSchema &
|
||||
BasicViewFieldMetadata & {
|
||||
related?: { field: string; subField: string }
|
||||
columns?: Record<string, UIRelationSchemaField>
|
||||
cellRenderType?: string
|
||||
}
|
||||
|
||||
interface UIRelationSchemaField extends RelationSchemaField {
|
||||
type: FieldType
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { User } from "@budibase/types"
|
||||
|
||||
export interface UIUser extends User {
|
||||
sessionId: string
|
||||
gridMetadata?: { focusedCellId?: string }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { ViewV2 } from "@budibase/types"
|
||||
import { UIFieldSchema } from "./table"
|
||||
|
||||
export interface UIView extends ViewV2 {
|
||||
schema: Record<string, UIFieldSchema>
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./integration"
|
||||
export * from "./automations"
|
||||
export * from "./grid"
|
||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -5946,6 +5946,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/shortid@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-2.2.0.tgz#905990fc4275f77e60ab0cd9f791b91a3d4bff04"
|
||||
integrity sha512-jBG2FgBxcaSf0h662YloTGA32M8UtNbnTPekUr/eCmWXq0JWQXgNEQ/P5Gf05Cv66QZtE1Ttr83I1AJBPdzCBg==
|
||||
|
||||
"@types/ssh2-streams@*":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
|
||||
|
@ -18639,16 +18644,7 @@ string-length@^4.0.1:
|
|||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -18740,7 +18736,7 @@ stringify-object@^3.2.1:
|
|||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
@ -18754,13 +18750,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
|||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||
|
@ -20508,7 +20497,7 @@ worker-farm@1.7.0:
|
|||
dependencies:
|
||||
errno "~0.1.7"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
@ -20526,15 +20515,6 @@ wrap-ansi@^5.1.0:
|
|||
string-width "^3.0.0"
|
||||
strip-ansi "^5.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
|
Loading…
Reference in New Issue