Refactor RBAC flow and use selected states more
This commit is contained in:
parent
d23d4156c3
commit
d61594d74e
|
@ -8,15 +8,15 @@
|
|||
const { nodes, edges, createRole } = getContext("flow")
|
||||
const flow = useSvelteFlow()
|
||||
|
||||
const doAutoLayout = async () => {
|
||||
const addRole = async () => {
|
||||
await createRole()
|
||||
doAutoLayout()
|
||||
}
|
||||
|
||||
const doAutoLayout = () => {
|
||||
const layout = autoLayout({ nodes: $nodes, edges: $edges })
|
||||
nodes.set(layout.nodes)
|
||||
edges.set(layout.edges)
|
||||
await tick()
|
||||
flow.fitView({
|
||||
maxZoom: MaxAutoZoom,
|
||||
duration: ZoomDuration,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
|||
<Button secondary on:click={doAutoLayout}>Auto layout</Button>
|
||||
</div>
|
||||
<div class="control bottom-right">
|
||||
<Button icon="Add" cta on:click={createRole}>Add role</Button>
|
||||
<Button icon="Add" cta on:click={addRole}>Add role</Button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -20,13 +20,12 @@
|
|||
export let target
|
||||
|
||||
const flow = useSvelteFlow()
|
||||
const { updateRole } = getContext("flow")
|
||||
const { updateRole, selectedNode } = getContext("flow")
|
||||
|
||||
let edgeHovered = false
|
||||
let labelHovered = false
|
||||
let iconHovered = false
|
||||
|
||||
$: hovered = edgeHovered || labelHovered
|
||||
$: edgeClasses = getEdgeClasses(hovered, labelHovered)
|
||||
$: active = source === $selectedNode || target === $selectedNode
|
||||
$: edgeClasses = getEdgeClasses(active, iconHovered)
|
||||
$: [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
|
@ -42,41 +41,19 @@
|
|||
? `Stop ${targetRole.uiMetadata.displayName} from inheriting ${sourceRole.uiMetadata.displayName}`
|
||||
: null
|
||||
|
||||
const getEdgeClasses = (hovered, labelHovered) => {
|
||||
const getEdgeClasses = (active, iconHovered) => {
|
||||
let classes = ""
|
||||
if (hovered) classes += `hovered `
|
||||
if (labelHovered) classes += `delete `
|
||||
if (active) classes += `active `
|
||||
if (iconHovered) classes += `delete `
|
||||
return classes
|
||||
}
|
||||
|
||||
const onEdgeMouseOver = () => {
|
||||
edgeHovered = true
|
||||
}
|
||||
|
||||
const onEdgeMouseOut = () => {
|
||||
edgeHovered = false
|
||||
}
|
||||
|
||||
const deleteEdge = async () => {
|
||||
flow.deleteElements({
|
||||
edges: [{ id }],
|
||||
})
|
||||
await updateRole(target)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const edge = document.querySelector(`.svelte-flow__edge[data-id="${id}"]`)
|
||||
if (edge) {
|
||||
edge.addEventListener("mouseover", onEdgeMouseOver)
|
||||
edge.addEventListener("mouseout", onEdgeMouseOut)
|
||||
}
|
||||
return () => {
|
||||
if (edge) {
|
||||
edge.removeEventListener("mouseover", onEdgeMouseOver)
|
||||
edge.removeEventListener("mouseout", onEdgeMouseOut)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<BaseEdge path={edgePath} class={edgeClasses} />
|
||||
|
@ -87,10 +64,10 @@
|
|||
<div
|
||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
||||
class="edge-label nodrag nopan"
|
||||
class:hovered
|
||||
class:active
|
||||
on:click={deleteEdge}
|
||||
on:mouseover={() => (labelHovered = true)}
|
||||
on:mouseout={() => (labelHovered = false)}
|
||||
on:mouseover={() => (iconHovered = true)}
|
||||
on:mouseout={() => (iconHovered = false)}
|
||||
>
|
||||
<Icon name="Delete" {tooltip} tooltipPosition={TooltipPosition.Top} />
|
||||
</div>
|
||||
|
@ -101,11 +78,11 @@
|
|||
position: absolute;
|
||||
padding: 8px;
|
||||
opacity: 0;
|
||||
pointer-events: all;
|
||||
pointer-events: none;
|
||||
}
|
||||
.edge-label:hover,
|
||||
.edge-label.hovered {
|
||||
.edge-label.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edge-label:hover :global(.spectrum-Icon) {
|
||||
|
@ -116,13 +93,12 @@
|
|||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
.edge-label :global(svg) {
|
||||
padding: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
:global(.svelte-flow__edge:hover .svelte-flow__edge-path),
|
||||
:global(.svelte-flow__edge-path.hovered) {
|
||||
:global(.svelte-flow__edge-path.active) {
|
||||
stroke: var(--spectrum-global-color-blue-400);
|
||||
}
|
||||
:global(.svelte-flow__edge-path.hovered.delete) {
|
||||
:global(.svelte-flow__edge-path.active.delete) {
|
||||
stroke: var(--spectrum-global-color-red-400);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,227 +1,8 @@
|
|||
<script>
|
||||
import { Heading, Helpers } from "@budibase/bbui"
|
||||
import { writable } from "svelte/store"
|
||||
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 { 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)
|
||||
|
||||
// Ensure role changes are synced with nodes and edges
|
||||
$: handleExternalRoleChanges($roles)
|
||||
|
||||
// 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,
|
||||
})
|
||||
import { SvelteFlowProvider } from "@xyflow/svelte"
|
||||
import RoleFlow from "./RoleFlow.svelte"
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
<div class="heading">
|
||||
<Heading size="S">Manage roles</Heading>
|
||||
</div>
|
||||
<div class="description">Roles inherit permissions from each other.</div>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<SvelteFlow
|
||||
fitView
|
||||
{nodes}
|
||||
{edges}
|
||||
snapGrid={[GridResolution, GridResolution]}
|
||||
nodeTypes={{ role: RoleNode }}
|
||||
edgeTypes={{ role: RoleEdge }}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
fitViewOptions={{ maxZoom: MaxAutoZoom }}
|
||||
defaultEdgeOptions={{ type: "role", animated: true, selectable: false }}
|
||||
onconnectstart={() => dragging.set(true)}
|
||||
onconnectend={() => dragging.set(false)}
|
||||
onconnect={onConnect}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<Controls />
|
||||
</SvelteFlow>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.description {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
margin-bottom: calc(20px - var(--spacing-l));
|
||||
}
|
||||
.flow {
|
||||
flex: 1 1 auto;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
--background-color: var(--spectrum-global-color-gray-50);
|
||||
--node-background: var(--spectrum-global-color-gray-100);
|
||||
--node-background-hover: var(--spectrum-global-color-gray-300);
|
||||
--border-color: var(--spectrum-global-color-gray-300);
|
||||
--edge-color: var(--spectrum-global-color-gray-500);
|
||||
--handle-color: var(--spectrum-global-color-gray-600);
|
||||
--selected-color: var(--spectrum-global-color-blue-400);
|
||||
}
|
||||
|
||||
/* Customise svelte-flow theme */
|
||||
.flow :global(.svelte-flow) {
|
||||
/* Panel */
|
||||
--xy-background-color: var(--background-color);
|
||||
|
||||
/* Controls */
|
||||
--xy-controls-button-background-color: var(--node-background);
|
||||
--xy-controls-button-background-color-hover: var(--node-background-hover);
|
||||
--xy-controls-button-border-color: var(--border-color);
|
||||
|
||||
/* Handles */
|
||||
--xy-handle-background-color: var(--handle-color);
|
||||
--xy-handle-border-color: var(--handle-color);
|
||||
|
||||
/* Edges */
|
||||
--xy-edge-stroke: var(--edge-color);
|
||||
--xy-edge-stroke-selected: var(--edge-color);
|
||||
--xy-edge-stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
<SvelteFlowProvider>
|
||||
<RoleFlow />
|
||||
</SvelteFlowProvider>
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
<script>
|
||||
import { Heading, Helpers } from "@budibase/bbui"
|
||||
import { writable, derived } from "svelte/store"
|
||||
import {
|
||||
SvelteFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Position,
|
||||
useSvelteFlow,
|
||||
} from "@xyflow/svelte"
|
||||
import "@xyflow/svelte/dist/style.css"
|
||||
import RoleNode from "./RoleNode.svelte"
|
||||
import RoleEdge from "./RoleEdge.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"
|
||||
import { meta } from "@roxi/routify"
|
||||
|
||||
const flow = useSvelteFlow()
|
||||
const nodes = writable([])
|
||||
const edges = writable([])
|
||||
const dragging = writable(false)
|
||||
const selectedNode = derived(
|
||||
nodes,
|
||||
$nodes => $nodes.find(x => x.selected)?.id
|
||||
)
|
||||
|
||||
// Ensure role changes are synced with nodes and edges
|
||||
$: handleExternalRoleChanges($roles)
|
||||
|
||||
// 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 nodes we want to persist some metadata
|
||||
nodes.set(
|
||||
newLayout.nodes.map(node => {
|
||||
const currentNode = currentNodes.find(x => x.id === node.id)
|
||||
if (!currentNode) {
|
||||
return node
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
position: currentNode.position || node.position,
|
||||
selected: currentNode.selected || node.selected,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Edges can always be updated
|
||||
edges.set(newLayout.edges)
|
||||
}
|
||||
|
||||
// Creates a new role
|
||||
const createRole = async () => {
|
||||
const newRole = {
|
||||
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,
|
||||
}
|
||||
|
||||
// Immediate state update
|
||||
const newNode = {
|
||||
...roleToNode({ ...newRole, _id: newRole.name }),
|
||||
selected: true,
|
||||
}
|
||||
const layout = autoLayout({
|
||||
nodes: [...$nodes.map(node => ({ ...node, selected: false })), newNode],
|
||||
edges: $edges,
|
||||
})
|
||||
nodes.set(layout.nodes)
|
||||
edges.set(layout.edges)
|
||||
|
||||
// Actually create role
|
||||
await roles.save(newRole)
|
||||
}
|
||||
|
||||
// Updates a role with new metadata
|
||||
const updateRole = async (roleId, metadata) => {
|
||||
// Don't update builtins
|
||||
const node = $nodes.find(x => x.id === roleId)
|
||||
if (!node || !node.data.custom) {
|
||||
return
|
||||
}
|
||||
|
||||
// Immediate state update
|
||||
if (metadata) {
|
||||
flow.updateNodeData(roleId, metadata)
|
||||
}
|
||||
|
||||
// Actually save changes
|
||||
const role = nodeToRole(node)
|
||||
await roles.save(role)
|
||||
}
|
||||
|
||||
// Deletes a role
|
||||
const deleteRole = async roleId => {
|
||||
// Immediate state update
|
||||
const layout = autoLayout({
|
||||
nodes: $nodes.filter(x => x.id !== roleId),
|
||||
edges: $edges.filter(x => x.source !== roleId && x.target !== roleId),
|
||||
})
|
||||
nodes.set(layout.nodes)
|
||||
edges.set(layout.edges)
|
||||
|
||||
// Actually delete role
|
||||
const role = $roles.find(x => x._id === roleId)
|
||||
if (role) {
|
||||
roles.delete(role)
|
||||
}
|
||||
}
|
||||
|
||||
// Saves a new connection
|
||||
const onConnect = async connection => {
|
||||
await updateRole(connection.target)
|
||||
}
|
||||
|
||||
setContext("flow", {
|
||||
nodes,
|
||||
edges,
|
||||
dragging,
|
||||
selectedNode,
|
||||
createRole,
|
||||
updateRole,
|
||||
deleteRole,
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
<div class="heading">
|
||||
<Heading size="S">Manage roles</Heading>
|
||||
</div>
|
||||
<div class="description">Roles inherit permissions from each other.</div>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<SvelteFlow
|
||||
fitView
|
||||
{nodes}
|
||||
{edges}
|
||||
snapGrid={[GridResolution, GridResolution]}
|
||||
nodeTypes={{ role: RoleNode }}
|
||||
edgeTypes={{ role: RoleEdge }}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
fitViewOptions={{ maxZoom: MaxAutoZoom }}
|
||||
defaultEdgeOptions={{ type: "role", animated: true, selectable: false }}
|
||||
onconnectstart={() => dragging.set(true)}
|
||||
onconnectend={() => dragging.set(false)}
|
||||
onconnect={onConnect}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<Controls />
|
||||
</SvelteFlow>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.description {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
margin-bottom: calc(20px - var(--spacing-l));
|
||||
}
|
||||
.flow {
|
||||
flex: 1 1 auto;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
--background-color: var(--spectrum-global-color-gray-50);
|
||||
--node-background: var(--spectrum-global-color-gray-100);
|
||||
--node-background-hover: var(--spectrum-global-color-gray-300);
|
||||
--border-color: var(--spectrum-global-color-gray-300);
|
||||
--edge-color: var(--spectrum-global-color-gray-500);
|
||||
--handle-color: var(--spectrum-global-color-gray-600);
|
||||
--selected-color: var(--spectrum-global-color-blue-400);
|
||||
}
|
||||
|
||||
/* Customise svelte-flow theme */
|
||||
.flow :global(.svelte-flow) {
|
||||
/* Panel */
|
||||
--xy-background-color: var(--background-color);
|
||||
|
||||
/* Controls */
|
||||
--xy-controls-button-background-color: var(--node-background);
|
||||
--xy-controls-button-background-color-hover: var(--node-background-hover);
|
||||
--xy-controls-button-border-color: var(--border-color);
|
||||
|
||||
/* Handles */
|
||||
--xy-handle-background-color: var(--handle-color);
|
||||
--xy-handle-border-color: var(--handle-color);
|
||||
|
||||
/* Edges */
|
||||
--xy-edge-stroke: var(--edge-color);
|
||||
--xy-edge-stroke-selected: var(--edge-color);
|
||||
--xy-edge-stroke-width: 2px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Handle, Position, useSvelteFlow } from "@xyflow/svelte"
|
||||
import { Handle, Position, useSvelteFlow, NodeToolbar } from "@xyflow/svelte"
|
||||
import {
|
||||
Icon,
|
||||
Input,
|
||||
|
@ -16,8 +16,9 @@
|
|||
|
||||
export let data
|
||||
export let id
|
||||
export let selected
|
||||
|
||||
const { nodes, edges, dragging, updateRole, deleteRole } = getContext("flow")
|
||||
const { dragging, updateRole, deleteRole } = getContext("flow")
|
||||
const flow = useSvelteFlow()
|
||||
|
||||
let anchor
|
||||
|
@ -48,34 +49,31 @@
|
|||
return null
|
||||
}
|
||||
|
||||
const openPopover = () => {
|
||||
const openPopover = e => {
|
||||
e.stopPropagation()
|
||||
tempDisplayName = data.displayName
|
||||
tempDescription = data.description
|
||||
tempColor = data.color
|
||||
modal.show()
|
||||
}
|
||||
|
||||
const saveChanges = async () => {
|
||||
const newData = {
|
||||
const saveChanges = () => {
|
||||
updateRole(id, {
|
||||
displayName: tempDisplayName,
|
||||
description: tempDescription,
|
||||
color: tempColor,
|
||||
}
|
||||
flow.updateNodeData(id, newData)
|
||||
await updateRole(id)
|
||||
})
|
||||
}
|
||||
|
||||
const doAutoLayout = () => {
|
||||
const layout = autoLayout({ nodes: $nodes, edges: $edges })
|
||||
nodes.set(layout.nodes)
|
||||
edges.set(layout.edges)
|
||||
flow.fitView({ maxZoom: MaxAutoZoom, duration: ZoomDuration })
|
||||
const handleDelete = async e => {
|
||||
e.stopPropagation()
|
||||
await deleteRole(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="node"
|
||||
class:selected={false}
|
||||
class:selected
|
||||
style={`--color:${data.color}; --width:${NodeWidth}px; --height:${NodeHeight}px;`}
|
||||
bind:this={anchor}
|
||||
>
|
||||
|
@ -88,12 +86,7 @@
|
|||
{#if data.custom}
|
||||
<div class="buttons">
|
||||
<Icon size="S" name="Edit" hoverable on:click={openPopover} />
|
||||
<Icon
|
||||
size="S"
|
||||
name="Delete"
|
||||
hoverable
|
||||
on:click={() => deleteRole(id)}
|
||||
/>
|
||||
<Icon size="S" name="Delete" hoverable on:click={handleDelete} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -153,9 +146,11 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
.node.selected {
|
||||
background: var(--spectrum-global-color-blue-100);
|
||||
cursor: grab;
|
||||
}
|
||||
.color {
|
||||
border-top-left-radius: 4px;
|
||||
|
@ -207,7 +202,7 @@
|
|||
color: var(--spectrum-global-color-gray-600);
|
||||
font-size: 12px;
|
||||
}
|
||||
.node:hover .buttons {
|
||||
.node.selected .buttons {
|
||||
display: flex;
|
||||
}
|
||||
.node :global(.svelte-flow__handle) {
|
||||
|
|
|
@ -23,7 +23,12 @@ export function createRolesStore() {
|
|||
roles.sort((a, b) => {
|
||||
const priorityA = RoleUtils.getRolePriority(a._id)
|
||||
const priorityB = RoleUtils.getRolePriority(b._id)
|
||||
return priorityA > priorityB ? -1 : 1
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityA > priorityB ? -1 : 1
|
||||
}
|
||||
const nameA = a.uiMetadata?.displayName || a.name
|
||||
const nameB = b.uiMetadata?.displayName || b.name
|
||||
return nameA < nameB ? -1 : 1
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue