Simplify websocket comms for grid and improve builder socket

This commit is contained in:
Andrew Kingston 2023-05-18 08:57:20 +01:00
parent 0bebacc741
commit 3794d8e204
10 changed files with 74 additions and 65 deletions

View File

@ -2,7 +2,7 @@ import { getFrontendStore } from "./store/frontend"
import { getAutomationStore } from "./store/automation" import { getAutomationStore } from "./store/automation"
import { getTemporalStore } from "./store/temporal" import { getTemporalStore } from "./store/temporal"
import { getThemeStore } from "./store/theme" import { getThemeStore } from "./store/theme"
import { getUsersStore } from "./store/users" import { getUserStore } from "./store/users"
import { derived } from "svelte/store" import { derived } from "svelte/store"
import { findComponent, findComponentPath } from "./componentUtils" import { findComponent, findComponentPath } from "./componentUtils"
import { RoleUtils } from "@budibase/frontend-core" import { RoleUtils } from "@budibase/frontend-core"
@ -13,7 +13,7 @@ export const store = getFrontendStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
export const themeStore = getThemeStore() export const themeStore = getThemeStore()
export const temporalStore = getTemporalStore() export const temporalStore = getTemporalStore()
export const usersStore = getUsersStore() export const userStore = getUserStore()
// Setup history for screens // Setup history for screens
export const screenHistoryStore = createHistoryStore({ export const screenHistoryStore = createHistoryStore({

View File

@ -5,6 +5,7 @@ import {
selectedComponent, selectedComponent,
screenHistoryStore, screenHistoryStore,
automationHistoryStore, automationHistoryStore,
userStore,
} from "builderStore" } from "builderStore"
import { import {
datasources, datasources,
@ -85,9 +86,6 @@ const INITIAL_FRONTEND_STATE = {
// Onboarding // Onboarding
onboarding: false, onboarding: false,
tourNodes: null, tourNodes: null,
// Multi user collab
users: [],
} }
export const getFrontendStore = () => { export const getFrontendStore = () => {
@ -122,7 +120,6 @@ export const getFrontendStore = () => {
initialise: async pkg => { initialise: async pkg => {
const { layouts, screens, application, clientLibPath, hasLock } = pkg const { layouts, screens, application, clientLibPath, hasLock } = pkg
websocket = createBuilderWebsocket() websocket = createBuilderWebsocket()
await store.actions.components.refreshDefinitions(application.appId) await store.actions.components.refreshDefinitions(application.appId)
// Reset store state // Reset store state

View File

@ -1,10 +1,43 @@
import { writable } from "svelte/store" import { writable, get } from "svelte/store"
export const getUsersStore = () => { export const getUserStore = () => {
const initialValue = { const store = writable([])
users: [],
const init = users => {
store.set(users)
} }
const store = writable(initialValue)
return store const updateUser = user => {
console.log(user)
const $users = get(store)
if (!$users.some(x => x.id === user.id)) {
store.set([...$users, user])
} else {
store.update(state => {
const index = state.findIndex(x => x.id === user.id)
state[index] = user
return state.slice()
})
}
}
const removeUser = user => {
store.update(state => {
return state.filter(x => x.id !== user.id)
})
}
const reset = () => {
store.set([])
}
return {
...store,
actions: {
init,
updateUser,
removeUser,
reset,
},
}
} }

View File

@ -1,26 +1,26 @@
import { createWebsocket } from "@budibase/frontend-core" import { createWebsocket } from "@budibase/frontend-core"
import { store } from "builderStore" import { userStore } from "builderStore"
export const createBuilderWebsocket = () => { export const createBuilderWebsocket = () => {
const socket = createWebsocket("/socket/builder") const socket = createWebsocket("/socket/builder")
socket.on("connect", () => { socket.on("connect", () => {
socket.emit("get-users", null, response => { socket.emit("get-users", null, response => {
console.log("conntected!", response.users) userStore.actions.init(response.users)
store.update(state => ({
...state,
users: response.users,
}))
}) })
}) })
socket.on("user-update", user => {}) socket.on("user-update", userStore.actions.updateUser)
socket.on("user-disconnect", userStore.actions.removeUser)
socket.on("user-disconnect", user => {})
socket.on("connect_error", err => { socket.on("connect_error", err => {
console.log("Failed to connect to builder websocket:", err.message) console.log("Failed to connect to builder websocket:", err.message)
}) })
return socket return {
...socket,
disconnect: () => {
socket?.disconnect()
userStore.actions.reset()
},
}
} }

View File

@ -36,6 +36,7 @@
allowDeleteRows={!isUsersTable} allowDeleteRows={!isUsersTable}
schemaOverrides={isUsersTable ? userSchemaOverrides : null} schemaOverrides={isUsersTable ? userSchemaOverrides : null}
on:updatetable={e => tables.updateTable(e.detail)} on:updatetable={e => tables.updateTable(e.detail)}
showAvatars={false}
> >
<svelte:fragment slot="controls"> <svelte:fragment slot="controls">
{#if isInternal} {#if isInternal}

View File

@ -1,5 +1,5 @@
<script> <script>
import { store, automationStore } from "builderStore" import { store, automationStore, userStore } from "builderStore"
import { roles, flags } from "stores/backend" import { roles, flags } from "stores/backend"
import { auth } from "stores/portal" import { auth } from "stores/portal"
import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags" import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags"
@ -209,7 +209,7 @@
{/if} {/if}
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<UserAvatars users={$store.users} /> <UserAvatars users={$userStore} />
<AppActions {application} /> <AppActions {application} />
</div> </div>
{/if} {/if}

View File

@ -41,6 +41,8 @@
export let allowExpandRows = true export let allowExpandRows = true
export let allowEditRows = true export let allowEditRows = true
export let allowDeleteRows = true export let allowDeleteRows = true
export let collaboration = true
export let showAvatars = true
// Unique identifier for DOM nodes inside this instance // Unique identifier for DOM nodes inside this instance
const rand = Math.random() const rand = Math.random()
@ -98,7 +100,11 @@
export const getContext = () => context export const getContext = () => context
// Initialise websocket for multi-user // Initialise websocket for multi-user
onMount(() => createGridWebsocket(context)) onMount(() => {
if (collaboration) {
return createGridWebsocket(context)
}
})
</script> </script>
<div <div
@ -119,7 +125,9 @@
<RowHeightButton /> <RowHeightButton />
</div> </div>
<div class="controls-right"> <div class="controls-right">
<UserAvatars /> {#if showAvatars}
<UserAvatars />
{/if}
</div> </div>
</div> </div>
{#if $loaded} {#if $loaded}

View File

@ -2,7 +2,7 @@ import { get } from "svelte/store"
import { createWebsocket } from "../../../utils" import { createWebsocket } from "../../../utils"
export const createGridWebsocket = context => { export const createGridWebsocket = context => {
const { rows, tableId, users, userId, focusedCellId } = context const { rows, tableId, users, focusedCellId } = context
const socket = createWebsocket("/socket/grid") const socket = createWebsocket("/socket/grid")
const connectToTable = tableId => { const connectToTable = tableId => {
@ -13,7 +13,6 @@ export const createGridWebsocket = context => {
socket.emit("select-table", tableId, response => { socket.emit("select-table", tableId, response => {
// handle initial connection info // handle initial connection info
users.set(response.users) users.set(response.users)
userId.set(response.id)
}) })
} }

View File

@ -2,56 +2,28 @@ import { writable, get, derived } from "svelte/store"
export const createStores = () => { export const createStores = () => {
const users = writable([]) const users = writable([])
const userId = writable(null)
// Enrich users with unique colours
const enrichedUsers = derived(
[users, userId],
([$users, $userId]) => {
return (
$users
.slice()
// Place current user first
.sort((a, b) => {
if (a.id === $userId) {
return -1
} else if (b.id === $userId) {
return 1
} else {
return 0
}
})
)
},
[]
)
return { return {
users: { users,
...users,
subscribe: enrichedUsers.subscribe,
},
userId,
} }
} }
export const deriveStores = context => { export const deriveStores = context => {
const { users, userId } = context const { users, focusedCellId } = context
// Generate a lookup map of cell ID to the user that has it selected, to make // Generate a lookup map of cell ID to the user that has it selected, to make
// lookups inside cells extremely fast // lookups inside cells extremely fast
const selectedCellMap = derived( const selectedCellMap = derived(
[users, userId], [users, focusedCellId],
([$enrichedUsers, $userId]) => { ([$users, $focusedCellId]) => {
let map = {} let map = {}
$enrichedUsers.forEach(user => { $users.forEach(user => {
if (user.focusedCellId && user.id !== $userId) { if (user.focusedCellId && user.focusedCellId !== $focusedCellId) {
map[user.focusedCellId] = user map[user.focusedCellId] = user
} }
}) })
return map return map
}, }
{}
) )
const updateUser = user => { const updateUser = user => {

View File

@ -34,7 +34,6 @@ export default class GridSocket extends Socket {
const sockets = await this.io.in(currentRoom).fetchSockets() const sockets = await this.io.in(currentRoom).fetchSockets()
callback({ callback({
users: sockets.map(socket => socket.data.user), users: sockets.map(socket => socket.data.user),
id: user.id,
}) })
}) })