Merge branch 'master' into type-portal-env-store
This commit is contained in:
commit
a193cf6629
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.29",
|
"version": "3.2.32",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -291,8 +291,8 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
let result: (AutomationStep | AutomationTrigger)[] = []
|
let result: (AutomationStep | AutomationTrigger)[] = []
|
||||||
pathWay.forEach(path => {
|
pathWay.forEach(path => {
|
||||||
const { stepIdx, branchIdx } = path
|
const { stepIdx, branchIdx } = path
|
||||||
let last = result ? result[result.length - 1] : []
|
let last = result.length ? result[result.length - 1] : []
|
||||||
if (!result) {
|
if (!result.length) {
|
||||||
// Preceeding steps.
|
// Preceeding steps.
|
||||||
result = steps.slice(0, stepIdx + 1)
|
result = steps.slice(0, stepIdx + 1)
|
||||||
return
|
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 { componentStore } from "@/stores/builder"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { BudiStore } from "../BudiStore"
|
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: [],
|
layouts: [],
|
||||||
selectedLayoutId: null,
|
selectedLayoutId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LayoutStore extends BudiStore {
|
export class LayoutStore extends BudiStore<LayoutState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(INITIAL_LAYOUT_STATE)
|
super(INITIAL_LAYOUT_STATE)
|
||||||
|
|
||||||
|
@ -22,14 +28,14 @@ export class LayoutStore extends BudiStore {
|
||||||
this.store.set({ ...INITIAL_LAYOUT_STATE })
|
this.store.set({ ...INITIAL_LAYOUT_STATE })
|
||||||
}
|
}
|
||||||
|
|
||||||
syncAppLayouts(pkg) {
|
syncAppLayouts(pkg: { layouts: Layout[] }) {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
layouts: [...pkg.layouts],
|
layouts: [...pkg.layouts],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
select(layoutId) {
|
select(layoutId: string) {
|
||||||
// Check this layout exists
|
// Check this layout exists
|
||||||
const state = get(this.store)
|
const state = get(this.store)
|
||||||
const componentState = get(componentStore)
|
const componentState = get(componentStore)
|
||||||
|
@ -48,15 +54,15 @@ export class LayoutStore extends BudiStore {
|
||||||
|
|
||||||
// Select new layout
|
// Select new layout
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
state.selectedLayoutId = layout._id
|
state.selectedLayoutId = layout._id!
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
componentStore.select(layout.props?._id)
|
componentStore.select(layout.props?._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteLayout(layout) {
|
async deleteLayout(layout: Layout) {
|
||||||
if (!layout?._id) {
|
if (!layout?._id || !layout?._rev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await API.deleteLayout(layout._id, layout._rev)
|
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,
|
: RelationshipType.MANY_TO_MANY,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchFunction(searchParams) {
|
async function searchFunction(_tableId, searchParams) {
|
||||||
if (
|
if (
|
||||||
subtype !== BBReferenceFieldSubType.USER &&
|
subtype !== BBReferenceFieldSubType.USER &&
|
||||||
subtype !== BBReferenceFieldSubType.USERS
|
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 { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
|
||||||
import { APISessionID } from "../api"
|
import { APISessionID } from "../api"
|
||||||
|
|
||||||
const DefaultOptions = {
|
const DefaultOptions = {
|
||||||
heartbeat: true,
|
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) {
|
if (!path) {
|
||||||
throw "A websocket path must be provided"
|
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
|
// Disable polling and rely on websocket only, as HTTP transport
|
||||||
// will only work with sticky sessions which we don't have
|
// will only work with sticky sessions which we don't have
|
||||||
transports: ["websocket"],
|
transports: ["websocket"],
|
||||||
})
|
}) as ExtendedSocket
|
||||||
|
|
||||||
// Set up a heartbeat that's half of the session TTL
|
// Set up a heartbeat that's half of the session TTL
|
||||||
let interval
|
let interval: NodeJS.Timeout | undefined
|
||||||
if (heartbeat) {
|
if (heartbeat) {
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
socket.emit(SocketEvent.Heartbeat)
|
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
|
// Helper utility to ignore events that were triggered due to API
|
||||||
// changes made by us
|
// changes made by us
|
||||||
socket.onOther = (event, callback) => {
|
socket.onOther = (event: string, callback: (data: any) => void) => {
|
||||||
socket.on(event, data => {
|
socket.on(event, data => {
|
||||||
if (data?.apiSessionId !== APISessionID) {
|
if (data?.apiSessionId !== APISessionID) {
|
||||||
callback(data)
|
callback(data)
|
|
@ -174,7 +174,9 @@ class QueryRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
// needs to an array for next step
|
// needs to an array for next step
|
||||||
if (!Array.isArray(rows)) {
|
if (rows === null) {
|
||||||
|
rows = []
|
||||||
|
} else if (!Array.isArray(rows)) {
|
||||||
rows = [rows]
|
rows = [rows]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,5 @@ import { User } from "@budibase/types"
|
||||||
export interface UIUser extends User {
|
export interface UIUser extends User {
|
||||||
sessionId: string
|
sessionId: string
|
||||||
gridMetadata?: { focusedCellId?: string }
|
gridMetadata?: { focusedCellId?: string }
|
||||||
|
builderMetadata?: { selectedResourceId?: string }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./integration"
|
export * from "./integration"
|
||||||
export * from "./automations"
|
export * from "./automations"
|
||||||
export * from "./grid"
|
export * from "./grid"
|
||||||
|
export * from "./preview"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export type PreviewDevice = "desktop" | "tablet" | "mobile"
|
Loading…
Reference in New Issue