From d80cca9a111d4092b4098e6ffa83f08448a9d1c3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 3 Jul 2023 11:14:07 +0100 Subject: [PATCH] Allow all users into the design section, enable multi dev collab on screens, improve routing --- .../src/builderStore/store/frontend.js | 29 ++++++++++++++ .../builder/src/builderStore/websocket.js | 11 ++++-- packages/builder/src/helpers/urlStateSync.js | 20 +++++----- .../builder/app/[application]/_layout.svelte | 38 +++++++------------ .../[application]/automation/_layout.svelte | 9 ----- .../app/[application]/design/_layout.svelte | 12 ------ packages/server/src/api/controllers/screen.ts | 10 ++++- packages/server/src/websockets/builder.ts | 22 ++++++++++- packages/shared-core/src/constants.ts | 2 + 9 files changed, 89 insertions(+), 64 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 5de58f02e7..4af3be4673 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -353,6 +353,35 @@ export const getFrontendStore = () => { } return await sequentialScreenPatch(patchFn, screenId) }, + replace: async (screenId, screen) => { + if (!screenId) { + return + } + + // Handle deletion + if (!screen) { + store.update(state => ({ + ...state, + screens: state.screens.filter(x => x._id !== screenId), + })) + return + } + + // Add new datasource + const index = get(store).screens.findIndex(x => x._id === screen._id) + if (index === -1) { + store.update(state => ({ + ...state, + screens: [...state.screens, screen], + })) + } + + // Update existing datasource + store.update(state => { + state.screens[index] = screen + return state + }) + }, delete: async screens => { const screensToDelete = Array.isArray(screens) ? screens : [screens] diff --git a/packages/builder/src/builderStore/websocket.js b/packages/builder/src/builderStore/websocket.js index af6d58ee7f..8562c8024c 100644 --- a/packages/builder/src/builderStore/websocket.js +++ b/packages/builder/src/builderStore/websocket.js @@ -31,7 +31,7 @@ export const createBuilderWebsocket = appId => { }) socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => { if (userId === get(auth)?.user?._id) { - notifications.success("You can now edit screens and automations") + notifications.success("You can now edit automations") store.update(state => ({ ...state, hasLock: true, @@ -39,15 +39,18 @@ export const createBuilderWebsocket = appId => { } }) - // Table events + // Data section events socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => { tables.replaceTable(id, table) }) - - // Datasource events socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => { datasources.replaceDatasource(id, datasource) }) + // Design section events + socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => { + store.actions.screens.replace(id, screen) + }) + return socket } diff --git a/packages/builder/src/helpers/urlStateSync.js b/packages/builder/src/helpers/urlStateSync.js index 47f3438468..c4c48fb3fb 100644 --- a/packages/builder/src/helpers/urlStateSync.js +++ b/packages/builder/src/helpers/urlStateSync.js @@ -114,26 +114,24 @@ export const syncURLToState = options => { // Updates the URL with new state values const mapStateToUrl = state => { - let needsUpdate = false const urlValue = cachedParams?.[urlParam] const stateValue = state?.[stateKey] - if (stateValue !== urlValue) { - needsUpdate = true - log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`) - if (validate && fallbackUrl) { - if (!validate(stateValue)) { - log("Invalid state param!", stateValue) - redirectUrl(fallbackUrl) - return - } + + // As the store updated, validate that the current state value is valid + if (validate && fallbackUrl) { + if (!validate(stateValue)) { + log("Invalid state param!", stateValue) + redirectUrl(fallbackUrl) + return } } // Avoid updating the URL if not necessary to prevent a wasted render // cycle - if (!needsUpdate) { + if (stateValue === urlValue) { return } + log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`) // Navigate to the new URL if (!get(isChangingPage)) { diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 0d5942c39e..3703279044 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -151,31 +151,19 @@ on:click={() => $goto("../../portal/apps")} /> - {#if $store.hasLock} - - {#each $layout.children as { path, title }} - - - - {/each} - - {:else} -
- -
- Another user is currently editing your screens and automations -
-
- {/if} + + {#each $layout.children as { path, title }} + + + + {/each} +
{$store.name} diff --git a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte index 79ca5df168..74dfe671ab 100644 --- a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte @@ -8,15 +8,6 @@ import { onDestroy, onMount } from "svelte" import { syncURLToState } from "helpers/urlStateSync" import * as routify from "@roxi/routify" - import { store } from "builderStore" - import { redirect } from "@roxi/routify" - - // Prevent access for other users than the lock holder - $: { - if (!$store.hasLock) { - $redirect("../data") - } - } // Keep URL and state in sync for selected screen ID const stopSyncing = syncURLToState({ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte index d23514ae6d..ec21d909aa 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte @@ -1,14 +1,2 @@ - - diff --git a/packages/server/src/api/controllers/screen.ts b/packages/server/src/api/controllers/screen.ts index 9cbd019d6e..ddfec91c0c 100644 --- a/packages/server/src/api/controllers/screen.ts +++ b/packages/server/src/api/controllers/screen.ts @@ -8,6 +8,7 @@ import { } from "@budibase/backend-core" import { updateAppPackage } from "./application" import { Plugin, ScreenProps, BBContext } from "@budibase/types" +import { builderSocket } from "../../websockets" export async function fetch(ctx: BBContext) { const db = context.getAppDB() @@ -87,13 +88,17 @@ export async function save(ctx: BBContext) { if (eventFn) { await eventFn(screen) } - ctx.message = `Screen ${screen.name} saved.` - ctx.body = { + const savedScreen = { ...screen, _id: response.id, _rev: response.rev, + } + ctx.message = `Screen ${screen.name} saved.` + ctx.body = { + ...savedScreen, pluginAdded, } + builderSocket?.emitScreenUpdate(ctx, savedScreen) } export async function destroy(ctx: BBContext) { @@ -108,6 +113,7 @@ export async function destroy(ctx: BBContext) { message: "Screen deleted successfully", } ctx.status = 200 + builderSocket?.emitScreenDeletion(ctx, id) } function findPlugins(component: ScreenProps, foundPlugins: string[]) { diff --git a/packages/server/src/websockets/builder.ts b/packages/server/src/websockets/builder.ts index 2524d9608b..839f8732b7 100644 --- a/packages/server/src/websockets/builder.ts +++ b/packages/server/src/websockets/builder.ts @@ -3,7 +3,13 @@ import { BaseSocket } from "./websocket" import { permissions, events } from "@budibase/backend-core" import http from "http" import Koa from "koa" -import { Datasource, Table, SocketSession, ContextUser } from "@budibase/types" +import { + Datasource, + Table, + SocketSession, + ContextUser, + Screen, +} from "@budibase/types" import { gridSocket } from "./index" import { clearLock, updateLock } from "../utilities/redis" import { Socket } from "socket.io" @@ -101,4 +107,18 @@ export default class BuilderSocket extends BaseSocket { datasource: null, }) } + + emitScreenUpdate(ctx: any, screen: Screen) { + this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.ScreenChange, { + id: screen._id, + screen, + }) + } + + emitScreenDeletion(ctx: any, id: string) { + this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.ScreenChange, { + id, + screen: null, + }) + } } diff --git a/packages/shared-core/src/constants.ts b/packages/shared-core/src/constants.ts index 307285012b..e3784ac354 100644 --- a/packages/shared-core/src/constants.ts +++ b/packages/shared-core/src/constants.ts @@ -86,6 +86,8 @@ export enum BuilderSocketEvent { TableChange = "TableChange", DatasourceChange = "DatasourceChange", LockTransfer = "LockTransfer", + ScreenChange = "ScreenChange", + AppMetadataChange = "AppMetadataChange", } export const SocketSessionTTL = 60