apps rich configuration v0 + resource picker clear fix for apps

This commit is contained in:
Ruben Fiszel
2023-03-17 12:25:03 +01:00
parent 4305670d90
commit a76f6f7bd9
59 changed files with 785 additions and 1251 deletions
@@ -75,6 +75,8 @@
}}
/>
{initialValue}
{value}
<div class="flex flex-row gap-x-1 w-full">
<Select
value={valueSelect}
@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -15,7 +15,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -14,7 +14,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -16,7 +16,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -13,16 +13,21 @@
} from 'chart.js'
import type { ChartOptions } from 'chart.js'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import type { AppInput, RichAppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -1,7 +1,11 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { concatCustomCss } from '../../utils'
import { AlignWrapper, InputValue } from '../helpers'
import { loadIcon } from '../icon'
@@ -9,7 +13,7 @@
export let id: string
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
export let render: boolean
@@ -2,15 +2,14 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
type FitOption = (typeof staticValues)['objectFitOptions'][number]
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
export let render: boolean
@@ -1,8 +1,12 @@
<script lang="ts">
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
@@ -28,7 +32,7 @@
} as const
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
export let render: boolean
@@ -3,8 +3,7 @@
import { twMerge } from 'tailwind-merge'
import { getDocument, type PDFDocumentProxy, type PDFPageProxy } from 'pdfjs-dist'
import 'pdfjs-dist/build/pdf.worker.entry'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { throttle } from '../../../../utils'
@@ -14,7 +13,7 @@
import { findGridItem, initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -14,14 +14,19 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -19,12 +19,17 @@
import type { ChartOptions, ChartData } from 'chart.js'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -8,7 +8,13 @@
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import Tooltip from '$lib/components/Tooltip.svelte'
@@ -17,7 +23,7 @@
export let componentInput: AppInput | undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let render: boolean
@@ -20,14 +20,19 @@
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -2,13 +2,13 @@
import { getContext, onMount } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import type { AppViewerContext, RichConfiguration, RichConfigurations } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let render: boolean
@@ -5,7 +5,7 @@
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppViewerContext } from '../../../types'
import type { AppViewerContext, RichConfiguration, RichConfigurations } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -16,7 +16,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let render: boolean
@@ -1,7 +1,12 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppViewerContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
import type {
AppViewerContext,
BaseAppComponent,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -20,7 +25,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export let initializing: boolean | undefined = undefined
export let customCss:
@@ -237,6 +242,7 @@
{#if context}
{@const component = renderCell(cell.column.columnDef.cell, context)}
<td
on:keydown={() => toggleRow(row, rowIndex)}
on:click={() => toggleRow(row, rowIndex)}
class="p-4 whitespace-pre-wrap truncate text-xs text-gray-900"
>
@@ -2,14 +2,14 @@
import { isCodeInjection } from '$lib/components/flows/utils'
import { deepEqual } from 'fast-equals'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import type { AppInput, EvalAppInput, RichAppInput, UploadAppInput } from '../../inputType'
import type { AppViewerContext, RichConfiguration } from '../../types'
import { accessPropertyByPath } from '../../utils'
import { computeGlobalContext, eval_like } from './eval'
type T = string | number | boolean | Record<string | number, any> | undefined
export let input: AppInput | undefined
export let input: AppInput | RichConfiguration
export let value: T
export let id: string | undefined = undefined
export let error: string = ''
@@ -64,8 +64,7 @@
async function handleConnection() {
if (lastInput.type === 'connected') {
console.log('connected', lastInput, `${id}-${key}`)
$worldStore?.connect<any>(lastInput, onValueChange, id)
$worldStore?.connect<any>(lastInput, onValueChange, `${id}-${key}`)
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
value = await getValue(lastInput)
} else if (lastInput.type == 'eval') {
@@ -4,13 +4,18 @@
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
@@ -2,15 +2,13 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let inputType: 'date'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
@@ -5,12 +5,12 @@
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -3,7 +3,7 @@
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -11,7 +11,7 @@
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
@@ -2,15 +2,13 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
@@ -11,7 +11,7 @@
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'handles' | 'bar' | 'limits' | 'values'> | undefined =
undefined
@@ -3,7 +3,7 @@
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -11,7 +11,7 @@
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { twMerge } from 'tailwind-merge'
@@ -11,7 +11,7 @@
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value' | 'bar'> | undefined =
undefined
@@ -4,13 +4,13 @@
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let inputType = 'text'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
@@ -4,14 +4,14 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../../inputType'
import type { Output } from '../../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../../types'
import { concatCustomCss } from '../../../utils'
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
import InputValue from '../../helpers/InputValue.svelte'
import CurrencyInput from './CurrencyInput.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
@@ -6,7 +6,6 @@
import { concatCustomCss } from '../../utils'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -6,6 +6,7 @@
AppViewerContext,
ComponentCustomCSS,
HorizontalAlignment,
RichConfigurations,
VerticalAlignment
} from '../../types'
import { concatCustomCss } from '../../utils'
@@ -13,7 +14,7 @@
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'divider'> | undefined = undefined
@@ -2,7 +2,7 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import Portal from 'svelte-portal'
import { concatCustomCss } from '../../utils'
@@ -12,7 +12,7 @@
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
@@ -2,14 +2,18 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfigurations
} from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { deepEqual } from 'fast-equals'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let horizontal: boolean = false
@@ -4,12 +4,12 @@
import { initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let configuration: RichConfigurations
export let componentContainerHeight: number
export let tabs: string[]
export let customCss:
@@ -353,7 +353,6 @@
/>
{:else if component.type === 'containercomponent'}
<AppContainer
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
{componentContainerHeight}
@@ -361,7 +360,6 @@
/>
{:else if component.type === 'verticalsplitpanescomponent'}
<AppSplitpanes
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
panes={component.panes}
@@ -370,7 +368,6 @@
/>
{:else if component.type === 'horizontalsplitpanescomponent'}
<AppSplitpanes
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
panes={component.panes}
@@ -36,7 +36,8 @@
{#each $app.grid as gridItem (gridItem?.data?.id)}
{#if gridItem?.data?.id && gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
defaultUserInput={gridItem?.data?.type == 'formcomponent'}
defaultUserInput={gridItem?.data?.type == 'formcomponent' ||
gridItem?.data?.type == 'formbuttoncomponent'}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
@@ -59,7 +60,8 @@
{#each $app.subgrids[key] as gridItem (gridItem?.data?.id)}
{#if gridItem?.data?.id && gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
defaultUserInput={gridItem.data?.type == 'formcomponent'}
defaultUserInput={gridItem.data?.type == 'formcomponent' ||
gridItem?.data?.type == 'formbuttoncomponent'}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
@@ -1,5 +1,11 @@
<script lang="ts">
import VariableEditor from '$lib/components/VariableEditor.svelte'
import { Badge, ToggleButton, ToggleButtonGroup } from '$lib/components/common'
import { addWhitespaceBeforeCapitals, capitalize } from '$lib/utils'
import { faArrowRight, faPen, faUpload, faUser } from '@fortawesome/free-solid-svg-icons'
import { fieldTypeToTsType } from '../../utils'
import Tooltip from '$lib/components/Tooltip.svelte'
import Popover from '$lib/components/Popover.svelte'
import type {
ConnectedAppInput,
EvalAppInput,
@@ -13,28 +19,120 @@
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'
export let id: string
export let componentInput:
export let componentInput: (
| StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
) & { tooltip?: string; onlyStatic?: boolean }
export let key: string
export let hasRows: boolean = false
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let resourceOnly = false
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#if componentInput.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput.type === 'static'}
<StaticInputEditor bind:componentInput />
{:else if componentInput.type === 'eval'}
<EvalInputEditor {hasRows} {id} bind:componentInput />
{:else if componentInput.type === 'upload'}
<UploadInputEditor bind:componentInput />
{:else if componentInput.type === 'user'}
<span class="text-2xs italic text-gray-6f00">Field's value is set by the user</span>
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
{#if componentInput.tooltip}
<Tooltip>
{componentInput.tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{componentInput.fieldType === 'array' && componentInput.subFieldType
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
</Badge>
{#if !componentInput.onlyStatic && componentInput.type != 'eval'}
<ToggleButtonGroup
bind:selected={componentInput.type}
on:selected={(e) => {
if (e.detail == 'connected' && !componentInput['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{#if userInputEnabled && !componentInput.format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
</Popover>
{/if}
{#if 'fileUpload' in componentInput}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="upload"
startIcon={{ icon: faUpload }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Upload</svelte:fragment>
</Popover>
{/if}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
</div>
</div>
{#if componentInput.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput.type === 'static'}
<StaticInputEditor bind:componentInput />
{:else if componentInput.type === 'eval'}
<EvalInputEditor {hasRows} {id} bind:componentInput />
{:else if componentInput.type === 'upload'}
<UploadInputEditor bind:componentInput />
{:else if componentInput.type === 'user'}
<span class="text-2xs italic text-gray-6f00">Field's value is set by the user</span>
{/if}
</div>
{/if}
@@ -1,117 +1,36 @@
<script lang="ts">
import { Badge, ToggleButton, ToggleButtonGroup } from '$lib/components/common'
import { addWhitespaceBeforeCapitals, capitalize } from '$lib/utils'
import { faArrowRight, faPen, faUpload, faUser } from '@fortawesome/free-solid-svg-icons'
import { fieldTypeToTsType } from '../../utils'
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../../inputType'
import type { RichConfigurations } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
import { getContext } from 'svelte'
import type { AppViewerContext, BaseAppComponent } from '../../types'
import Tooltip from '$lib/components/Tooltip.svelte'
import Popover from '$lib/components/Popover.svelte'
export let id: string
export let inputSpecs: BaseAppComponent['configuration']
export let inputSpecs:
| RichConfigurations
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let rowColumns = false
export let resourceOnly = false
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#if inputSpecs}
<div class="w-full flex flex-col gap-4">
{#each Object.keys(inputSpecs) as inputSpecKey (inputSpecKey)}
{@const input = inputSpecs[inputSpecKey]}
{#if !(resourceOnly && (input.fieldType !== 'object' || !input.format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize
? capitalize(addWhitespaceBeforeCapitals(inputSpecKey))
: inputSpecKey}
{#if input.tooltip}
<Tooltip>
{input.tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{input.fieldType === 'array' && input.subFieldType
? `${capitalize(fieldTypeToTsType(input.subFieldType))}[]`
: capitalize(fieldTypeToTsType(input.fieldType))}
</Badge>
{#if !inputSpecs[inputSpecKey].onlyStatic && inputSpecs[inputSpecKey].type != 'eval'}
<ToggleButtonGroup
bind:selected={inputSpecs[inputSpecKey].type}
on:selected={(e) => {
if (e.detail == 'connected' && !inputSpecs[inputSpecKey]['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{#if userInputEnabled && !input.format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
</Popover>
{/if}
{#if 'fileUpload' in inputSpecs[inputSpecKey]}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="upload"
startIcon={{ icon: faUpload }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Upload</svelte:fragment>
</Popover>
{/if}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
</div>
</div>
<InputsSpecEditor
hasRows={rowColumns}
{id}
bind:componentInput={inputSpecs[inputSpecKey]}
/>
</div>
{#each Object.entries(inputSpecs) as [k, v] (k)}
{#if v.ctype == undefined}
<InputsSpecEditor
key={k}
bind:componentInput={v}
{id}
{userInputEnabled}
{shouldCapitalize}
{resourceOnly}
hasRows={rowColumns}
/>
{/if}
{/each}
</div>
@@ -1,8 +1,8 @@
<script lang="ts">
import InputValue from '../../components/helpers/InputValue.svelte'
import type { AppInput } from '../../inputType'
import type { RichConfiguration } from '../../types'
export let componentInput: AppInput | undefined
export let componentInput: RichConfiguration | undefined
let label: string = ''
</script>
@@ -52,9 +52,12 @@
initialValue={componentInput.value?.['value']?.split('$res:')[1] || ''}
on:change={(e) => {
let path = e.detail
if (componentInput && path) {
componentInput.value['value'] = `$res:${path}`
if (componentInput) {
if (path) {
componentInput.value['value'] = `$res:${path}`
} else {
componentInput.value['value'] = undefined
}
}
}}
/>
@@ -70,9 +73,12 @@
initialValue={componentInput.value?.split('$res:')[1] || ''}
on:change={(e) => {
let path = e.detail
if (componentInput && path) {
componentInput.value = `$res:${path}`
if (componentInput) {
if (path) {
componentInput.value = `$res:${path}`
} else {
componentInput.value = undefined
}
}
}}
resourceType={componentInput.format.split('-').length > 1
+14 -10
View File
@@ -98,9 +98,9 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
| ResultInput
| TemplateInput
) &
InputConfiguration<T, U, V>
InputConfiguration<T, V>
type InputConfiguration<T extends InputType, U, V extends InputType> = {
type InputConfiguration<T extends InputType, V extends InputType> = {
fieldType: T
subFieldType?: V
format?: string | undefined
@@ -140,11 +140,11 @@ export type AppInput =
| AppInputSpec<'object', Record<string | number, any>>
| AppInputSpec<'object', string>
| (AppInputSpec<'select', string> & {
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'select', string> & StaticOptions)
| AppInputSpec<'icon-select', string>
| AppInputSpec<'color', string>
@@ -157,13 +157,12 @@ export type AppInput =
| AppInputSpec<'array', string[], 'datetime'>
| AppInputSpec<'array', object[], 'object'>
| (AppInputSpec<'array', string[], 'select'> & {
optionValuesKey: keyof typeof staticValues
})
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'labeledresource', object>
export type RowAppInput = Extract<AppInput, { type: 'row' }>
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
export type ConnectedAppInput = Extract<AppInput, { type: 'connected' }>
@@ -172,4 +171,9 @@ export type ResultAppInput = Extract<AppInput, { type: 'runnable' }>
export type EvalAppInput = Extract<AppInput, { type: 'eval' }>
export type UploadAppInput = Extract<AppInput, { type: 'upload' }>
export type RichAppInput =
| AppInput
| { type: 'oneOf'; oneOf: string[]; configuration: Record<string, AppInput> }
| { type: 'group'; title: string; configuration: Record<string, AppInput> }
export type AppInputs = Record<string, AppInput>
+1 -1
View File
@@ -1,5 +1,5 @@
import type { AppInput } from './inputType'
import { get, writable, type Writable } from 'svelte/store'
import { writable, type Writable } from 'svelte/store'
import { deepEqual } from 'fast-equals'
export interface Subscriber<T> {
+11 -12
View File
@@ -38,21 +38,20 @@ export type ComponentCssProperty = {
export type ComponentCustomCSS<T extends string = string> = Record<T, ComponentCssProperty>
export type Configuration = GeneralAppInput &
(StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput | EvalAppInput | UploadAppInput)
export type RichConfiguration =
| (Configuration & { ctype?: undefined })
| { ctype: 'oneOf'; selected: string; configuration: Record<string, Configuration> }
| { ctype: 'group'; title: string; configuration: Record<string, Configuration> }
export type RichConfigurations = Record<string, RichConfiguration>
export interface BaseAppComponent extends Partial<Aligned> {
id: ComponentID
componentInput: AppInput | undefined
configuration: Record<
string,
GeneralAppInput &
(
| StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
)
>
configuration: RichConfigurations
customCss?: ComponentCustomCSS
/**
* If `true` then the wrapper will allow items to flow outside of it's borders.
@@ -1,5 +1,5 @@
import type { Schema } from '$lib/common'
import type { Flow, FlowModule, FlowStatusModule } from '$lib/gen'
import type { Flow, FlowModule } from '$lib/gen'
import type { Writable } from 'svelte/store'
import { loadFlowModuleState } from './flowStateUtils'
import { emptyFlowModuleState } from './utils'
@@ -1,5 +1,5 @@
import type { Flow, FlowModule, ForloopFlow, InputTransform } from '$lib/gen'
import { get, writable, type Writable } from 'svelte/store'
import { writable, type Writable } from 'svelte/store'
import { initFlowState, type FlowState } from './flowState'
import { numberToChars } from './utils'
@@ -1,5 +1,5 @@
import type { Schema } from '$lib/common'
import type { Flow, FlowModule, Job } from '$lib/gen'
import type { Flow, FlowModule } from '$lib/gen'
import { schemaToObject } from '$lib/utils'
import type { FlowState } from './flowState'
@@ -32,7 +32,6 @@
export let rebuildOnChange: any = undefined
export let selectedId: Writable<string | undefined> = writable<string | undefined>(undefined)
export let initialZoom: number | undefined = 1.0
export let insertable = false
export let moving: string | undefined = undefined
@@ -650,7 +649,6 @@
<Svelvet
highlightEdges={false}
locked
{initialZoom}
{nodes}
{width}
{edges}
@@ -20,10 +20,8 @@
export let canvasId: string
export let width: number
export let height: number
export let initialZoom = 3
export let initialLocation
export let boundary = false
export let minimap = false
export let scroll = false
// here we lookup the store using the unique key
const store = findStore(canvasId)
@@ -31,9 +29,6 @@
edgesStore,
nodesStore,
anchorsStore,
potentialAnchorsStore,
temporaryEdgeStore,
resizeNodesStore,
nodeSelected,
backgroundStore,
movementStore,
@@ -45,27 +40,6 @@
$: nodes = Object.values($nodesStore)
$: edges = Object.values($edgesStore)
$: anchors = Object.values($anchorsStore)
$: resizeNodes = Object.values($resizeNodesStore)
$: potentialAnchors = Object.values($potentialAnchorsStore)
$: tempEdges = $temporaryEdgeStore
/*
This block of code is responsible for reactivity of the collapsible feature
When collaspsibleStore changes, nodes/edges/resizeNodes/anchors are filtered so that
only the visible ones are displayed
*/
let filteredNodes: NodeType[]
let filteredEdges: EdgeType[]
let filteredResizeNodes: ResizeNodeType[]
let filteredAnchors: AnchorType[]
$: {
const tmp = $collapsibleStore // assignment is necessary for reactivity
const obj = filterByCollapsible(store, nodes, resizeNodes, anchors, edges)
filteredNodes = obj['filteredNodes']
filteredEdges = obj['filteredEdges']
filteredResizeNodes = obj['filteredResizeNodes']
filteredAnchors = obj['filteredAnchors']
}
// declaring the grid and dot size for d3's transformations and zoom
const gridSize = 15
@@ -188,7 +162,7 @@
<div class={`Nodes Nodes-${canvasId}`} on:contextmenu|preventDefault>
<!-- This container is transformed by d3zoom -->
<div class={`Node Node-${canvasId}`}>
{#each filteredNodes as node}
{#each nodes as node}
{#if node.data.html}
<Node {node} {canvasId} nodeId={node.id}>{@html node.data.html}</Node>
{:else if node.data.custom}
@@ -239,7 +213,7 @@
<!-- <g> tag defines which edge type to render depending on properties of edge object -->
<g>
{#each filteredEdges as edge}
{#each edges as edge}
{#if edge.type === 'smoothstep'}
<SmoothStepEdge {edge} {canvasId} />
{:else if edge.type === 'step'}
@@ -278,8 +252,6 @@
width: 24px;
}
svg {
}
.Nodes {
position: absolute;
width: 100%;
@@ -13,7 +13,6 @@
export let nodes: UserNodeType[] // TODO: update type to make possible user id being a number
export let edges: UserEdgeType[] // TODO: update type to make possible user id being a number
export let bgColor = '#ffffff' // this is used to set the background color of the the Svelvet canvas
export let minimap = false
export let width: number = 600
export let height: number = 600
export let background: boolean = true
@@ -22,14 +21,10 @@
export let snap: boolean = false
export let snapTo: number = 30
export let nodeCreate: boolean = false
export let initialZoom = 3
export let initialLocation = { x: 0, y: 0 }
export let boundary = false
export let collapsible = false
export let shareable: boolean = false // used for "importExport" feature
export let locked: boolean = false // if true, node movement is disabled
export let editable: boolean = false
export let resizable: boolean = true
export let highlightEdges: boolean = true
export let scroll: boolean = false
@@ -62,7 +57,6 @@
store.collapsibleOption.set(collapsible)
store.lockedOption.set(locked)
store.editableOption.set(editable)
store.resizableOption.set(resizable)
store.highlightEdgesOption.set(highlightEdges)
// make sure that all canvas options are compatible
@@ -97,7 +91,6 @@
store.collapsibleOption.set(collapsible)
store.lockedOption.set(locked)
store.editableOption.set(editable)
store.resizableOption.set(resizable)
store.highlightEdgesOption.set(highlightEdges)
// make sure that all canvas options are compatible
@@ -120,16 +113,7 @@
{#if error != ''}
<div class="error text-red-600 center-center p-4">{error}</div>
{:else}
<GraphView
{scroll}
{canvasId}
{width}
{height}
{initialLocation}
{initialZoom}
{boundary}
{minimap}
/>
<GraphView {scroll} {canvasId} {width} {height} {boundary} />
{/if}
</div>
@@ -1,62 +1,50 @@
// Required for "custom css" feature
import type { NodeType, StoreType } from '../../store/types/types';
import type { NodeType, StoreType } from '../../store/types/types'
// WHAT: For a given node with a user-defined classname, see if there are any css properties for height and width
// Get that height and width and set properties in node store. This is necessary for
export const forceCssHeightAndWidth = (store: StoreType, node: NodeType) => {
let width, height, innerText;
// Look through each CSS rule to find the one the user defined
for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i];
const styleRules = styleSheet.cssRules;
for (let j = 0; j < styleRules.length; j++) {
const rule = styleRules[j];
let width, height, innerText
// Look through each CSS rule to find the one the user defined
for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i]
const styleRules = styleSheet.cssRules
for (let j = 0; j < styleRules.length; j++) {
const rule = styleRules[j]
// this is necessary to get rid of typescript warning for rule.selectorText
if (!(rule instanceof CSSStyleRule)) continue;
// this is necessary to get rid of typescript warning for rule.selectorText
if (!(rule instanceof CSSStyleRule)) continue
if (rule.selectorText === `.${node.className}`) {
const initialText = rule.cssText; // getting the full text of the CSS rule
const i = initialText.indexOf('{'); // finding index of first bracket
innerText = initialText.substring(i + 1, initialText.length - 1); // extracting the CSS to insert into inline style
// Adjusting the width and height if they are set via the custom class
const arr = innerText.split(' ');
arr.forEach((str, i) => {
if (str === 'width:') {
width = str.concat(arr[i + 1]); // go through the array and join width and the number
const w = parseInt(arr[i + 1]); // getting the number for the width
width = w;
}
if (str === 'height:') {
height = str.concat(arr[i + 1]); // same as with the width
const h = parseInt(arr[i + 1]);
height = h;
}
});
}
}
}
if (rule.selectorText === `.${node.className}`) {
const initialText = rule.cssText // getting the full text of the CSS rule
const i = initialText.indexOf('{') // finding index of first bracket
innerText = initialText.substring(i + 1, initialText.length - 1) // extracting the CSS to insert into inline style
// Adjusting the width and height if they are set via the custom class
const arr = innerText.split(' ')
arr.forEach((str, i) => {
if (str === 'width:') {
width = str.concat(arr[i + 1]) // go through the array and join width and the number
const w = parseInt(arr[i + 1]) // getting the number for the width
width = w
}
if (str === 'height:') {
height = str.concat(arr[i + 1]) // same as with the width
const h = parseInt(arr[i + 1])
height = h
}
})
}
}
}
// update the width/height of the node
store.nodesStore.update((nodes) => {
if (width !== undefined) nodes[node.id].width = width;
if (height !== undefined) nodes[node.id].height = height;
return { ...nodes };
});
// update the width/height of the node
store.nodesStore.update((nodes) => {
if (width !== undefined) nodes[node.id].width = width
if (height !== undefined) nodes[node.id].height = height
return { ...nodes }
})
// move the node a bit to force update to anchors, potential anchors
node.setPositionFromMovement(0, 0);
// update the position of the resize node. TODO: this should be refactored so that it cascades from movement
store.resizeNodesStore.update((resizeNodes) => {
for (let resizeNode of Object.values(resizeNodes)) {
if (resizeNode.nodeId === node.id) {
// set resizeNode position to bottom left corner of Node
resizeNode.positionX = node.positionX + node.width;
resizeNode.positionY = node.positionY + node.height;
}
}
return { ...resizeNodes };
});
};
// move the node a bit to force update to anchors, potential anchors
node.setPositionFromMovement(0, 0)
}
@@ -1,7 +1,6 @@
import type { StoreType } from '../../store/types/types';
import { writable, derived, get, readable } from 'svelte/store';
import { get } from 'svelte/store';
import type { AnchorType } from '../types/types';
import { getNodeById } from '../../nodes/controllers/util';
/**
* Finds all Anchors that matches the conditions specified in the filter parameter from a Svelvet store and returns these Anchors in an array.
@@ -1,5 +1,5 @@
import type { StoreType } from '../../store/types/types';
import { writable, derived, get, readable } from 'svelte/store';
import { get } from 'svelte/store';
/**
* getNodeById will look for the targeted Node that has the same id provided in the Svelvet component store.
@@ -1,9 +1,8 @@
/** this is where we create our node store */
import type { NodeType } from '../../store/types/types';
import { writable, derived, get, readable } from 'svelte/store';
import { stores } from '../../store/models/store';
import { getAnchors, getEdgeById } from '../../edges/controllers/util';
import { getResizeNodes } from '../../resizableNodes/controllers/util';
import type { NodeType } from '../../store/types/types'
import { get } from 'svelte/store'
import { stores } from '../../store/models/store'
import { getAnchors, getEdgeById } from '../../edges/controllers/util'
/** A Node class that implements NodeType interface
* @param {string} id The id of the Node
* @param {number} positionX The X-axis position of the Node (left top corner of the Node)
@@ -22,187 +21,137 @@ import { getResizeNodes } from '../../resizableNodes/controllers/util';
* @param {string} className The custom class name if user specifies. This is for the custom className feature for Node.
*/
export class Node implements NodeType {
constructor(
public id: string,
public positionX: number,
public positionY: number,
public width: number,
public height: number,
public bgColor: string,
public data: object,
public canvasId: string,
public borderColor: string,
public image: boolean,
public src: string,
public textColor: string,
public borderRadius: number,
public childNodes: string[],
public className: string,
public clickCallback: Function
) { }
constructor(
public id: string,
public positionX: number,
public positionY: number,
public width: number,
public height: number,
public bgColor: string,
public data: object,
public canvasId: string,
public borderColor: string,
public image: boolean,
public src: string,
public textColor: string,
public borderRadius: number,
public childNodes: string[],
public className: string,
public clickCallback: Function
) {}
/**
* setPositionFromMovement will update the positionX and positionY of the Node when user drags a Node around on the canvas, reflect the changes in real time in the nodesStore, and also cascade the changes to all relative elements like Anchors and Edges.
* @param {number} movementX The mouse movement value on the X-axis
* @param {number} movementY The mouse movement value on the Y-axis
*/
setPositionFromMovement(movementX: number, movementY: number) {
const {
nodesStore,
anchorsStore,
resizeNodesStore,
potentialAnchorsStore,
boundary,
lockedOption,
} = stores[this.canvasId];
/**
* setPositionFromMovement will update the positionX and positionY of the Node when user drags a Node around on the canvas, reflect the changes in real time in the nodesStore, and also cascade the changes to all relative elements like Anchors and Edges.
* @param {number} movementX The mouse movement value on the X-axis
* @param {number} movementY The mouse movement value on the Y-axis
*/
setPositionFromMovement(movementX: number, movementY: number) {
const { nodesStore, anchorsStore, potentialAnchorsStore, boundary, lockedOption } =
stores[this.canvasId]
if (get(lockedOption)) return; // don't do anything if locked is enabled
if (get(lockedOption)) return // don't do anything if locked is enabled
// boundary sets the boundary of the canvas, or else it is false if their is no boundary
// check if out of bounds, and if so return without doing anything
const boundaryObj = get(boundary);
if (
typeof boundaryObj === 'object' &&
(this.positionX + this.width + movementX >= boundaryObj.x ||
this.positionY + this.height + movementY >= boundaryObj.y ||
this.positionY + movementY <= 0 ||
this.positionX + movementX <= 0)
)
return;
//update all necessary data
this.positionX += movementX;
this.positionY += movementY;
// boundary sets the boundary of the canvas, or else it is false if their is no boundary
// check if out of bounds, and if so return without doing anything
const boundaryObj = get(boundary)
if (
typeof boundaryObj === 'object' &&
(this.positionX + this.width + movementX >= boundaryObj.x ||
this.positionY + this.height + movementY >= boundaryObj.y ||
this.positionY + movementY <= 0 ||
this.positionX + movementX <= 0)
)
return
//update all necessary data
this.positionX += movementX
this.positionY += movementY
// update children
nodesStore.update((nodes) => {
if (this.childNodes)
for (const childNodeId of this.childNodes)
nodes[childNodeId].setPositionFromMovement(movementX, movementY);
return { ...nodes };
});
// update children
nodesStore.update((nodes) => {
if (this.childNodes)
for (const childNodeId of this.childNodes)
nodes[childNodeId].setPositionFromMovement(movementX, movementY)
return { ...nodes }
})
//update all the anchors on the node in the anchorsStore
anchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].setPositionFromNode();
}
}
return { ...anchors };
});
//update all the anchors on the node in the anchorsStore
anchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].setPositionFromNode()
}
}
return { ...anchors }
})
//update all the anchors on the node in the anchorsStore
potentialAnchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].callback(); // we don't have to worry about setting partner anchors/etc;
}
}
return { ...anchors };
});
//update all the anchors on the node in the anchorsStore
potentialAnchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].callback() // we don't have to worry about setting partner anchors/etc;
}
}
return { ...anchors }
})
}
resizeNodesStore.update((resAnchors) => {
for (const anchorId in resAnchors) {
if (resAnchors[anchorId].nodeId === this.id) {
resAnchors[anchorId].setPosition(movementX, movementY);
//resAnchors[anchorId].setPosition(movementX, movementY);
}
}
return { ...resAnchors };
});
}
/**
* setSizeFromMovement will update the width and height of the Node when user resizes the Node by dragging at the right bottom corner (where the ResizedNode attached), reflect the changes in real time in the nodesStore, and also cascade the changes to all relative elements like Anchors and potential Anchors.
*
* @param movementX The mouse movement value on the X-axis
* @param movementY The mouse movement value on the Y-axis
*/
setSizeFromMovement(movementX: number, movementY: number) {
this.width += movementX
this.height += movementY
/**
* setSizeFromMovement will update the width and height of the Node when user resizes the Node by dragging at the right bottom corner (where the ResizedNode attached), reflect the changes in real time in the nodesStore, and also cascade the changes to all relative elements like Anchors and potential Anchors.
*
* @param movementX The mouse movement value on the X-axis
* @param movementY The mouse movement value on the Y-axis
*/
setSizeFromMovement(movementX: number, movementY: number) {
this.width += movementX;
this.height += movementY;
const { anchorsStore, potentialAnchorsStore } = stores[this.canvasId]
const { anchorsStore, potentialAnchorsStore } = stores[this.canvasId];
//Updates the anchor so it follows the node's position as the dimensions change
anchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].setPositionFromNode()
//anchors[anchorId].setPosition(movementX, movementY);
}
}
return { ...anchors }
})
//Updates the anchor so it follows the node's position as the dimensions change
anchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].setPositionFromNode();
//anchors[anchorId].setPosition(movementX, movementY);
}
}
return { ...anchors };
});
//update all the anchors on the node in the anchorsStore
potentialAnchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].callback() // we don't have to worry about setting partner anchors/etc;
}
}
return { ...anchors }
})
}
//update all the anchors on the node in the anchorsStore
potentialAnchorsStore.update((anchors) => {
for (const anchorId in anchors) {
if (anchors[anchorId].nodeId === this.id) {
anchors[anchorId].callback(); // we don't have to worry about setting partner anchors/etc;
}
}
return { ...anchors };
});
}
/**
* setExportableData is going to construct an object that holds all the node data that can be exported. This method is used for Exporting Diagrams feature.
*
* @returns An object with all the exportable data of the Node. The format of this object should align with the original format of node data user provided.
*/
setExportableData() {
const exportableData = {
id: this.id,
// canvasId: this.canvasId,
width: this.width,
height: this.height,
position: { x: this.positionX, y: this.positionY },
data: this.data,
bgColor: this.bgColor,
borderColor: this.borderColor,
textColor: this.textColor,
borderRadius: this.borderRadius,
image: this.image,
src: this.src,
childNodes: this.childNodes,
customClassName: this.className
}
/**
* delete will handle the deletion of a Node (also waterfall down to delete anchors and edges)
*/
delete() {
const store = stores[this.canvasId];
const { nodesStore, anchorsStore, edgesStore } = stores[this.canvasId];
nodesStore.update((nodes) => {
for (const nodeId in nodes) {
if (nodes[nodeId].id === this.id) {
delete nodes[nodeId];
}
}
return { ...nodes };
});
// variable `anchors` is an array of Anchor objects on the node
const anchors = getAnchors(store, { nodeId: this.id });
for (let anchorSelf of anchors) {
const edgeId = anchorSelf.edgeId;
const edge = getEdgeById(store, edgeId);
edge.delete(); // this also deletes anchors. TODO: maybe this should be renamed to explicitly say
}
// delete the resize nodes
const resizeNodesArr = getResizeNodes(store, { nodeId: this.id });
// there should be only 1 resize node if option is enabled, 0 if not enabled
for (const resizeNode of resizeNodesArr) {
resizeNode.delete();
}
}
/**
* setExportableData is going to construct an object that holds all the node data that can be exported. This method is used for Exporting Diagrams feature.
*
* @returns An object with all the exportable data of the Node. The format of this object should align with the original format of node data user provided.
*/
setExportableData() {
const exportableData = {
id: this.id,
// canvasId: this.canvasId,
width: this.width,
height: this.height,
position: { x: this.positionX, y: this.positionY },
data: this.data,
bgColor: this.bgColor,
borderColor: this.borderColor,
textColor: this.textColor,
borderRadius: this.borderRadius,
image: this.image,
src: this.src,
childNodes: this.childNodes,
customClassName: this.className,
};
return exportableData;
}
return exportableData
}
}
@@ -1,146 +0,0 @@
<script>
import { findStore } from '../../store/controllers/storeApi';
import { getNodeById } from '../../nodes/controllers/util';
export let nodeId;
export let canvasId;
export let isEditing;
let label;
let width;
let height;
let customClass;
let backgroundColor;
const store = findStore(canvasId);
const { nodesStore, nodeSelected } = store;
let currentNode = $nodesStore[nodeId];
const editNode = (e) => {
e.preventDefault();
if (label) currentNode.data.label = label;
if (width) currentNode.width = width;
if (height) currentNode.height = height;
if (backgroundColor) currentNode.bgColor = backgroundColor;
width = '';
height = '';
customClass = '';
label = '';
store.nodesStore.set($nodesStore);
};
</script>
{#if isEditing}
<div
on:mouseover={(e) => ($nodeSelected = true)}
on:focus
on:wheel|preventDefault
class="EditNode"
style="left:{currentNode.positionX +
currentNode.width}px; top:{currentNode.positionY}px"
>
<form on:submit={editNode}>
<label for="label-input">Label</label>
<input
type="text"
id="label-input-{nodeId}"
placeholder={currentNode.data.label}
bind:value={label}
/>
<label for="bg-color-input">Background Color</label>
<input
type="color"
id="bg-color-input-{nodeId}"
class="bgci"
bind:value={backgroundColor}
/>
<input
type="text"
placeholder={currentNode.bgColor}
bind:value={backgroundColor}
/>
</form>
<div class="btn-container">
<button
on:click={(e) => {
const store = findStore(canvasId);
const node = getNodeById(store, nodeId);
node.delete();
isEditing = false;
}}>Delete Node</button
>
<button
on:click={(e) => {
editNode(e);
isEditing = false;
}}>Submit</button
>
</div>
</div>
{/if}
<style>
.EditNode {
position: absolute;
padding: 0 1rem 1rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #333333;
border-radius: 0.25rem;
background-color: #ffffff;
z-index: 10;
user-select: text;
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.4);
color: #333333;
pointer-events: auto; /* this is needed for pointer events to work since we disable them in graphview */
}
label {
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 0.15rem;
}
.btn-container {
display: flex;
justify-content: space-between;
margin-top: 0.5rem;
}
button {
border-radius: 0.25rem;
background-color: white;
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.4);
margin: 0.2rem;
}
input {
height: 1.6rem;
border-color: #e45b56;
}
.bgci {
height: 2rem;
width: 5rem;
padding: 0;
border: none;
background-color: none;
}
button:hover {
cursor: pointer;
background-color: #e45b56;
color: white;
}
form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
@@ -1,28 +0,0 @@
import type { StoreType, ResizeNodeType } from '../../store/types/types';
import { get } from 'svelte/store';
/**
* Finds all resizeNodes that matches the conditions specified in the filter parameter from a Svelvet store and returns these resizeNodes in an array
*
* @param store The Svelvet store containing the state of a Svelvet component
* @param filter An object to specify conditions.
* @returns An array of resizeNode objects that matches the conditions specified in filter parameter
*/
export function getResizeNodes(
store: StoreType,
filter?: { [key: string]: any }
) {
let resizeNodes = Object.values(get(store.resizeNodesStore));
// filter the array for elements that match filter
if (filter !== undefined) {
resizeNodes = resizeNodes.filter((resizeNode) => {
for (let filterKey in filter) {
const filterValue = filter[filterKey];
if (resizeNode[filterKey as keyof ResizeNodeType] !== filterValue)
return false;
}
return true;
});
}
// return list of anchors
return resizeNodes;
}
@@ -1,73 +0,0 @@
import type { ResizeNodeType } from '../../store/types/types';
// import { writable, derived, get, readable } from 'svelte/store';
import { stores } from '../../store/models/store';
/** A ResizeNode class that implements ResizeNodeType interface
* @param {string} id The id of the ResizeNode
* @param {string} canvasId The canvasId of the Svelvet component that the instantiated ResizeNode will be on.
* @param {string} nodeId The id of the Node that the instantiated ResizedNode will be attached to.
* @param {number} positionX The X-axis position of the ResizeNode (left top corner of the ResizeNode)
* @param {number} positionY The Y-axis position of the ResizeNode (left top corner of the ResizeNode)
*/
export class ResizeNode implements ResizeNodeType {
constructor(
public id: string,
public canvasId: string,
public nodeId: string,
public positionX: number,
public positionY: number
) {}
/**
* setPosition will update the positionX and positionY of the ResizeNode. This will be invoked when the Node that this ResizeNode attached to is moving so that the ResizeNode will follow the Node.
*
* @param movementX The mouse movement value on the X-axis
* @param movementY The mouse movement value on the Y-axis
*/
setPosition(movementX: number, movementY: number) {
this.positionX += movementX;
this.positionY += movementY;
}
/**
* setPositionAndCascade will update the positionX and positionY of the ResizeNode and also cascade changes to related Node. This will be invoked when the user drags the ResizeNode so the position of the ResizeNode and the width and height of the Node would change accordingly.
* @param movementX The mouse movement value on the X-axis
* @param movementY The mouse movement value on the Y-axis
*/
setPositionAndCascade(movementX: number, movementY: number) {
//declare variables needed to interact with the corresponding node to this resize anchor
const nodeId = this.nodeId;
const { nodesStore } = stores[this.canvasId];
//Updates the width/height of the corresponding Node
nodesStore.update((nodes) => {
const node = nodes[nodeId];
//sets condition so node cannot be less than 20 px in width or height.
if (node.width + movementX > 20 && node.height + movementY > 20) {
//Updates position to this resizeNode anchor. Must be done within the update method so ensure the position doesn't change if the width goes below 20px
this.positionX += movementX;
this.positionY += movementY;
//setSizeFromMovement will then update the anchors position as the width & height changes
node.setSizeFromMovement(movementX, movementY);
}
return { ...nodes };
});
}
/**
* delete is going to delete the ResizeNode from the resizeNodesStore.
*/
delete() {
const store = stores[this.canvasId];
const { resizeNodesStore } = stores[this.canvasId];
resizeNodesStore.update((resizeNodes) => {
for (const resizeNodeId in resizeNodes) {
if (resizeNodes[resizeNodeId].id === this.id) {
delete resizeNodes[resizeNodeId];
}
}
return { ...resizeNodes };
});
}
}
@@ -1,85 +0,0 @@
<script lang="ts">
import { findStore } from '../../store/controllers/storeApi';
import type { ResizeNodeType } from '../../store/types/types';
import { writable, derived, get, readable } from 'svelte/store';
export let resizeId: string;
export let canvasId: string;
const store = findStore(canvasId);
const { resizeNodesStore, nodeSelected } = store;
let isSelected = false;
let reactResizeNode: ResizeNodeType;
$: reactResizeNode = $resizeNodesStore[resizeId];
</script>
<svelte:window
on:mousemove={(e) => {
e.preventDefault();
if (isSelected) {
resizeNodesStore.update((resNode) => {
const newResNode = resNode[resizeId];
const d3Scale = get(store.d3Scale);
newResNode.setPositionAndCascade(
e.movementX / d3Scale,
e.movementY / d3Scale,
resizeId
);
return { ...resNode };
});
}
}}
on:mouseup={(e) => {
e.preventDefault();
// don't need to set $nodeSelected = false because Node component will do it for you
isSelected = false;
}}
/>
<div
on:mousedown={(e) => {
e.preventDefault();
$nodeSelected = true;
isSelected = true;
}}
on:mouseover={(e) => ($nodeSelected = true)}
on:focus
on:mouseleave={(e) => ($nodeSelected = false)}
on:mouseenter={(e) => ($nodeSelected = true)}
on:wheel|preventDefault
class="ResizeNode"
style="
left: {reactResizeNode.positionX - 10}px;
top: {reactResizeNode.positionY - 10}px;
width: {20}px;
height: {20}px;
background-color: purple;
border-color: purple;
border-radius: 50%;"
id="svelvet-{resizeId}"
/>
<style>
.ResizeNode {
position: absolute;
display: grid;
user-select: none;
cursor: move;
justify-content: center;
overscroll-behavior: auto;
align-items: center;
font-size: 14px;
text-align: center;
border: solid 1px black;
border-radius: 5px;
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.2);
opacity: 0;
pointer-events: auto; /* this is needed for pointer events to work since we disable them in graphview */
}
.ResizeNode:hover {
cursor: nwse-resize;
opacity: 0.5;
}
</style>
@@ -30,18 +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 { writable, get } from 'svelte/store';
import { stores } from '../models/store'
import { writable, get } from 'svelte/store'
import type { StoreType } from '../types/types';
import type { UserNodeType, UserEdgeType } from '../../types/types';
import {
populateAnchorsStore,
populateNodesStore,
populateEdgesStore,
populateResizeNodeStore,
} from './util';
import { populateCollapsibleStore } from '../../collapsible/controllers/util';
import type { StoreType } from '../types/types'
import type { UserNodeType, UserEdgeType } from '../../types/types'
import { populateAnchorsStore, populateNodesStore, populateEdgesStore } from './util'
import { populateCollapsibleStore } from '../../collapsible/controllers/util'
/**
* findStore is going to return the target Svelvet store with the canvasId provided as argument.
@@ -50,7 +45,7 @@ import { populateCollapsibleStore } from '../../collapsible/controllers/util';
* @returns The store of a Svelvet component that matches the canvasId
*/
export function findStore(canvasId: string): StoreType {
return stores[canvasId];
return stores[canvasId]
}
/**
@@ -68,33 +63,31 @@ export function findStore(canvasId: string): StoreType {
* @returns An empty store for the newly created Svelvet component.
*/
export function createStoreEmpty(canvasId: string): StoreType {
stores[canvasId] = {
nodesStore: writable({}),
edgesStore: writable({}),
anchorsStore: writable({}),
resizeNodesStore: writable({}),
potentialAnchorsStore: writable({}),
widthStore: writable(600),
heightStore: writable(600),
backgroundStore: writable(false),
movementStore: writable(true),
nodeSelected: writable(false),
nodeIdSelected: writable(-1),
d3Scale: writable(1),
options: writable({}),
temporaryEdgeStore: writable([]),
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
resizableOption: writable(true), // option to turn on/off resizable nodes.
highlightEdgesOption: writable(true), // option to turn on/off highlightable edges
};
return stores[canvasId];
stores[canvasId] = {
nodesStore: writable({}),
edgesStore: writable({}),
anchorsStore: writable({}),
potentialAnchorsStore: writable({}),
widthStore: writable(600),
heightStore: writable(600),
backgroundStore: writable(false),
movementStore: writable(true),
nodeSelected: writable(false),
nodeIdSelected: writable(-1),
d3Scale: writable(1),
options: writable({}),
temporaryEdgeStore: writable([]),
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
highlightEdgesOption: writable(true) // option to turn on/off highlightable edges
}
return stores[canvasId]
}
/**
@@ -105,24 +98,20 @@ export function createStoreEmpty(canvasId: string): StoreType {
* @param edges Same as nodes, this is an array of objects containing edge info THAT IS DIFFERENT FROM THE EDGE CLASS.
*/
export function populateSvelvetStoreFromUserInput(
canvasId: string,
nodes: UserNodeType[],
edges: UserEdgeType[]
canvasId: string,
nodes: UserNodeType[],
edges: UserEdgeType[]
): void {
// find the store
const store = findStore(canvasId);
// find the store
const store = findStore(canvasId)
// populate store.nodesStore with user nodes
populateNodesStore(store, nodes, canvasId);
// populate store.anchorsStore with anchors. Note the userdoes not explictly define anchors; anchors are calculated from the edges
populateAnchorsStore(store, nodes, edges, canvasId);
// populate edges
populateEdgesStore(store, edges, canvasId);
//populate resize Store
if (get(store.resizableOption))
populateResizeNodeStore(store, nodes, canvasId);
// populate store.nodesStore with user nodes
populateNodesStore(store, nodes, canvasId)
// populate store.anchorsStore with anchors. Note the userdoes not explictly define anchors; anchors are calculated from the edges
populateAnchorsStore(store, nodes, edges, canvasId)
// populate edges
populateEdgesStore(store, edges, canvasId)
// populatate collapsible objects if "collapsible" feature is turned on
if (get(store.collapsibleOption))
populateCollapsibleStore(store, nodes, edges, canvasId);
// populatate collapsible objects if "collapsible" feature is turned on
if (get(store.collapsibleOption)) populateCollapsibleStore(store, nodes, edges, canvasId)
}
@@ -1,51 +1,15 @@
const pkStringGenerator = () => (Math.random() + 1).toString(36).substring(7);
import {
rightCb,
leftCb,
topCb,
bottomCb,
} from '../../edges/controllers/anchorCbUser'; // these are callbacks used to calculate anchor position relative to node
import {
dynamicCbCreator,
fixedCbCreator,
} from '../../edges/controllers/anchorCbDev';
const pkStringGenerator = () => (Math.random() + 1).toString(36).substring(7) // these are callbacks used to calculate anchor position relative to node
import { dynamicCbCreator, fixedCbCreator } from '../../edges/controllers/anchorCbDev'
import type { AnchorType, AnchorCbType } from '../../edges/types/types';
import type { AnchorType, AnchorCbType } from '../../edges/types/types'
import type {
NodeType,
EdgeType,
StoreType,
ResizeNodeType,
PotentialAnchorType,
} from '../types/types';
import type { UserNodeType, UserEdgeType } from '../../types/types';
import type { NodeType, EdgeType, StoreType, ResizeNodeType } from '../types/types'
import type { UserNodeType, UserEdgeType } from '../../types/types'
import { ResizeNode } from '../../resizableNodes/models/ResizeNode';
import { Anchor } from '../../edges/models/Anchor';
import { Node } from '../../nodes/models/Node';
import { Edge } from '../../edges/models/Edge';
import { writable, derived, get, readable } from 'svelte/store';
import { getEdgeById, getAnchors } from '../../edges/controllers/util';
/**
* Creates resize node on the bottom right corner of the targeted Node
* @param canvasId The canvasId of the Svelvet component that holds the targeted Node
* @param nodeId The id of the Node that the resize node attached to
* @param posX The number of pixels on the x-axis relative to the left top corner of the targeted Node
* @param posY The number of pixels on the y-axis relative to the left top corner of the targeted Node
* @returns A ReziseNode object with randomized id, canvasId, nodeId, posX, and posY
*/
function createResizeNode(
canvasId: string,
nodeId: string,
posX: number,
posY: number
) {
const id = pkStringGenerator();
const resizeNode = new ResizeNode(id, canvasId, nodeId, posX, posY);
return resizeNode;
}
import { Anchor } from '../../edges/models/Anchor'
import { Node } from '../../nodes/models/Node'
import { Edge } from '../../edges/models/Edge'
import { getAnchors } from '../../edges/controllers/util'
/**
* Creates an Anchor on the targeted Node with infomation the userNode holds
@@ -58,45 +22,44 @@ function createResizeNode(
*/
function createAnchor(
store: StoreType,
userNode: UserNodeType | null,
sourceOrTarget: 'source' | 'target',
canvasId: string,
edge: UserEdgeType
store: StoreType,
userNode: UserNodeType | null,
sourceOrTarget: 'source' | 'target',
canvasId: string,
edge: UserEdgeType
) {
// edge case
if (userNode === null)
throw `you cannot create an anchor without a user node (for now)`;
// edge case
if (userNode === null) throw `you cannot create an anchor without a user node (for now)`
const edgeId = edge.id;
const anchorId = pkStringGenerator();
const edgeId = edge.id
const anchorId = pkStringGenerator()
// userCb is the appropriate source or taret callback from userEdge object. It is
// possible the user to NOT set userCb in which case userCb will be undefined
let userCb: Function | undefined;
if (sourceOrTarget === 'target') userCb = edge.targetAnchorCb;
else userCb = edge.sourceAnchorCb;
// userCb is the appropriate source or taret callback from userEdge object. It is
// possible the user to NOT set userCb in which case userCb will be undefined
let userCb: Function | undefined
if (sourceOrTarget === 'target') userCb = edge.targetAnchorCb
else userCb = edge.sourceAnchorCb
// create anchor callbacks
// create anchor callbacks
let cb: AnchorCbType;
if (userCb === undefined) cb = dynamicCbCreator(store, edgeId, anchorId);
else cb = fixedCbCreator(store, edgeId, anchorId, userNode.id, userCb);
let cb: AnchorCbType
if (userCb === undefined) cb = dynamicCbCreator(store, edgeId, anchorId)
else cb = fixedCbCreator(store, edgeId, anchorId, userNode.id, userCb)
// Create a new anchor.
const anchor = new Anchor(
anchorId,
userNode.id,
edgeId,
sourceOrTarget,
-1, // dummy variables for x,y,angle for now
-1, // dummy variables for x,y,angle for now
cb,
canvasId,
0 // dummy variables for x,y,angle for now
);
// return
return anchor;
// Create a new anchor.
const anchor = new Anchor(
anchorId,
userNode.id,
edgeId,
sourceOrTarget,
-1, // dummy variables for x,y,angle for now
-1, // dummy variables for x,y,angle for now
cb,
canvasId,
0 // dummy variables for x,y,angle for now
)
// return
return anchor
}
/**
@@ -105,71 +68,67 @@ function createAnchor(
* @param edges An edge that the user specifies. This is NOT the same as a Edge object.
* @param canvasId The canvasId of the Svelvet component that holds the Edges
*/
export function populateEdgesStore(
store: StoreType,
edges: UserEdgeType[],
canvasId: string
) {
const edgesStore: { [key: string]: EdgeType } = {};
for (let i = 0; i < edges.length; i++) {
const userEdge = edges[i];
// { id: 'e1-2', source: 1, type: 'straight', target: 2, label: 'e1-2' },
// source is node.id for the source node
// target is node.id for the target node
// We need to get the anchors
const {
source: sourceNodeId,
target: targetNodeId,
id: edgeId,
type,
label,
labelBgColor,
labelTextColor,
edgeColor,
animate,
noHandle,
arrow,
clickCallback,
className,
} = userEdge;
export function populateEdgesStore(store: StoreType, edges: UserEdgeType[], canvasId: string) {
const edgesStore: { [key: string]: EdgeType } = {}
for (let i = 0; i < edges.length; i++) {
const userEdge = edges[i]
// { id: 'e1-2', source: 1, type: 'straight', target: 2, label: 'e1-2' },
// source is node.id for the source node
// target is node.id for the target node
// We need to get the anchors
const {
source: sourceNodeId,
target: targetNodeId,
id: edgeId,
type,
label,
labelBgColor,
labelTextColor,
edgeColor,
animate,
noHandle,
arrow,
clickCallback,
className
} = userEdge
const anchors = getAnchors(store, { edgeId: edgeId });
// check that we have two anchors for every edge
if (anchors.length !== 2) throw 'We should have two anchors for every node';
// check that we have 1 source anchor and 1 target anchor. Since sourceOrTarget is typed to be either 'source'
// or 'target', it suffices to check whether there are two unique elements
if (new Set(anchors.map((e) => e.sourceOrTarget)).size !== 2)
throw 'we should have one source and one target anchor';
// get source and target anchor
let sourceAnchor, targetAnchor;
if (anchors[0].sourceOrTarget === 'source') {
sourceAnchor = anchors[0];
targetAnchor = anchors[1];
} else {
sourceAnchor = anchors[1];
targetAnchor = anchors[0];
}
const anchors = getAnchors(store, { edgeId: edgeId })
// check that we have two anchors for every edge
if (anchors.length !== 2) throw 'We should have two anchors for every node'
// check that we have 1 source anchor and 1 target anchor. Since sourceOrTarget is typed to be either 'source'
// or 'target', it suffices to check whether there are two unique elements
if (new Set(anchors.map((e) => e.sourceOrTarget)).size !== 2)
throw 'we should have one source and one target anchor'
// get source and target anchor
let sourceAnchor, targetAnchor
if (anchors[0].sourceOrTarget === 'source') {
sourceAnchor = anchors[0]
targetAnchor = anchors[1]
} else {
sourceAnchor = anchors[1]
targetAnchor = anchors[0]
}
edgesStore[edgeId] = new Edge(
edgeId,
sourceAnchor.positionX,
sourceAnchor.positionY,
targetAnchor.positionX,
targetAnchor.positionY,
canvasId,
userEdge.label === undefined ? '' : userEdge.label,
userEdge.type === undefined ? 'bezier' : userEdge.type,
userEdge.labelBgColor === undefined ? 'white' : userEdge.labelBgColor,
userEdge.labelTextColor === undefined ? 'black' : userEdge.labelTextColor,
userEdge.edgeColor === undefined ? 'black' : userEdge.edgeColor,
userEdge.animate === undefined ? false : userEdge.animate,
userEdge.noHandle === undefined ? false : userEdge.noHandle,
userEdge.arrow === undefined ? false : userEdge.arrow,
userEdge.clickCallback === undefined ? () => { } : userEdge.clickCallback,
userEdge.className === undefined ? '' : userEdge.className
);
}
store.edgesStore.set(edgesStore);
edgesStore[edgeId] = new Edge(
edgeId,
sourceAnchor.positionX,
sourceAnchor.positionY,
targetAnchor.positionX,
targetAnchor.positionY,
canvasId,
userEdge.label === undefined ? '' : userEdge.label,
userEdge.type === undefined ? 'bezier' : userEdge.type,
userEdge.labelBgColor === undefined ? 'white' : userEdge.labelBgColor,
userEdge.labelTextColor === undefined ? 'black' : userEdge.labelTextColor,
userEdge.edgeColor === undefined ? 'black' : userEdge.edgeColor,
userEdge.animate === undefined ? false : userEdge.animate,
userEdge.noHandle === undefined ? false : userEdge.noHandle,
userEdge.arrow === undefined ? false : userEdge.arrow,
userEdge.clickCallback === undefined ? () => {} : userEdge.clickCallback,
userEdge.className === undefined ? '' : userEdge.className
)
}
store.edgesStore.set(edgesStore)
}
/**
@@ -178,18 +137,14 @@ export function populateEdgesStore(
* @param userNodes The array of userNodes (NOT the same as Node object)
* @returns The node that user specified or null if not found
*/
function findUserNodeById(
id: string,
userNodes: UserNodeType[]
): UserNodeType | null {
for (let i = 0; i < userNodes.length; i++) {
const userNode = userNodes[i];
if (userNode.id === id) return userNode;
}
return null;
function findUserNodeById(id: string, userNodes: UserNodeType[]): UserNodeType | null {
for (let i = 0; i < userNodes.length; i++) {
const userNode = userNodes[i]
if (userNode.id === id) return userNode
}
return null
}
/**
* Populates the anchorsStore. This will overwrite any data in the AnchorsStore.
* @param store The Svelvet store containing the state of the Svelvet component
@@ -198,50 +153,38 @@ function findUserNodeById(
* @param canvasId The canvasId of the Svelvet component that holds the nodes and edges
*/
export function populateAnchorsStore(
store: StoreType,
nodes: UserNodeType[],
edges: UserEdgeType[],
canvasId: string
store: StoreType,
nodes: UserNodeType[],
edges: UserEdgeType[],
canvasId: string
) {
// anchorsStore will populated and eventaully synchronized to store.anchorsStore
const anchorsStore: { [key: string]: AnchorType } = {};
// iterate through user edges. Note the user never explicitly defines anchors; we calculate anchors
// from the user edge/node information
for (let i = 0; i < edges.length; i++) {
const userEdge = edges[i];
// find the source and target userNodes. These will be used to create the nodeId foreign key and
// determine placement of the anchor based on userNode.targetPosition, useNode.sourcePosition
const { source: sourceNodeId, target: targetNodeId } = userEdge;
const sourceUserNode = findUserNodeById(sourceNodeId, nodes);
const targetUserNode = findUserNodeById(targetNodeId, nodes);
// create source anchor
const sourceAnchor = createAnchor(
store,
sourceUserNode,
'source',
canvasId,
userEdge
);
// create target anchor
const targetAnchor = createAnchor(
store,
targetUserNode,
'target',
canvasId,
userEdge
);
// store source and target anchors
anchorsStore[sourceAnchor.id] = sourceAnchor;
anchorsStore[targetAnchor.id] = targetAnchor;
}
// anchorsStore will populated and eventaully synchronized to store.anchorsStore
const anchorsStore: { [key: string]: AnchorType } = {}
// iterate through user edges. Note the user never explicitly defines anchors; we calculate anchors
// from the user edge/node information
for (let i = 0; i < edges.length; i++) {
const userEdge = edges[i]
// find the source and target userNodes. These will be used to create the nodeId foreign key and
// determine placement of the anchor based on userNode.targetPosition, useNode.sourcePosition
const { source: sourceNodeId, target: targetNodeId } = userEdge
const sourceUserNode = findUserNodeById(sourceNodeId, nodes)
const targetUserNode = findUserNodeById(targetNodeId, nodes)
// create source anchor
const sourceAnchor = createAnchor(store, sourceUserNode, 'source', canvasId, userEdge)
// create target anchor
const targetAnchor = createAnchor(store, targetUserNode, 'target', canvasId, userEdge)
// store source and target anchors
anchorsStore[sourceAnchor.id] = sourceAnchor
anchorsStore[targetAnchor.id] = targetAnchor
}
//populates the anchorsStore
store.anchorsStore.set(anchorsStore);
//populates the anchorsStore
store.anchorsStore.set(anchorsStore)
// set anchor positions. We can only set anchor positions after anchorsStore and nodesStore
// has been populated. TODO: maybe add a check to see that anchorsStore and NodesStore populated?
const anchors = getAnchors(store);
for (const anchor of anchors) anchor.callback();
// set anchor positions. We can only set anchor positions after anchorsStore and nodesStore
// has been populated. TODO: maybe add a check to see that anchorsStore and NodesStore populated?
const anchors = getAnchors(store)
for (const anchor of anchors) anchor.callback()
}
/**
@@ -250,68 +193,36 @@ export function populateAnchorsStore(
* @param nodes An array of user specifed nodes
* @param canvasId The canvasId of the Svelvet component that holds the nodes
*/
export function populateNodesStore(
store: StoreType,
nodes: UserNodeType[],
canvasId: string
) {
// this is the nodesStore object. THIS IS NOT THE SAME AS A NODESTORE
const nodesStore: { [key: string]: NodeType } = {};
// iterate through user nodes and create node objects
for (let i = 0; i < nodes.length; i++) {
const userNode: UserNodeType = nodes[i];
const nodeId = userNode.id;
export function populateNodesStore(store: StoreType, nodes: UserNodeType[], canvasId: string) {
// this is the nodesStore object. THIS IS NOT THE SAME AS A NODESTORE
const nodesStore: { [key: string]: NodeType } = {}
// iterate through user nodes and create node objects
for (let i = 0; i < nodes.length; i++) {
const userNode: UserNodeType = nodes[i]
const nodeId = userNode.id
// TODO: move sanitizing default values to middleware
const node = new Node(
nodeId.toString(),
userNode.position.x,
userNode.position.y,
userNode.width,
userNode.height,
userNode.bgColor ?? 'white',
userNode.data,
canvasId,
userNode.borderColor === undefined ? 'black' : userNode.borderColor,
userNode.image === undefined ? false : userNode.image,
userNode.src === undefined ? '' : userNode.src,
userNode.textColor === undefined ? '' : userNode.textColor,
userNode.borderRadius === undefined ? 0 : userNode.borderRadius,
userNode.childNodes === undefined ? [] : userNode.childNodes,
userNode.className === undefined ? '' : userNode.className,
userNode.clickCallback === undefined ? () => { } : userNode.clickCallback
);
// TODO: move sanitizing default values to middleware
const node = new Node(
nodeId.toString(),
userNode.position.x,
userNode.position.y,
userNode.width,
userNode.height,
userNode.bgColor ?? 'white',
userNode.data,
canvasId,
userNode.borderColor === undefined ? 'black' : userNode.borderColor,
userNode.image === undefined ? false : userNode.image,
userNode.src === undefined ? '' : userNode.src,
userNode.textColor === undefined ? '' : userNode.textColor,
userNode.borderRadius === undefined ? 0 : userNode.borderRadius,
userNode.childNodes === undefined ? [] : userNode.childNodes,
userNode.className === undefined ? '' : userNode.className,
userNode.clickCallback === undefined ? () => {} : userNode.clickCallback
)
nodesStore[nodeId] = node;
}
// This is actually what sets the store
store.nodesStore.set(nodesStore);
}
/**
* Populates the resizeNodeStore. If a Node is resizable, a small ResizeNode object is going to be attached to the Node's right bottom corner to react to the mouse drag.
* @param store The Svelvet store containing the state of the Svelvet component
* @param nodes An array of user specifed nodes (NOT the same as Node)
* @param canvasId The canvasId of the Svelvet component that holds the resizeNodes
*/
export function populateResizeNodeStore(
store: StoreType,
nodes: UserNodeType[],
canvasId: string
) {
const resizeNodeStore: { [key: string]: ResizeNodeType } = {};
for (let i = 0; i < nodes.length; i++) {
const userNode = nodes[i];
const { id, width, height, position } = userNode;
const resizeNode = createResizeNode(
canvasId,
id,
position.x + width,
position.y + height
);
resizeNodeStore[resizeNode.id] = resizeNode;
}
store.resizeNodesStore.set(resizeNodeStore);
nodesStore[nodeId] = node
}
// This is actually what sets the store
store.nodesStore.set(nodesStore)
}
@@ -1,124 +1,125 @@
import type { CollapsibleType } from '../../collapsible/types/types';
import type { Writable } from 'svelte/store';
import type { AnchorType } from '../../edges/types/types';
import type { SvelteComponentTyped } from "svelte";
import type { CollapsibleType } from '../../collapsible/types/types'
import type { Writable } from 'svelte/store'
import type { AnchorType } from '../../edges/types/types'
export interface ResizeNodeType {
id: string;
nodeId: string;
edgeId?: string;
canvasId: string;
anchorId?: string;
positionX: number;
positionY: number;
setPositionAndCascade: Function;
setPosition: Function;
delete: Function;
id: string
nodeId: string
edgeId?: string
canvasId: string
anchorId?: string
positionX: number
positionY: number
setPositionAndCascade: Function
setPosition: Function
delete: Function
}
/*
Type for a single svelvet store
*/
export interface StoreType {
nodesStore: Writable<{ [key: string]: NodeType }>;
edgesStore: Writable<{ [key: string]: EdgeType }>;
anchorsStore: Writable<{ [key: string]: AnchorType }>;
resizeNodesStore: Writable<{ [key: string]: ResizeNodeType }>;
potentialAnchorsStore: Writable<{ [key: string]: PotentialAnchorType }>;
widthStore: Writable<number>;
heightStore: Writable<number>;
backgroundStore: Writable<boolean>;
movementStore: Writable<boolean>;
nodeIdSelected: Writable<number>;
nodeSelected: Writable<boolean>; // this is used to stop d3 panning when node is being dragged
d3Scale: Writable<number>; // for zoom and pan
options: Writable<{ [key: string]: any }>;
temporaryEdgeStore: Writable<TemporaryEdgeType[]>;
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<{
[key: string]: number;
}>;
resizableOption: Writable<boolean>;
highlightEdgesOption: Writable<boolean>;
nodesStore: Writable<{ [key: string]: NodeType }>
edgesStore: Writable<{ [key: string]: EdgeType }>
anchorsStore: Writable<{ [key: string]: AnchorType }>
potentialAnchorsStore: Writable<{ [key: string]: PotentialAnchorType }>
widthStore: Writable<number>
heightStore: Writable<number>
backgroundStore: Writable<boolean>
movementStore: Writable<boolean>
nodeIdSelected: Writable<number>
nodeSelected: Writable<boolean> // this is used to stop d3 panning when node is being dragged
d3Scale: Writable<number> // for zoom and pan
options: Writable<{ [key: string]: any }>
temporaryEdgeStore: Writable<TemporaryEdgeType[]>
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<{
[key: string]: number
}>
highlightEdgesOption: Writable<boolean>
}
export interface PositionType {
x: number;
y: number;
x: number
y: number
}
export interface NodeType {
id: string;
width: number;
height: number;
positionX: number;
positionY: number;
bgColor: string;
data: { html?: any, custom?: { component: any, props?: any, cb?: (e: string, detail: any) => void }, img?: any, label?: string }
canvasId: string;
setPositionFromMovement: Function;
delete: Function; //This is the method to delete the node from the store
setSizeFromMovement: Function;
setExportableData: Function;
borderColor: string;
image: boolean;
src: string;
textColor: string;
borderRadius: number;
childNodes: string[];
className: string; //This is for custom className for node
clickCallback: Function; // user-supplied callback that executes when the node is clicked
id: string
width: number
height: number
positionX: number
positionY: number
bgColor: string
data: {
html?: any
custom?: { component: any; props?: any; cb?: (e: string, detail: any) => void }
img?: any
label?: string
}
canvasId: string
setPositionFromMovement: Function
setSizeFromMovement: Function
setExportableData: Function
borderColor: string
image: boolean
src: string
textColor: string
borderRadius: number
childNodes: string[]
className: string //This is for custom className for node
clickCallback: Function // user-supplied callback that executes when the node is clicked
}
export interface EdgeType {
id: string;
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
canvasId: string;
label: string;
type: 'straight' | 'smoothstep' | 'step' | 'bezier';
labelBgColor: string;
labelTextColor: string;
edgeColor: string;
animate: boolean;
noHandle: boolean;
arrow: boolean;
clickCallback: Function;
className: string;
delete: Function;
setExportableData: Function;
id: string
sourceX: number
sourceY: number
targetX: number
targetY: number
canvasId: string
label: string
type: 'straight' | 'smoothstep' | 'step' | 'bezier'
labelBgColor: string
labelTextColor: string
edgeColor: string
animate: boolean
noHandle: boolean
arrow: boolean
clickCallback: Function
className: string
delete: Function
setExportableData: Function
}
export interface PotentialAnchorType {
id: string;
nodeId: string;
callback: Function; // callback is used to calculate positionX, positionY based on parent node's data, and set the anchor position // TODO: rename to something better
positionX: number;
positionY: number;
angle: number;
canvasId: string;
delete: Function;
id: string
nodeId: string
callback: Function // callback is used to calculate positionX, positionY based on parent node's data, and set the anchor position // TODO: rename to something better
positionX: number
positionY: number
angle: number
canvasId: string
delete: Function
}
export interface TemporaryEdgeType {
id: string;
sourcePotentialAnchorId: string; // this will always be set
sourceX: number;
sourceY: number;
targetPotentialAnchorId: string | null; // this will be null until the temporary edge reaches another temporary anchor
targetX: number;
targetY: number;
canvasId: string;
type: string;
edgeColor: string;
createEdge: Function;
createNode: Function;
id: string
sourcePotentialAnchorId: string // this will always be set
sourceX: number
sourceY: number
targetPotentialAnchorId: string | null // this will be null until the temporary edge reaches another temporary anchor
targetX: number
targetY: number
canvasId: string
type: string
edgeColor: string
createEdge: Function
createNode: Function
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": ["tslint-etc"],
"rules": {
"no-unused-declaration": true
}
}