Allow all users into the design section, enable multi dev collab on screens, improve routing
This commit is contained in:
parent
bc47f5b0b2
commit
d80cca9a11
|
@ -353,6 +353,35 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
return await sequentialScreenPatch(patchFn, screenId)
|
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 => {
|
delete: async screens => {
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const createBuilderWebsocket = appId => {
|
||||||
})
|
})
|
||||||
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
||||||
if (userId === get(auth)?.user?._id) {
|
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 => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
hasLock: true,
|
hasLock: true,
|
||||||
|
@ -39,15 +39,18 @@ export const createBuilderWebsocket = appId => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Table events
|
// Data section events
|
||||||
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
||||||
tables.replaceTable(id, table)
|
tables.replaceTable(id, table)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Datasource events
|
|
||||||
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
||||||
datasources.replaceDatasource(id, datasource)
|
datasources.replaceDatasource(id, datasource)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Design section events
|
||||||
|
socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => {
|
||||||
|
store.actions.screens.replace(id, screen)
|
||||||
|
})
|
||||||
|
|
||||||
return socket
|
return socket
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,26 +114,24 @@ export const syncURLToState = options => {
|
||||||
|
|
||||||
// Updates the URL with new state values
|
// Updates the URL with new state values
|
||||||
const mapStateToUrl = state => {
|
const mapStateToUrl = state => {
|
||||||
let needsUpdate = false
|
|
||||||
const urlValue = cachedParams?.[urlParam]
|
const urlValue = cachedParams?.[urlParam]
|
||||||
const stateValue = state?.[stateKey]
|
const stateValue = state?.[stateKey]
|
||||||
if (stateValue !== urlValue) {
|
|
||||||
needsUpdate = true
|
// As the store updated, validate that the current state value is valid
|
||||||
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
if (validate && fallbackUrl) {
|
||||||
if (validate && fallbackUrl) {
|
if (!validate(stateValue)) {
|
||||||
if (!validate(stateValue)) {
|
log("Invalid state param!", stateValue)
|
||||||
log("Invalid state param!", stateValue)
|
redirectUrl(fallbackUrl)
|
||||||
redirectUrl(fallbackUrl)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid updating the URL if not necessary to prevent a wasted render
|
// Avoid updating the URL if not necessary to prevent a wasted render
|
||||||
// cycle
|
// cycle
|
||||||
if (!needsUpdate) {
|
if (stateValue === urlValue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||||
|
|
||||||
// Navigate to the new URL
|
// Navigate to the new URL
|
||||||
if (!get(isChangingPage)) {
|
if (!get(isChangingPage)) {
|
||||||
|
|
|
@ -151,31 +151,19 @@
|
||||||
on:click={() => $goto("../../portal/apps")}
|
on:click={() => $goto("../../portal/apps")}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{#if $store.hasLock}
|
<Tabs {selected} size="M">
|
||||||
<Tabs {selected} size="M">
|
{#each $layout.children as { path, title }}
|
||||||
{#each $layout.children as { path, title }}
|
<TourWrap tourStepKey={`builder-${title}-section`}>
|
||||||
<TourWrap tourStepKey={`builder-${title}-section`}>
|
<Tab
|
||||||
<Tab
|
quiet
|
||||||
quiet
|
selected={$isActive(path)}
|
||||||
selected={$isActive(path)}
|
on:click={topItemNavigate(path)}
|
||||||
on:click={topItemNavigate(path)}
|
title={capitalise(title)}
|
||||||
title={capitalise(title)}
|
id={`builder-${title}-tab`}
|
||||||
id={`builder-${title}-tab`}
|
/>
|
||||||
/>
|
</TourWrap>
|
||||||
</TourWrap>
|
{/each}
|
||||||
{/each}
|
</Tabs>
|
||||||
</Tabs>
|
|
||||||
{:else}
|
|
||||||
<div class="secondary-editor">
|
|
||||||
<Icon name="LockClosed" />
|
|
||||||
<div
|
|
||||||
class="secondary-editor-body"
|
|
||||||
title="Another user is currently editing your screens and automations"
|
|
||||||
>
|
|
||||||
Another user is currently editing your screens and automations
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="topcenternav">
|
<div class="topcenternav">
|
||||||
<Heading size="XS">{$store.name}</Heading>
|
<Heading size="XS">{$store.name}</Heading>
|
||||||
|
|
|
@ -8,15 +8,6 @@
|
||||||
import { onDestroy, onMount } from "svelte"
|
import { onDestroy, onMount } from "svelte"
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
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
|
// Keep URL and state in sync for selected screen ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
|
|
|
@ -1,14 +1,2 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
// Prevent access for other users than the lock holder
|
|
||||||
$: {
|
|
||||||
if (!$store.hasLock) {
|
|
||||||
$redirect("../data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- routify:options index=2 -->
|
<!-- routify:options index=2 -->
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { updateAppPackage } from "./application"
|
import { updateAppPackage } from "./application"
|
||||||
import { Plugin, ScreenProps, BBContext } from "@budibase/types"
|
import { Plugin, ScreenProps, BBContext } from "@budibase/types"
|
||||||
|
import { builderSocket } from "../../websockets"
|
||||||
|
|
||||||
export async function fetch(ctx: BBContext) {
|
export async function fetch(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
@ -87,13 +88,17 @@ export async function save(ctx: BBContext) {
|
||||||
if (eventFn) {
|
if (eventFn) {
|
||||||
await eventFn(screen)
|
await eventFn(screen)
|
||||||
}
|
}
|
||||||
ctx.message = `Screen ${screen.name} saved.`
|
const savedScreen = {
|
||||||
ctx.body = {
|
|
||||||
...screen,
|
...screen,
|
||||||
_id: response.id,
|
_id: response.id,
|
||||||
_rev: response.rev,
|
_rev: response.rev,
|
||||||
|
}
|
||||||
|
ctx.message = `Screen ${screen.name} saved.`
|
||||||
|
ctx.body = {
|
||||||
|
...savedScreen,
|
||||||
pluginAdded,
|
pluginAdded,
|
||||||
}
|
}
|
||||||
|
builderSocket?.emitScreenUpdate(ctx, savedScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: BBContext) {
|
export async function destroy(ctx: BBContext) {
|
||||||
|
@ -108,6 +113,7 @@ export async function destroy(ctx: BBContext) {
|
||||||
message: "Screen deleted successfully",
|
message: "Screen deleted successfully",
|
||||||
}
|
}
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
builderSocket?.emitScreenDeletion(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPlugins(component: ScreenProps, foundPlugins: string[]) {
|
function findPlugins(component: ScreenProps, foundPlugins: string[]) {
|
||||||
|
|
|
@ -3,7 +3,13 @@ import { BaseSocket } from "./websocket"
|
||||||
import { permissions, events } from "@budibase/backend-core"
|
import { permissions, events } from "@budibase/backend-core"
|
||||||
import http from "http"
|
import http from "http"
|
||||||
import Koa from "koa"
|
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 { gridSocket } from "./index"
|
||||||
import { clearLock, updateLock } from "../utilities/redis"
|
import { clearLock, updateLock } from "../utilities/redis"
|
||||||
import { Socket } from "socket.io"
|
import { Socket } from "socket.io"
|
||||||
|
@ -101,4 +107,18 @@ export default class BuilderSocket extends BaseSocket {
|
||||||
datasource: null,
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,8 @@ export enum BuilderSocketEvent {
|
||||||
TableChange = "TableChange",
|
TableChange = "TableChange",
|
||||||
DatasourceChange = "DatasourceChange",
|
DatasourceChange = "DatasourceChange",
|
||||||
LockTransfer = "LockTransfer",
|
LockTransfer = "LockTransfer",
|
||||||
|
ScreenChange = "ScreenChange",
|
||||||
|
AppMetadataChange = "AppMetadataChange",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SocketSessionTTL = 60
|
export const SocketSessionTTL = 60
|
||||||
|
|
Loading…
Reference in New Issue