Simplify and rewrite some flow logic

This commit is contained in:
Andrew Kingston 2024-09-16 12:15:17 +01:00
parent 0a109165f1
commit c634cfdeac
No known key found for this signature in database
9 changed files with 168 additions and 112 deletions

View File

@ -81,6 +81,7 @@
text="Manage roles"
selected={$isActive("./roles")}
on:click={() => $goto("./roles")}
selectedBy={$userSelectedResourceMap.roles}
/>
{#each enrichedDataSources.filter(ds => ds.show) as datasource}
<DatasourceNavItem

View File

@ -1,32 +1,15 @@
<script>
import { Button, Helpers, ActionButton } from "@budibase/bbui"
import { Button, ActionButton } from "@budibase/bbui"
import { useSvelteFlow } from "@xyflow/svelte"
import { getContext, tick } from "svelte"
import { autoLayout, roleToNode } from "./layout"
import { autoLayout } from "./layout"
import { MaxAutoZoom, ZoomDuration } from "./constants"
import { getSequentialName } from "helpers/duplicate"
import { roles } from "stores/builder"
import { Roles } from "constants/backend"
const { nodes, edges } = getContext("flow")
const { nodes, edges, createRole } = getContext("flow")
const flow = useSvelteFlow()
const addRole = async () => {
const role = {
name: Helpers.uuid(),
uiMetadata: {
displayName: getSequentialName($roles, "New role ", {
getName: x => x.uiMetadata.displayName,
}),
color: "var(--spectrum-global-color-gray-700)",
description: "Custom role",
},
permissionId: "write",
inherits: Roles.BASIC,
}
const savedRole = await roles.save(role)
nodes.update(state => [...state, roleToNode(savedRole)])
await doAutoLayout()
await createRole()
}
const doAutoLayout = async () => {

View File

@ -6,7 +6,7 @@
useSvelteFlow,
} from "@xyflow/svelte"
import { Icon, TooltipPosition } from "@budibase/bbui"
import { onMount } from "svelte"
import { onMount, getContext } from "svelte"
import { roles } from "stores/builder"
export let sourceX
@ -20,6 +20,7 @@
export let target
const flow = useSvelteFlow()
const { updateRole } = getContext("flow")
let edgeHovered = false
let labelHovered = false
@ -56,10 +57,11 @@
edgeHovered = false
}
const deleteEdge = () => {
const deleteEdge = async () => {
flow.deleteElements({
edges: [{ id }],
})
await updateRole(target)
}
onMount(() => {

View File

@ -1,25 +1,157 @@
<script>
import { Heading } from "@budibase/bbui"
import { Heading, Helpers } from "@budibase/bbui"
import { writable } from "svelte/store"
import { SvelteFlow, Background, BackgroundVariant } from "@xyflow/svelte"
import {
SvelteFlow,
Background,
BackgroundVariant,
Position,
} from "@xyflow/svelte"
import "@xyflow/svelte/dist/style.css"
import RoleNode from "./RoleNode.svelte"
import RoleEdge from "./RoleEdge.svelte"
import { rolesToNodes, autoLayout } from "./layout"
import { onMount, setContext } from "svelte"
import { autoLayout } from "./layout"
import { setContext } from "svelte"
import Controls from "./Controls.svelte"
import { GridResolution, MaxAutoZoom } from "./constants"
import { roles } from "stores/builder"
import { Roles } from "constants/backend"
import { getSequentialName } from "helpers/duplicate"
const nodes = writable([])
const edges = writable([])
const dragging = writable(false)
setContext("flow", { nodes, edges, dragging })
// Ensure role changes are synced with nodes and edges
$: handleExternalRoleChanges($roles)
onMount(() => {
const layout = autoLayout(rolesToNodes())
nodes.set(layout.nodes)
edges.set(layout.edges)
// Converts a role doc into a node structure
const roleToNode = role => ({
id: role._id,
sourcePosition: Position.Right,
targetPosition: Position.Left,
type: "role",
position: { x: 0, y: 0 },
data: {
...role.uiMetadata,
custom: !role._id.match(/[A-Z]+/),
},
})
// Converts a node structure back into a role doc
const nodeToRole = node => {
const role = $roles.find(x => x._id === node.id)
const inherits = $edges.filter(x => x.target === node.id).map(x => x.source)
console.log(inherits)
return {
...role,
// inherits,
uiMetadata: {
displayName: node.data.displayName,
color: node.data.color,
description: node.data.description,
},
}
}
// Builds a layout from an array of roles
const rolesToLayout = roles => {
let nodes = []
let edges = []
for (let role of roles.filter(role => role._id !== Roles.PUBLIC)) {
// Add node for this role
nodes.push(roleToNode(role))
// Add edges for this role
let inherits = []
if (role.inherits) {
inherits = Array.isArray(role.inherits)
? role.inherits
: [role.inherits]
}
for (let sourceRole of inherits) {
if (!roles.some(x => x._id === sourceRole)) {
continue
}
edges.push({
id: `${sourceRole}-${role._id}`,
source: sourceRole,
target: role._id,
})
}
}
return {
nodes,
edges,
}
}
// Updates nodes and edges based on external changes to roles
const handleExternalRoleChanges = roles => {
const currentNodes = $nodes
const newLayout = autoLayout(rolesToLayout(roles))
// For roles we want to persist their current positions
nodes.set(
newLayout.nodes.map(node => {
const position = currentNodes.find(x => x.id === node.id)?.position
if (!position) {
return node
}
return { ...node, position }
})
)
// Edges can always be updated
edges.set(newLayout.edges)
}
// Creates a new role
const createRole = async () => {
const role = {
name: Helpers.uuid(),
uiMetadata: {
displayName: getSequentialName($roles, "New role ", {
getName: x => x.uiMetadata.displayName,
}),
color: "var(--spectrum-global-color-gray-700)",
description: "Custom role",
},
permissionId: "write",
inherits: Roles.BASIC,
}
await roles.save(role)
}
// Updates a role based on the latest flow data
const updateRole = async roleId => {
const node = $nodes.find(x => x.id === roleId)
if (node) {
const role = nodeToRole(node)
await roles.save(role)
}
}
// Deletes a role
const deleteRole = async roleId => {
const role = $roles.find(x => x._id === roleId)
if (role) {
roles.delete(role)
}
}
// Saves a new connection
const onConnect = async connection => {
await saveRole(connection.target)
}
setContext("flow", {
nodes,
edges,
dragging,
createRole,
updateRole,
deleteRole,
})
</script>
@ -42,6 +174,7 @@
defaultEdgeOptions={{ type: "role", animated: true, selectable: false }}
onconnectstart={() => dragging.set(true)}
onconnectend={() => dragging.set(false)}
onconnect={onConnect}
>
<Background variant={BackgroundVariant.Dots} />
<Controls />

View File

@ -11,13 +11,13 @@
import { Roles } from "constants/backend"
import { NodeWidth, NodeHeight, MaxAutoZoom, ZoomDuration } from "./constants"
import { getContext, tick } from "svelte"
import { autoLayout, nodeToRole } from "./layout"
import { autoLayout } from "./layout"
import { roles } from "stores/builder"
export let data
export let id
const { nodes, edges, dragging } = getContext("flow")
const { nodes, edges, dragging, updateRole, deleteRole } = getContext("flow")
const flow = useSvelteFlow()
let anchor
@ -49,12 +49,7 @@
}
const deleteNode = async () => {
flow.deleteElements({
nodes: [{ id }],
})
await tick()
doAutoLayout()
await roles.delete(nodeToRole({ id, data }))
await deleteRole(id)
}
const openPopover = () => {
@ -71,7 +66,7 @@
color: tempColor,
}
flow.updateNodeData(id, newData)
await roles.save(nodeToRole({ id, data: newData }))
await updateRole(id)
}
const doAutoLayout = () => {

View File

@ -1,79 +1,11 @@
import dagre from "@dagrejs/dagre"
import { NodeWidth, NodeHeight, GridResolution } from "./constants"
import { Position } from "@xyflow/svelte"
import { roles } from "stores/builder"
import { Roles } from "constants/backend"
import { get } from "svelte/store"
import { Helpers } from "@budibase/bbui"
// Converts a role doc into a node structure
export const roleToNode = role => ({
id: role._id,
sourcePosition: Position.Right,
targetPosition: Position.Left,
type: "role",
position: { x: 0, y: 0 },
data: {
...role.uiMetadata,
custom: !role._id.match(/[A-Z]+/),
},
})
// Converts a node structure back into a role doc
export const nodeToRole = node => {
const role = get(roles).find(x => x._id === node.id)
return {
...role,
uiMetadata: {
displayName: node.data.displayName,
color: node.data.color,
description: node.data.description,
},
}
}
// Generates a flow compatible structure of nodes and edges from the current roles
export const rolesToNodes = () => {
const ignoredRoles = [Roles.PUBLIC]
const $roles = get(roles)
let nodes = []
let edges = []
for (let role of $roles) {
if (ignoredRoles.includes(role._id)) {
continue
}
// Add node for this role
nodes.push(roleToNode(role))
// Add edges for this role
let inherits = []
if (role.inherits) {
inherits = Array.isArray(role.inherits) ? role.inherits : [role.inherits]
}
for (let sourceRole of inherits) {
// Ensure source role exists
if (!$roles.some(x => x._id === sourceRole)) {
continue
}
edges.push({
id: `${sourceRole}-${role._id}`,
source: sourceRole,
target: role._id,
})
}
}
return {
nodes,
edges,
}
}
// Updates positions of nodes and edges into a nice graph structure
const dagreLayout = ({ nodes, edges }) => {
export const dagreLayout = ({ nodes, edges }) => {
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
dagreGraph.setGraph({

View File

@ -1,5 +1,8 @@
<script>
import RoleEditor from "components/backend/RoleEditor/RoleEditor.svelte"
import { builderStore } from "stores/builder"
builderStore.selectResource("roles")
</script>
<RoleEditor />

View File

@ -1,4 +1,4 @@
import { derived, writable } from "svelte/store"
import { derived, writable, get } from "svelte/store"
import { API } from "api"
import { RoleUtils } from "@budibase/frontend-core"
@ -56,6 +56,13 @@ export function createRolesStore() {
}
},
replace: (roleId, role) => {
// Remove role_ prefix
if (roleId?.startsWith("role_")) {
roleId = roleId.replace("role_", "")
}
if (role?._id.startsWith("role_")) {
role._id = role._id.replace("role_", "")
}
console.log("replace", roleId, role)
// Handles external updates of roles

View File

@ -59,7 +59,7 @@ export const createBuilderWebsocket = appId => {
// Role events
socket.onOther(BuilderSocketEvent.RoleChange, ({ id, role }) => {
roles.replaceRole(id, role)
roles.replace(id, role)
})
// Design section events