mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
fix circular dependencies frontend
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
DraftService,
|
||||
type PathScript,
|
||||
ScriptService,
|
||||
Script
|
||||
Script,
|
||||
type HubScriptKind
|
||||
} from '$lib/gen'
|
||||
import { initHistory, push, redo, undo } from '$lib/history'
|
||||
import {
|
||||
@@ -413,7 +414,7 @@
|
||||
$copilotModulesStore[idx].hubCompletions = scripts as {
|
||||
path: string
|
||||
summary: string
|
||||
kind: string
|
||||
kind: HubScriptKind
|
||||
app: string
|
||||
ask_id: number
|
||||
}[]
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
</div>
|
||||
{#if copilotModule.source === 'hub' && copilotModule.selectedCompletion && copilotModule.selectedCompletion?.kind !== 'script'}
|
||||
<Badge color="gray" baseClass="border"
|
||||
>{capitalize(copilotModule.selectedCompletion.kind)}</Badge
|
||||
>{capitalize(copilotModule.selectedCompletion.kind.toString())}</Badge
|
||||
>
|
||||
{/if}
|
||||
|
||||
@@ -254,7 +254,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if item.kind !== 'script'}
|
||||
<Badge color="gray" baseClass="border">{capitalize(item.kind)}</Badge>
|
||||
<Badge color="gray" baseClass="border"
|
||||
>{capitalize(item.kind.toString())}</Badge
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Script, FlowModule } from '$lib/gen'
|
||||
import type { Script, FlowModule, HubScriptKind } from '$lib/gen'
|
||||
import { addResourceTypes, deltaCodeCompletion, getNonStreamingCompletion } from './lib'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type Editor from '../Editor.svelte'
|
||||
@@ -15,7 +15,7 @@ export type FlowCopilotModule = {
|
||||
hubCompletions: {
|
||||
path: string
|
||||
summary: string
|
||||
kind: string
|
||||
kind: HubScriptKind,
|
||||
app: string
|
||||
ask_id: number
|
||||
}[]
|
||||
@@ -23,7 +23,7 @@ export type FlowCopilotModule = {
|
||||
| {
|
||||
path: string
|
||||
summary: string
|
||||
kind: string
|
||||
kind: HubScriptKind
|
||||
app: string
|
||||
ask_id: number
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
|
||||
import { APP_TO_ICON_COMPONENT } from '$lib/components/icons'
|
||||
import ListFilters from '$lib/components/home/ListFilters.svelte'
|
||||
import { IntegrationService, ScriptService } from '$lib/gen'
|
||||
import { IntegrationService, ScriptService, type HubScriptKind } from '$lib/gen'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
|
||||
export let kind: HubScriptKind & string = 'script'
|
||||
export let filter = ''
|
||||
export let syncQuery = false
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
version_id: number
|
||||
ask_id: number
|
||||
app: string
|
||||
kind: typeof kind
|
||||
kind: HubScriptKind
|
||||
}[] = []
|
||||
|
||||
let allApps: string[] = []
|
||||
@@ -89,7 +89,7 @@
|
||||
id: number
|
||||
ask_id: number
|
||||
app: string
|
||||
kind: typeof kind
|
||||
kind: HubScriptKind
|
||||
}) => ({
|
||||
...x,
|
||||
path: `hub/${x.version_id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import type { EdgeType, NodeType, ResizeNodeType, StoreType } from '../../store/types/types'
|
||||
import type { CollapsibleType } from '../types/types'
|
||||
import { get } from 'svelte/store'
|
||||
import type { AnchorType } from '../../edges/types/types'
|
||||
import { getAnchorById } from '../../edges/controllers/util'
|
||||
import { getAnchors } from '../../edges/controllers/util'
|
||||
|
||||
|
||||
|
||||
|
||||
// Given a nodeId, find ids of all connecting target nodes
|
||||
function findTargets(store: StoreType, nodeId: string): string[] {
|
||||
// get source anchors on the node
|
||||
const anchors = getAnchors(store, {
|
||||
nodeId: nodeId,
|
||||
sourceOrTarget: 'source'
|
||||
})
|
||||
|
||||
// get target anchors on other node, and record the node id
|
||||
const targetNodeIds: string[] = []
|
||||
for (const anchor of anchors) {
|
||||
const targetAnchorId = anchor.getOtherAnchorId()
|
||||
const targetAnchor = getAnchorById(store, targetAnchorId)
|
||||
targetNodeIds.push(targetAnchor.nodeId)
|
||||
}
|
||||
return targetNodeIds
|
||||
}
|
||||
|
||||
// traverses tree and increments hideCount
|
||||
function traverseAndIncrement(
|
||||
store: StoreType,
|
||||
nodeId: string,
|
||||
operation: 'increment' | 'decrement'
|
||||
) {
|
||||
const collapsibles = get(store.collapsibleStore)
|
||||
recursiveTraverse(nodeId)
|
||||
store.collapsibleStore.set(collapsibles)
|
||||
|
||||
function recursiveTraverse(nId: string) {
|
||||
for (const collapsible of collapsibles) {
|
||||
if (collapsible.nodeId === nId) {
|
||||
if (operation === 'increment') collapsible.hideCount++
|
||||
else collapsible.hideCount--
|
||||
const targetIds = findTargets(store, nId)
|
||||
for (const targetId of targetIds) {
|
||||
recursiveTraverse(targetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collapse(store: StoreType, nodeId: string) {
|
||||
const targetNodeIds = findTargets(store, nodeId)
|
||||
for (const targetNodeId of targetNodeIds) traverseAndIncrement(store, targetNodeId, 'increment')
|
||||
}
|
||||
function expand(store: StoreType, nodeId: string) {
|
||||
const targetNodeIds = findTargets(store, nodeId)
|
||||
for (const targetNodeId of targetNodeIds) traverseAndIncrement(store, targetNodeId, 'decrement')
|
||||
}
|
||||
|
||||
export function toggleExpandAndCollapse(store: StoreType, nodeId: string) {
|
||||
const collapsibles = getCollapsibles(store, { nodeId: nodeId })
|
||||
if (collapsibles.length === 0) return // when the collapsible feature is disabled, there will be no collapbsible objects
|
||||
if (collapsibles.length > 1) throw 'there should only be one collapsible object per node'
|
||||
const collapsible = collapsibles[0]
|
||||
if (collapsible.state === 'expanded') collapse(store, nodeId)
|
||||
else expand(store, nodeId)
|
||||
store.collapsibleStore.update((arr) => {
|
||||
for (const c of arr) if (c.id === collapsible.id) c.toggleState()
|
||||
return [...arr]
|
||||
})
|
||||
}
|
||||
|
||||
export function getCollapsibles(store: StoreType, filter?: { [key: string]: any }) {
|
||||
let collapsibles = Object.values(get(store.collapsibleStore))
|
||||
// filter the array of anchors for elements that match filter
|
||||
// Example: if filter = {sourceOrTarget: 'source', positionX: 35} then we will
|
||||
//return all anchors with sourceOrTarget = source AND poxitionX = 35
|
||||
if (filter !== undefined) {
|
||||
collapsibles = collapsibles.filter((collapsible) => {
|
||||
for (let filterKey in filter) {
|
||||
const filterValue = filter[filterKey]
|
||||
if (collapsible[filterKey as keyof CollapsibleType] !== filterValue) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
// return list of anchors
|
||||
return collapsibles
|
||||
}
|
||||
|
||||
/*
|
||||
This function is responsible for filtering nodes should be displayed based on Collapsible.
|
||||
It also filters node-associated elements such as anchors, edges, etc. so that when you collapse a node, the
|
||||
edges also hide.
|
||||
There is a better way to implement this with foreign keys; when collapsing a node, you would also collapse any rows with a foreign key
|
||||
linking to that node (like a cascading delete in SQL, but with hiding instead of deleting)
|
||||
*/
|
||||
export function filterByCollapsible(
|
||||
store: StoreType,
|
||||
nodes: NodeType[],
|
||||
resizeNodes: ResizeNodeType[],
|
||||
anchors: AnchorType[],
|
||||
edges: EdgeType[]
|
||||
) {
|
||||
// filter nodes for the collapsible nodes feature
|
||||
const filteredNodes = nodes.filter((node) => {
|
||||
const nodeId = node.id
|
||||
const collapssibleObj = get(store.collapsibleStore).find((e) => e.nodeId === nodeId)
|
||||
if (collapssibleObj === undefined) return true
|
||||
return collapssibleObj.isHidden() === false
|
||||
})
|
||||
const filteredNodeIds = filteredNodes.map((e) => e.id)
|
||||
// filter resizeNodes
|
||||
const filteredResizeNodes = resizeNodes.filter((resizeNode) =>
|
||||
filteredNodeIds.includes(resizeNode.nodeId)
|
||||
)
|
||||
const filteredAnchors = anchors.filter((selfAnchor) => {
|
||||
const otherAnchorId = selfAnchor.getOtherAnchorId()
|
||||
const otherAnchor = get(store.anchorsStore)[otherAnchorId]
|
||||
|
||||
if (filteredNodeIds.includes(selfAnchor.nodeId) && filteredNodeIds.includes(otherAnchor.nodeId))
|
||||
return true
|
||||
return false
|
||||
})
|
||||
const filteredEdgeIds = new Set(filteredAnchors.map((e) => e.edgeId))
|
||||
const filteredEdges = edges.filter((edge) => filteredEdgeIds.has(edge.id))
|
||||
|
||||
return {
|
||||
filteredNodes,
|
||||
filteredResizeNodes,
|
||||
filteredAnchors,
|
||||
filteredEdges
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* This model implements functionality for nodes to expand and collapse their children
|
||||
*/
|
||||
|
||||
import type { CollapsibleType } from '../types/types';
|
||||
|
||||
/** Class that implements collapsible/expandable functionality for Node objects
|
||||
* @param {string} id Unique string that serves as a primary key
|
||||
* @param {string} nodeId Foreign key to a Node Object
|
||||
*/
|
||||
export class Collapsible implements CollapsibleType {
|
||||
constructor(
|
||||
public id: string,
|
||||
public nodeId: string,
|
||||
public hideCount: number,
|
||||
public state: 'expanded' | 'collapsed'
|
||||
) {}
|
||||
|
||||
isHidden() {
|
||||
return this.hideCount > 0;
|
||||
}
|
||||
|
||||
toggleState() {
|
||||
this.state = this.state === 'expanded' ? 'collapsed' : 'expanded';
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface CollapsibleType {
|
||||
id: string;
|
||||
nodeId: string;
|
||||
hideCount: number;
|
||||
state: 'expanded' | 'collapsed';
|
||||
isHidden: Function;
|
||||
toggleState: Function;
|
||||
}
|
||||
@@ -3,30 +3,16 @@ This file contains "middleware" functions that sanitize user input (UserNodeType
|
||||
maintain consistency between previous
|
||||
|
||||
*/
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
bottomCb,
|
||||
leftCb,
|
||||
rightCb,
|
||||
topCb,
|
||||
} from '../../edges/controllers/anchorCbUser';
|
||||
import type { StoreType } from '../../store/types/types';
|
||||
import type { UserEdgeType, UserNodeType } from '../../types/types';
|
||||
|
||||
/**
|
||||
* sanitizeCanvasOptions will sanitize the canvas level options so that incompatible features will not be run simulataneously
|
||||
* @param store The array of nodes that have a UserNodeType
|
||||
* @returns void. The store is modified directly
|
||||
*/
|
||||
export function sanitizeCanvasOptions(store: StoreType) {
|
||||
enforceCollapsibleCompatibility(store);
|
||||
}
|
||||
|
||||
function enforceCollapsibleCompatibility(store: StoreType) {
|
||||
if (get(store.collapsibleOption)) {
|
||||
store.nodeCreate.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sanitizeUserNodesAndEdges will sanitize the data initially passed in to Svelvet component. For example, the node that user specified have an integar as its id but to instantiate a Node and be compatible with uuid we will need to convert the integar id to a string.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Node from '../../nodes/views/Node.svelte'
|
||||
|
||||
import { determineD3Instance } from '../..//d3/controllers/d3'
|
||||
import { findStore } from '../../store/controllers/storeApi'
|
||||
import { findStore } from '../../store/models/store'
|
||||
|
||||
import { Expand, Minus, Plus } from 'lucide-svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
} from '../../store/controllers/storeApi'
|
||||
import { afterUpdate, onMount, getContext } from 'svelte'
|
||||
import GraphView from './GraphView.svelte'
|
||||
import { sanitizeCanvasOptions, sanitizeUserNodesAndEdges } from '../controllers/middleware'
|
||||
import { sanitizeUserNodesAndEdges } from '../controllers/middleware'
|
||||
import { SVELVET_CONTEXT_KEY, type SvelvetSettingsContext } from '../models'
|
||||
|
||||
const settings = getContext<SvelvetSettingsContext | undefined>(SVELVET_CONTEXT_KEY)
|
||||
@@ -24,7 +24,6 @@
|
||||
export let snapTo: number = 30
|
||||
export let nodeCreate: boolean = false
|
||||
export let boundary = false
|
||||
export let collapsible = false
|
||||
export let locked: boolean = false // if true, node movement is disabled
|
||||
export let editable: boolean = false
|
||||
export let highlightEdges: boolean = true
|
||||
@@ -58,13 +57,10 @@
|
||||
store.options.set(optionsObj) //
|
||||
store.nodeCreate.set(nodeCreate)
|
||||
store.boundary.set(boundary)
|
||||
store.collapsibleOption.set(collapsible)
|
||||
store.lockedOption.set(locked)
|
||||
store.editableOption.set(editable)
|
||||
store.highlightEdgesOption.set(highlightEdges)
|
||||
|
||||
// make sure that all canvas options are compatible
|
||||
sanitizeCanvasOptions(store)
|
||||
// set node/edge related stores
|
||||
populateSvelvetStoreFromUserInput(canvasId, userNodes, userEdges)
|
||||
error = ''
|
||||
@@ -92,13 +88,10 @@
|
||||
store.options.set(optionsObj) //
|
||||
store.nodeCreate.set(nodeCreate)
|
||||
store.boundary.set(boundary)
|
||||
store.collapsibleOption.set(collapsible)
|
||||
store.lockedOption.set(locked)
|
||||
store.editableOption.set(editable)
|
||||
store.highlightEdgesOption.set(highlightEdges)
|
||||
|
||||
// make sure that all canvas options are compatible
|
||||
sanitizeCanvasOptions(store)
|
||||
// set node/edge related stores
|
||||
populateSvelvetStoreFromUserInput(canvasId, userNodes, userEdges)
|
||||
error = ''
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { findStore } from '../../store/controllers/storeApi'
|
||||
import { findStore } from '../../store/models/store'
|
||||
import type { UserEdgeType } from '../../types/types'
|
||||
import type { EdgeType } from '../../store/types/types'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { findStore } from '../../../store/controllers/storeApi'
|
||||
import { findStore } from '../../../store/models/store'
|
||||
import { getEdgeById } from '../../../edges/controllers/util'
|
||||
import EdgeText from '../Edges/EdgeText.svelte'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
const Position = { Left: 'left', Right: 'right', Top: 'top', Bottom: 'bottom' }
|
||||
|
||||
import { findStore } from '../../../store/controllers/storeApi'
|
||||
import { findStore } from '../../../store/models/store'
|
||||
import { getAnchorFromEdge } from '../../../edges/controllers/util'
|
||||
|
||||
function calculateControlOffset(distance, curvature) {
|
||||
|
||||
+150
-169
@@ -1,176 +1,157 @@
|
||||
<script>
|
||||
import BaseEdge from './BaseEdge.svelte';
|
||||
import { getCenter } from './utils';
|
||||
import BaseEdge from './BaseEdge.svelte'
|
||||
import { getCenter } from './utils'
|
||||
|
||||
const Position = {};
|
||||
Position['Left'] = 'left';
|
||||
Position['Right'] = 'right';
|
||||
Position['Top'] = 'top';
|
||||
Position['Bottom'] = 'bottom';
|
||||
// These are some helper methods for drawing the round corners
|
||||
// The name indicates the direction of the path. "bottomLeftCorner" goes
|
||||
// from bottom to the left and "leftBottomCorner" goes from left to the bottom.
|
||||
// We have to consider the direction of the paths because of the animated lines.
|
||||
const bottomLeftCorner = (x, y, size) =>
|
||||
`L ${x},${y - size}Q ${x},${y} ${x + size},${y}`;
|
||||
const leftBottomCorner = (x, y, size) =>
|
||||
`L ${x + size},${y}Q ${x},${y} ${x},${y - size}`;
|
||||
const bottomRightCorner = (x, y, size) =>
|
||||
`L ${x},${y - size}Q ${x},${y} ${x - size},${y}`;
|
||||
const rightBottomCorner = (x, y, size) =>
|
||||
`L ${x - size},${y}Q ${x},${y} ${x},${y - size}`;
|
||||
const leftTopCorner = (x, y, size) =>
|
||||
`L ${x + size},${y}Q ${x},${y} ${x},${y + size}`;
|
||||
const topLeftCorner = (x, y, size) =>
|
||||
`L ${x},${y + size}Q ${x},${y} ${x + size},${y}`;
|
||||
const topRightCorner = (x, y, size) =>
|
||||
`L ${x},${y + size}Q ${x},${y} ${x - size},${y}`;
|
||||
const rightTopCorner = (x, y, size) =>
|
||||
`L ${x - size},${y}Q ${x},${y} ${x},${y + size}`;
|
||||
// returns string to pass into edge 'path' svg d attribute (where to be drawn)
|
||||
export function getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
borderRadius = 5,
|
||||
centerX,
|
||||
centerY,
|
||||
}) {
|
||||
const [_centerX, _centerY, offsetX, offsetY] = getCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX));
|
||||
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY));
|
||||
const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY);
|
||||
const leftAndRight = [Position.Left, Position.Right];
|
||||
const cX = typeof centerX !== 'undefined' ? centerX : _centerX;
|
||||
const cY = typeof centerY !== 'undefined' ? centerY : _centerY;
|
||||
let firstCornerPath = null;
|
||||
let secondCornerPath = null;
|
||||
// for non-mixed edge top/bottom
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(sourceX, cY, cornerSize)
|
||||
: topLeftCorner(sourceX, cY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(targetX, cY, cornerSize)
|
||||
: rightBottomCorner(targetX, cY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY < targetY
|
||||
? bottomRightCorner(sourceX, cY, cornerSize)
|
||||
: topRightCorner(sourceX, cY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY < targetY
|
||||
? leftTopCorner(targetX, cY, cornerSize)
|
||||
: leftBottomCorner(targetX, cY, cornerSize);
|
||||
}
|
||||
// for non-mixed edge left/right
|
||||
if (
|
||||
leftAndRight.includes(sourcePosition) &&
|
||||
leftAndRight.includes(targetPosition)
|
||||
) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(cX, sourceY, cornerSize)
|
||||
: rightBottomCorner(cX, sourceY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(cX, targetY, cornerSize)
|
||||
: topLeftCorner(cX, targetY, cornerSize);
|
||||
} else if (
|
||||
(sourcePosition === Position.Right &&
|
||||
targetPosition === Position.Left) ||
|
||||
(sourcePosition === Position.Left &&
|
||||
targetPosition === Position.Right) ||
|
||||
(sourcePosition === Position.Left && targetPosition === Position.Left)
|
||||
) {
|
||||
// and sourceX > targetX
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? leftTopCorner(cX, sourceY, cornerSize)
|
||||
: leftBottomCorner(cX, sourceY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomRightCorner(cX, targetY, cornerSize)
|
||||
: topRightCorner(cX, targetY, cornerSize);
|
||||
}
|
||||
// for mixed edges (top/bottom to left/right) OR (left/right to top/bottom)
|
||||
} else if (
|
||||
leftAndRight.includes(sourcePosition) &&
|
||||
!leftAndRight.includes(targetPosition)
|
||||
) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(targetX, sourceY, cornerSize)
|
||||
: rightBottomCorner(targetX, sourceY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? leftTopCorner(targetX, sourceY, cornerSize)
|
||||
: leftBottomCorner(targetX, sourceY, cornerSize);
|
||||
}
|
||||
secondCornerPath = '';
|
||||
} else if (
|
||||
!leftAndRight.includes(sourcePosition) &&
|
||||
leftAndRight.includes(targetPosition)
|
||||
) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(sourceX, targetY, cornerSize)
|
||||
: topLeftCorner(sourceX, targetY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomRightCorner(sourceX, targetY, cornerSize)
|
||||
: topRightCorner(sourceX, targetY, cornerSize);
|
||||
}
|
||||
secondCornerPath = '';
|
||||
}
|
||||
return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`;
|
||||
}
|
||||
export let edge;
|
||||
export let borderRadius = 5;
|
||||
export let canvasId;
|
||||
const Position = {}
|
||||
Position['Left'] = 'left'
|
||||
Position['Right'] = 'right'
|
||||
Position['Top'] = 'top'
|
||||
Position['Bottom'] = 'bottom'
|
||||
// These are some helper methods for drawing the round corners
|
||||
// The name indicates the direction of the path. "bottomLeftCorner" goes
|
||||
// from bottom to the left and "leftBottomCorner" goes from left to the bottom.
|
||||
// We have to consider the direction of the paths because of the animated lines.
|
||||
const bottomLeftCorner = (x, y, size) => `L ${x},${y - size}Q ${x},${y} ${x + size},${y}`
|
||||
const leftBottomCorner = (x, y, size) => `L ${x + size},${y}Q ${x},${y} ${x},${y - size}`
|
||||
const bottomRightCorner = (x, y, size) => `L ${x},${y - size}Q ${x},${y} ${x - size},${y}`
|
||||
const rightBottomCorner = (x, y, size) => `L ${x - size},${y}Q ${x},${y} ${x},${y - size}`
|
||||
const leftTopCorner = (x, y, size) => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}`
|
||||
const topLeftCorner = (x, y, size) => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}`
|
||||
const topRightCorner = (x, y, size) => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`
|
||||
const rightTopCorner = (x, y, size) => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`
|
||||
// returns string to pass into edge 'path' svg d attribute (where to be drawn)
|
||||
export function getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
borderRadius = 5,
|
||||
centerX,
|
||||
centerY
|
||||
}) {
|
||||
const [_centerX, _centerY, offsetX, offsetY] = getCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY
|
||||
})
|
||||
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX))
|
||||
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY))
|
||||
const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY)
|
||||
const leftAndRight = [Position.Left, Position.Right]
|
||||
const cX = typeof centerX !== 'undefined' ? centerX : _centerX
|
||||
const cY = typeof centerY !== 'undefined' ? centerY : _centerY
|
||||
let firstCornerPath = null
|
||||
let secondCornerPath = null
|
||||
// for non-mixed edge top/bottom
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(sourceX, cY, cornerSize)
|
||||
: topLeftCorner(sourceX, cY, cornerSize)
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(targetX, cY, cornerSize)
|
||||
: rightBottomCorner(targetX, cY, cornerSize)
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY < targetY
|
||||
? bottomRightCorner(sourceX, cY, cornerSize)
|
||||
: topRightCorner(sourceX, cY, cornerSize)
|
||||
secondCornerPath =
|
||||
sourceY < targetY
|
||||
? leftTopCorner(targetX, cY, cornerSize)
|
||||
: leftBottomCorner(targetX, cY, cornerSize)
|
||||
}
|
||||
// for non-mixed edge left/right
|
||||
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(cX, sourceY, cornerSize)
|
||||
: rightBottomCorner(cX, sourceY, cornerSize)
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(cX, targetY, cornerSize)
|
||||
: topLeftCorner(cX, targetY, cornerSize)
|
||||
} else if (
|
||||
(sourcePosition === Position.Right && targetPosition === Position.Left) ||
|
||||
(sourcePosition === Position.Left && targetPosition === Position.Right) ||
|
||||
(sourcePosition === Position.Left && targetPosition === Position.Left)
|
||||
) {
|
||||
// and sourceX > targetX
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? leftTopCorner(cX, sourceY, cornerSize)
|
||||
: leftBottomCorner(cX, sourceY, cornerSize)
|
||||
secondCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomRightCorner(cX, targetY, cornerSize)
|
||||
: topRightCorner(cX, targetY, cornerSize)
|
||||
}
|
||||
// for mixed edges (top/bottom to left/right) OR (left/right to top/bottom)
|
||||
} else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(targetX, sourceY, cornerSize)
|
||||
: rightBottomCorner(targetX, sourceY, cornerSize)
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? leftTopCorner(targetX, sourceY, cornerSize)
|
||||
: leftBottomCorner(targetX, sourceY, cornerSize)
|
||||
}
|
||||
secondCornerPath = ''
|
||||
} else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(sourceX, targetY, cornerSize)
|
||||
: topLeftCorner(sourceX, targetY, cornerSize)
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomRightCorner(sourceX, targetY, cornerSize)
|
||||
: topRightCorner(sourceX, targetY, cornerSize)
|
||||
}
|
||||
secondCornerPath = ''
|
||||
}
|
||||
return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`
|
||||
}
|
||||
export let edge
|
||||
export let borderRadius = 5
|
||||
export let canvasId
|
||||
|
||||
import { findStore } from '../../../store/controllers/storeApi';
|
||||
import { getAnchorFromEdge } from '../../../edges/controllers/util';
|
||||
import { findStore } from '../../../store/models/store'
|
||||
import { getAnchorFromEdge } from '../../../edges/controllers/util'
|
||||
|
||||
let params;
|
||||
$: {
|
||||
const store = findStore(canvasId);
|
||||
const sourceAnchor = getAnchorFromEdge(store, edge.id, 'source');
|
||||
const targetAnchor = getAnchorFromEdge(store, edge.id, 'target');
|
||||
const mapAngle = { 0: 'right', 90: 'top', 180: 'left', 270: 'bottom' };
|
||||
params = {
|
||||
sourceX: edge.sourceX,
|
||||
sourceY: edge.sourceY,
|
||||
targetX: edge.targetX,
|
||||
targetY: edge.targetY,
|
||||
sourcePosition: mapAngle[sourceAnchor.angle],
|
||||
targetPosition: mapAngle[targetAnchor.angle],
|
||||
borderRadius: borderRadius,
|
||||
};
|
||||
}
|
||||
$: [centerX, centerY] = getCenter(params);
|
||||
$: path = getSmoothStepPath(params);
|
||||
$: baseEdgeProps = {
|
||||
...edge,
|
||||
path: path,
|
||||
centerX: centerX,
|
||||
centerY: centerY,
|
||||
};
|
||||
let params
|
||||
$: {
|
||||
const store = findStore(canvasId)
|
||||
const sourceAnchor = getAnchorFromEdge(store, edge.id, 'source')
|
||||
const targetAnchor = getAnchorFromEdge(store, edge.id, 'target')
|
||||
const mapAngle = { 0: 'right', 90: 'top', 180: 'left', 270: 'bottom' }
|
||||
params = {
|
||||
sourceX: edge.sourceX,
|
||||
sourceY: edge.sourceY,
|
||||
targetX: edge.targetX,
|
||||
targetY: edge.targetY,
|
||||
sourcePosition: mapAngle[sourceAnchor.angle],
|
||||
targetPosition: mapAngle[targetAnchor.angle],
|
||||
borderRadius: borderRadius
|
||||
}
|
||||
}
|
||||
$: [centerX, centerY] = getCenter(params)
|
||||
$: path = getSmoothStepPath(params)
|
||||
$: baseEdgeProps = {
|
||||
...edge,
|
||||
path: path,
|
||||
centerX: centerX,
|
||||
centerY: centerY
|
||||
}
|
||||
</script>
|
||||
|
||||
<BaseEdge {baseEdgeProps} {canvasId} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from 'svelte'
|
||||
|
||||
import { findStore } from '../../store/controllers/storeApi'
|
||||
import { findStore } from '../../store/models/store'
|
||||
import type { NodeType } from '../../store/types/types'
|
||||
|
||||
import { forceCssHeightAndWidth } from '../../customCss/controllers/getCss'
|
||||
|
||||
@@ -30,22 +30,13 @@ populateSvelvetStoreFromUserInput(canvasId, nodes, edges)
|
||||
- edges: same as nodes, this is an array of objects containing edge info THAT IS DIFFERENT FROM THE EDGE CLASS.
|
||||
- Returns: store
|
||||
*/
|
||||
import { stores } from '../models/store'
|
||||
import { findStore, stores } from '../models/store'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
import type { StoreType } from '../types/types'
|
||||
import type { UserNodeType, UserEdgeType } from '../../types/types'
|
||||
import { populateAnchorsStore, populateNodesStore, populateEdgesStore } from './util'
|
||||
|
||||
/**
|
||||
* findStore is going to return the target Svelvet store with the canvasId provided as argument.
|
||||
* There can be multiple Svelvet canvases on the same page, and each has their own store with a unique canvasId.
|
||||
* @param canvasId The canvasId of a Svelvet component
|
||||
* @returns The store of a Svelvet component that matches the canvasId
|
||||
*/
|
||||
export function findStore(canvasId: string): StoreType {
|
||||
return stores[canvasId]
|
||||
}
|
||||
|
||||
/**
|
||||
* createStoreEmpty will initialize a new Svelvet store with a unique canvasId.
|
||||
@@ -79,8 +70,6 @@ export function createStoreEmpty(canvasId: string): StoreType {
|
||||
nodeCreate: writable(false), // this option sets whether the "nodeEdit" feature is enabled
|
||||
boundary: writable(false),
|
||||
edgeEditModal: writable(null), // this is used for edgeEditModal feature. When an edge is right clicked, store.edgeEditModal is set to the edgeId string. This causes a modal to be rendered
|
||||
collapsibleStore: writable([]), // this is used for the collaspsible node feature. If the feature is enabled, store.collapsible will be populated with Collapsible objects which will track whether the node should be displayed or not
|
||||
collapsibleOption: writable(false),
|
||||
lockedOption: writable(false),
|
||||
editableOption: writable(false), // true if you want nodes/edges to be editable. See feature editEdges
|
||||
d3ZoomParameters: writable({}), // this stores d3 parameters x, y, and zoom. This isn't used for anything other than giving users a way to access d3 zoom parameters if they want to build on top of Svelvet
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { findStore } from './storeApi';
|
||||
import { findStore } from '../models/store';
|
||||
import { get } from 'svelte/store';
|
||||
export function getD3PositionX(canvasId) {
|
||||
const store = findStore(canvasId);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { StoreType } from '../types/types';
|
||||
|
||||
|
||||
export function findStore(canvasId: string): StoreType {
|
||||
return stores[canvasId]
|
||||
}
|
||||
/**
|
||||
`store` is a dictionary of Svelvet stores.
|
||||
* The reason why we have multiple Svelvet stores is to handle multiple canvases on the same page.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { CollapsibleType } from '../../collapsible/types/types'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { AnchorType } from '../../edges/types/types'
|
||||
|
||||
@@ -35,8 +34,6 @@ export interface StoreType {
|
||||
nodeCreate: Writable<boolean> // this option sets whether the "nodeEdit" feature is enabled
|
||||
boundary: Writable<boolean | PositionType>
|
||||
edgeEditModal: Writable<null | string> // this options is used to place the edgeEdit modal when an edge is right-clicked. null is no modal, positionType if modal should be placed at position defined by postionType.x, positionType.y
|
||||
collapsibleStore: Writable<CollapsibleType[]>
|
||||
collapsibleOption: Writable<boolean>
|
||||
lockedOption: Writable<boolean>
|
||||
editableOption: Writable<boolean>
|
||||
d3ZoomParameters: Writable<{
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface UserEdgeType {
|
||||
offset?: number
|
||||
}
|
||||
|
||||
import { findStore } from '../store/controllers/storeApi'
|
||||
import { findStore } from '../store/models/store'
|
||||
import { get } from 'svelte/store'
|
||||
export function getD3PositionX(canvasId: string) {
|
||||
const store = findStore(canvasId)
|
||||
|
||||
Reference in New Issue
Block a user