Merge branch 'master' into type-portal-backups-store
This commit is contained in:
commit
cf3236c1e0
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "3.2.29",
|
||||
"version": "3.2.32",
|
||||
"npmClient": "yarn",
|
||||
"concurrency": 20,
|
||||
"command": {
|
||||
|
|
|
@ -291,8 +291,8 @@ const automationActions = (store: AutomationStore) => ({
|
|||
let result: (AutomationStep | AutomationTrigger)[] = []
|
||||
pathWay.forEach(path => {
|
||||
const { stepIdx, branchIdx } = path
|
||||
let last = result ? result[result.length - 1] : []
|
||||
if (!result) {
|
||||
let last = result.length ? result[result.length - 1] : []
|
||||
if (!result.length) {
|
||||
// Preceeding steps.
|
||||
result = steps.slice(0, stepIdx + 1)
|
||||
return
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { API } from "@/api"
|
||||
|
||||
export function createFlagsStore() {
|
||||
const { subscribe, set } = writable({})
|
||||
|
||||
const actions = {
|
||||
fetch: async () => {
|
||||
const flags = await API.getFlags()
|
||||
set(flags)
|
||||
},
|
||||
updateFlag: async (flag, value) => {
|
||||
await API.updateFlag(flag, value)
|
||||
await actions.fetch()
|
||||
},
|
||||
toggleUiFeature: async feature => {
|
||||
await API.toggleUiFeature(feature)
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
...actions,
|
||||
}
|
||||
}
|
||||
|
||||
export const flags = createFlagsStore()
|
|
@ -0,0 +1,40 @@
|
|||
import { API } from "@/api"
|
||||
import { GetUserFlagsResponse } from "@budibase/types"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
|
||||
interface FlagsState {
|
||||
flags: GetUserFlagsResponse
|
||||
}
|
||||
|
||||
const INITIAL_FLAGS_STATE: FlagsState = {
|
||||
flags: {},
|
||||
}
|
||||
|
||||
export class FlagsStore extends BudiStore<FlagsState> {
|
||||
constructor() {
|
||||
super(INITIAL_FLAGS_STATE)
|
||||
|
||||
this.fetch = this.fetch.bind(this)
|
||||
this.updateFlag = this.updateFlag.bind(this)
|
||||
this.toggleUiFeature = this.toggleUiFeature.bind(this)
|
||||
}
|
||||
|
||||
async fetch() {
|
||||
const flags = await API.getFlags()
|
||||
this.update(state => ({
|
||||
...state,
|
||||
flags,
|
||||
}))
|
||||
}
|
||||
|
||||
async updateFlag(flag: string, value: any) {
|
||||
await API.updateFlag(flag, value)
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async toggleUiFeature(feature: string) {
|
||||
await API.toggleUiFeature(feature)
|
||||
}
|
||||
}
|
||||
|
||||
export const flags = new FlagsStore()
|
|
@ -2,13 +2,19 @@ import { derived, get } from "svelte/store"
|
|||
import { componentStore } from "@/stores/builder"
|
||||
import { API } from "@/api"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { Layout } from "@budibase/types"
|
||||
|
||||
export const INITIAL_LAYOUT_STATE = {
|
||||
interface LayoutState {
|
||||
layouts: Layout[]
|
||||
selectedLayoutId: string | null
|
||||
}
|
||||
|
||||
export const INITIAL_LAYOUT_STATE: LayoutState = {
|
||||
layouts: [],
|
||||
selectedLayoutId: null,
|
||||
}
|
||||
|
||||
export class LayoutStore extends BudiStore {
|
||||
export class LayoutStore extends BudiStore<LayoutState> {
|
||||
constructor() {
|
||||
super(INITIAL_LAYOUT_STATE)
|
||||
|
||||
|
@ -22,14 +28,14 @@ export class LayoutStore extends BudiStore {
|
|||
this.store.set({ ...INITIAL_LAYOUT_STATE })
|
||||
}
|
||||
|
||||
syncAppLayouts(pkg) {
|
||||
syncAppLayouts(pkg: { layouts: Layout[] }) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
layouts: [...pkg.layouts],
|
||||
}))
|
||||
}
|
||||
|
||||
select(layoutId) {
|
||||
select(layoutId: string) {
|
||||
// Check this layout exists
|
||||
const state = get(this.store)
|
||||
const componentState = get(componentStore)
|
||||
|
@ -48,15 +54,15 @@ export class LayoutStore extends BudiStore {
|
|||
|
||||
// Select new layout
|
||||
this.update(state => {
|
||||
state.selectedLayoutId = layout._id
|
||||
state.selectedLayoutId = layout._id!
|
||||
return state
|
||||
})
|
||||
|
||||
componentStore.select(layout.props?._id)
|
||||
}
|
||||
|
||||
async deleteLayout(layout) {
|
||||
if (!layout?._id) {
|
||||
async deleteLayout(layout: Layout) {
|
||||
if (!layout?._id || !layout?._rev) {
|
||||
return
|
||||
}
|
||||
await API.deleteLayout(layout._id, layout._rev)
|
|
@ -1,80 +0,0 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
const INITIAL_PREVIEW_STATE = {
|
||||
previewDevice: "desktop",
|
||||
previewEventHandler: null,
|
||||
showPreview: false,
|
||||
selectedComponentContext: null,
|
||||
}
|
||||
|
||||
export const createPreviewStore = () => {
|
||||
const store = writable({
|
||||
...INITIAL_PREVIEW_STATE,
|
||||
})
|
||||
|
||||
const setDevice = device => {
|
||||
store.update(state => {
|
||||
state.previewDevice = device
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Potential evt names "eject-block", "dragging-new-component"
|
||||
const sendEvent = (name, payload) => {
|
||||
const { previewEventHandler } = get(store)
|
||||
previewEventHandler?.(name, payload)
|
||||
}
|
||||
|
||||
const registerEventHandler = handler => {
|
||||
store.update(state => {
|
||||
state.previewEventHandler = handler
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const startDrag = component => {
|
||||
sendEvent("dragging-new-component", {
|
||||
dragging: true,
|
||||
component,
|
||||
})
|
||||
}
|
||||
|
||||
const stopDrag = () => {
|
||||
sendEvent("dragging-new-component", {
|
||||
dragging: false,
|
||||
})
|
||||
}
|
||||
|
||||
//load preview?
|
||||
const showPreview = isVisible => {
|
||||
store.update(state => {
|
||||
state.showPreview = isVisible
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const setSelectedComponentContext = context => {
|
||||
store.update(state => {
|
||||
state.selectedComponentContext = context
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const requestComponentContext = () => {
|
||||
sendEvent("request-context")
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
setDevice,
|
||||
sendEvent, //may not be required
|
||||
registerEventHandler,
|
||||
startDrag,
|
||||
stopDrag,
|
||||
showPreview,
|
||||
setSelectedComponentContext,
|
||||
requestComponentContext,
|
||||
}
|
||||
}
|
||||
|
||||
export const previewStore = createPreviewStore()
|
|
@ -0,0 +1,90 @@
|
|||
import { get } from "svelte/store"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
|
||||
type PreviewDevice = "desktop" | "tablet" | "mobile"
|
||||
type PreviewEventHandler = (name: string, payload?: any) => void
|
||||
type ComponentContext = Record<string, any>
|
||||
|
||||
interface PreviewState {
|
||||
previewDevice: PreviewDevice
|
||||
previewEventHandler: PreviewEventHandler | null
|
||||
showPreview: boolean
|
||||
selectedComponentContext: ComponentContext | null
|
||||
}
|
||||
|
||||
const INITIAL_PREVIEW_STATE: PreviewState = {
|
||||
previewDevice: "desktop",
|
||||
previewEventHandler: null,
|
||||
showPreview: false,
|
||||
selectedComponentContext: null,
|
||||
}
|
||||
|
||||
export class PreviewStore extends BudiStore<PreviewState> {
|
||||
constructor() {
|
||||
super(INITIAL_PREVIEW_STATE)
|
||||
|
||||
this.setDevice = this.setDevice.bind(this)
|
||||
this.sendEvent = this.sendEvent.bind(this)
|
||||
this.registerEventHandler = this.registerEventHandler.bind(this)
|
||||
this.startDrag = this.startDrag.bind(this)
|
||||
this.stopDrag = this.stopDrag.bind(this)
|
||||
this.showPreview = this.showPreview.bind(this)
|
||||
this.setSelectedComponentContext =
|
||||
this.setSelectedComponentContext.bind(this)
|
||||
this.requestComponentContext = this.requestComponentContext.bind(this)
|
||||
}
|
||||
|
||||
setDevice(device: PreviewDevice) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
previewDevice: device,
|
||||
}))
|
||||
}
|
||||
|
||||
// Potential evt names "eject-block", "dragging-new-component"
|
||||
sendEvent(name: string, payload?: any) {
|
||||
const { previewEventHandler } = get(this.store)
|
||||
previewEventHandler?.(name, payload)
|
||||
}
|
||||
|
||||
registerEventHandler(handler: PreviewEventHandler) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
previewEventHandler: handler,
|
||||
}))
|
||||
}
|
||||
|
||||
startDrag(component: any) {
|
||||
this.sendEvent("dragging-new-component", {
|
||||
dragging: true,
|
||||
component,
|
||||
})
|
||||
}
|
||||
|
||||
stopDrag() {
|
||||
this.sendEvent("dragging-new-component", {
|
||||
dragging: false,
|
||||
})
|
||||
}
|
||||
|
||||
//load preview?
|
||||
showPreview(isVisible: boolean) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
showPreview: isVisible,
|
||||
}))
|
||||
}
|
||||
|
||||
setSelectedComponentContext(context: ComponentContext) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
selectedComponentContext: context,
|
||||
}))
|
||||
}
|
||||
|
||||
requestComponentContext() {
|
||||
this.sendEvent("request-context")
|
||||
}
|
||||
}
|
||||
|
||||
export const previewStore = new PreviewStore()
|
|
@ -1,62 +0,0 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
export const createUserStore = () => {
|
||||
const store = writable([])
|
||||
|
||||
const init = users => {
|
||||
store.set(users)
|
||||
}
|
||||
|
||||
const updateUser = user => {
|
||||
const $users = get(store)
|
||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||
store.set([...$users, user])
|
||||
} else {
|
||||
store.update(state => {
|
||||
const index = state.findIndex(x => x.sessionId === user.sessionId)
|
||||
state[index] = user
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeUser = sessionId => {
|
||||
store.update(state => {
|
||||
return state.filter(x => x.sessionId !== sessionId)
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
store.set([])
|
||||
}
|
||||
|
||||
return {
|
||||
...store,
|
||||
actions: {
|
||||
init,
|
||||
updateUser,
|
||||
removeUser,
|
||||
reset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const userStore = createUserStore()
|
||||
|
||||
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||
let map = {}
|
||||
$userStore.forEach(user => {
|
||||
const resource = user.builderMetadata?.selectedResourceId
|
||||
if (resource) {
|
||||
if (!map[resource]) {
|
||||
map[resource] = []
|
||||
}
|
||||
map[resource].push(user)
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
export const isOnlyUser = derived(userStore, $userStore => {
|
||||
return $userStore.length < 2
|
||||
})
|
|
@ -0,0 +1,59 @@
|
|||
import { get, derived } from "svelte/store"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { UIUser } from "@budibase/types"
|
||||
|
||||
export class UserStore extends BudiStore<UIUser[]> {
|
||||
constructor() {
|
||||
super([])
|
||||
}
|
||||
|
||||
init(users: UIUser[]) {
|
||||
this.set(users)
|
||||
}
|
||||
|
||||
updateUser(user: UIUser) {
|
||||
const $users = get(this)
|
||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||
this.set([...$users, user])
|
||||
} else {
|
||||
this.update(state => {
|
||||
const index = state.findIndex(x => x.sessionId === user.sessionId)
|
||||
state[index] = user
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
removeUser(sessionId: string) {
|
||||
this.update(state => {
|
||||
return state.filter(x => x.sessionId !== sessionId)
|
||||
})
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.set([])
|
||||
}
|
||||
}
|
||||
|
||||
export const userStore = new UserStore()
|
||||
|
||||
export const userSelectedResourceMap = derived(
|
||||
userStore,
|
||||
($userStore): Record<string, UIUser[]> => {
|
||||
let map: Record<string, UIUser[]> = {}
|
||||
$userStore.forEach(user => {
|
||||
const resource = user.builderMetadata?.selectedResourceId
|
||||
if (resource) {
|
||||
if (!map[resource]) {
|
||||
map[resource] = []
|
||||
}
|
||||
map[resource].push(user)
|
||||
}
|
||||
})
|
||||
return map
|
||||
}
|
||||
)
|
||||
|
||||
export const isOnlyUser = derived(userStore, $userStore => {
|
||||
return $userStore.length < 2
|
||||
})
|
|
@ -1,95 +0,0 @@
|
|||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
import {
|
||||
automationStore,
|
||||
userStore,
|
||||
appStore,
|
||||
themeStore,
|
||||
navigationStore,
|
||||
deploymentStore,
|
||||
snippets,
|
||||
datasources,
|
||||
tables,
|
||||
roles,
|
||||
} from "@/stores/builder"
|
||||
import { get } from "svelte/store"
|
||||
import { auth, appsStore } from "@/stores/portal"
|
||||
import { screenStore } from "./screens"
|
||||
import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export const createBuilderWebsocket = appId => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
||||
// Built-in events
|
||||
socket.on("connect", () => {
|
||||
socket.emit(BuilderSocketEvent.SelectApp, { appId }, ({ users }) => {
|
||||
userStore.actions.init(users)
|
||||
})
|
||||
})
|
||||
socket.on("connect_error", err => {
|
||||
console.error("Failed to connect to builder websocket:", err.message)
|
||||
})
|
||||
socket.on("disconnect", () => {
|
||||
userStore.actions.reset()
|
||||
})
|
||||
|
||||
// User events
|
||||
socket.onOther(SocketEvent.UserUpdate, ({ user }) => {
|
||||
userStore.actions.updateUser(user)
|
||||
})
|
||||
socket.onOther(SocketEvent.UserDisconnect, ({ sessionId }) => {
|
||||
userStore.actions.removeUser(sessionId)
|
||||
})
|
||||
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
||||
if (userId === get(auth)?.user?._id) {
|
||||
appStore.update(state => ({
|
||||
...state,
|
||||
hasLock: true,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Data section events
|
||||
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
||||
tables.replaceTable(id, table)
|
||||
})
|
||||
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
||||
datasources.replaceDatasource(id, datasource)
|
||||
})
|
||||
|
||||
// Role events
|
||||
socket.onOther(BuilderSocketEvent.RoleChange, ({ id, role }) => {
|
||||
roles.replace(id, role)
|
||||
})
|
||||
|
||||
// Design section events
|
||||
socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => {
|
||||
screenStore.replace(id, screen)
|
||||
})
|
||||
|
||||
// App events
|
||||
socket.onOther(BuilderSocketEvent.AppMetadataChange, ({ metadata }) => {
|
||||
appStore.syncMetadata(metadata)
|
||||
themeStore.syncMetadata(metadata)
|
||||
navigationStore.syncMetadata(metadata)
|
||||
snippets.syncMetadata(metadata)
|
||||
})
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppPublishChange,
|
||||
async ({ user, published }) => {
|
||||
await appsStore.load()
|
||||
if (published) {
|
||||
await deploymentStore.load()
|
||||
}
|
||||
const verb = published ? "published" : "unpublished"
|
||||
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||
}
|
||||
)
|
||||
|
||||
// Automation events
|
||||
socket.onOther(BuilderSocketEvent.AutomationChange, ({ id, automation }) => {
|
||||
automationStore.actions.replace(id, automation)
|
||||
})
|
||||
|
||||
return socket
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
import {
|
||||
automationStore,
|
||||
userStore,
|
||||
appStore,
|
||||
themeStore,
|
||||
navigationStore,
|
||||
deploymentStore,
|
||||
snippets,
|
||||
datasources,
|
||||
tables,
|
||||
roles,
|
||||
} from "@/stores/builder"
|
||||
import { get } from "svelte/store"
|
||||
import { auth, appsStore } from "@/stores/portal"
|
||||
import { screenStore } from "./screens"
|
||||
import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Automation, Datasource, Role, Table, UIUser } from "@budibase/types"
|
||||
|
||||
export const createBuilderWebsocket = (appId: string) => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
||||
// Built-in events
|
||||
socket.on("connect", () => {
|
||||
socket.emit(
|
||||
BuilderSocketEvent.SelectApp,
|
||||
{ appId },
|
||||
({ users }: { users: UIUser[] }) => {
|
||||
userStore.init(users)
|
||||
}
|
||||
)
|
||||
})
|
||||
socket.on("connect_error", err => {
|
||||
console.error("Failed to connect to builder websocket:", err.message)
|
||||
})
|
||||
socket.on("disconnect", () => {
|
||||
userStore.reset()
|
||||
})
|
||||
|
||||
// User events
|
||||
socket.onOther(SocketEvent.UserUpdate, ({ user }: { user: UIUser }) => {
|
||||
userStore.updateUser(user)
|
||||
})
|
||||
socket.onOther(
|
||||
SocketEvent.UserDisconnect,
|
||||
({ sessionId }: { sessionId: string }) => {
|
||||
userStore.removeUser(sessionId)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.LockTransfer,
|
||||
({ userId }: { userId: string }) => {
|
||||
if (userId === get(auth)?.user?._id) {
|
||||
appStore.update(state => ({
|
||||
...state,
|
||||
hasLock: true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Data section events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.TableChange,
|
||||
({ id, table }: { id: string; table: Table }) => {
|
||||
tables.replaceTable(id, table)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.DatasourceChange,
|
||||
({ id, datasource }: { id: string; datasource: Datasource }) => {
|
||||
datasources.replaceDatasource(id, datasource)
|
||||
}
|
||||
)
|
||||
|
||||
// Role events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.RoleChange,
|
||||
({ id, role }: { id: string; role: Role }) => {
|
||||
roles.replace(id, role)
|
||||
}
|
||||
)
|
||||
|
||||
// Design section events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.ScreenChange,
|
||||
({ id, screen }: { id: string; screen: Screen }) => {
|
||||
screenStore.replace(id, screen)
|
||||
}
|
||||
)
|
||||
|
||||
// App events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppMetadataChange,
|
||||
({ metadata }: { metadata: any }) => {
|
||||
appStore.syncMetadata(metadata)
|
||||
themeStore.syncMetadata(metadata)
|
||||
navigationStore.syncMetadata(metadata)
|
||||
snippets.syncMetadata(metadata)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppPublishChange,
|
||||
async ({ user, published }: { user: UIUser; published: boolean }) => {
|
||||
await appsStore.load()
|
||||
if (published) {
|
||||
await deploymentStore.load()
|
||||
}
|
||||
const verb = published ? "published" : "unpublished"
|
||||
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||
}
|
||||
)
|
||||
|
||||
// Automation events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AutomationChange,
|
||||
({ id, automation }: { id: string; automation: Automation }) => {
|
||||
automationStore.actions.replace(id, automation)
|
||||
}
|
||||
)
|
||||
|
||||
return socket
|
||||
}
|
|
@ -26,7 +26,7 @@
|
|||
: RelationshipType.MANY_TO_MANY,
|
||||
}
|
||||
|
||||
async function searchFunction(searchParams) {
|
||||
async function searchFunction(_tableId, searchParams) {
|
||||
if (
|
||||
subtype !== BBReferenceFieldSubType.USER &&
|
||||
subtype !== BBReferenceFieldSubType.USERS
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { io } from "socket.io-client"
|
||||
import { io, Socket } from "socket.io-client"
|
||||
import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
|
||||
import { APISessionID } from "../api"
|
||||
|
||||
const DefaultOptions = {
|
||||
heartbeat: true,
|
||||
}
|
||||
export interface ExtendedSocket extends Socket {
|
||||
onOther: (event: string, callback: (data: any) => void) => void
|
||||
}
|
||||
|
||||
export const createWebsocket = (path, options = DefaultOptions) => {
|
||||
export const createWebsocket = (
|
||||
path: string,
|
||||
options = DefaultOptions
|
||||
): ExtendedSocket => {
|
||||
if (!path) {
|
||||
throw "A websocket path must be provided"
|
||||
}
|
||||
|
@ -32,10 +38,10 @@ export const createWebsocket = (path, options = DefaultOptions) => {
|
|||
// Disable polling and rely on websocket only, as HTTP transport
|
||||
// will only work with sticky sessions which we don't have
|
||||
transports: ["websocket"],
|
||||
})
|
||||
}) as ExtendedSocket
|
||||
|
||||
// Set up a heartbeat that's half of the session TTL
|
||||
let interval
|
||||
let interval: NodeJS.Timeout | undefined
|
||||
if (heartbeat) {
|
||||
interval = setInterval(() => {
|
||||
socket.emit(SocketEvent.Heartbeat)
|
||||
|
@ -48,7 +54,7 @@ export const createWebsocket = (path, options = DefaultOptions) => {
|
|||
|
||||
// Helper utility to ignore events that were triggered due to API
|
||||
// changes made by us
|
||||
socket.onOther = (event, callback) => {
|
||||
socket.onOther = (event: string, callback: (data: any) => void) => {
|
||||
socket.on(event, data => {
|
||||
if (data?.apiSessionId !== APISessionID) {
|
||||
callback(data)
|
|
@ -174,7 +174,9 @@ class QueryRunner {
|
|||
}
|
||||
|
||||
// needs to an array for next step
|
||||
if (!Array.isArray(rows)) {
|
||||
if (rows === null) {
|
||||
rows = []
|
||||
} else if (!Array.isArray(rows)) {
|
||||
rows = [rows]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ import { User } from "@budibase/types"
|
|||
export interface UIUser extends User {
|
||||
sessionId: string
|
||||
gridMetadata?: { focusedCellId?: string }
|
||||
builderMetadata?: { selectedResourceId?: string }
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./integration"
|
||||
export * from "./automations"
|
||||
export * from "./grid"
|
||||
export * from "./preview"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export type PreviewDevice = "desktop" | "tablet" | "mobile"
|
Loading…
Reference in New Issue