diff --git a/src/hooks/use-get-overlapping-nodes.tsx b/src/hooks/use-get-overlapping-nodes.tsx index 8bc4f65..f20b365 100644 --- a/src/hooks/use-get-overlapping-nodes.tsx +++ b/src/hooks/use-get-overlapping-nodes.tsx @@ -19,7 +19,7 @@ const useGetOverlappingNodes = (): Set => { } } return overlaps - }, [nodes]) ; + }, [nodes]); return overlappingNodes; } @@ -48,9 +48,6 @@ function isNodesOverlapping(nodeA: Node, nodeB: Node) { bottom: nodeB.position.y + nodeBHeight, }; - - - return !(a.right <= b.left || a.left >= b.right || a.bottom <= b.top || a.top >= b.bottom); } diff --git a/src/hooks/use-get-related-edges.tsx b/src/hooks/use-get-related-edges.tsx index 5b46acf..23cc5a6 100644 --- a/src/hooks/use-get-related-edges.tsx +++ b/src/hooks/use-get-related-edges.tsx @@ -1,4 +1,4 @@ -import { Edge, useStore } from "@xyflow/react"; +import { Edge, useReactFlow, useStore } from "@xyflow/react"; import { useEffect, useState } from "react"; import hash from 'object-hash'; @@ -6,19 +6,25 @@ import hash from 'object-hash'; -const useGetRelatedEdges = ( nodeId : string) => { - const edges = useStore((store) => store.edges) as Edge[]; +const useGetRelatedEdges = (nodeId: string) => { + const [relatedEdges, setRelatedEdges] = useState([]); + const { getEdges } = useReactFlow(); + const edges = getEdges(); + + useEffect(() => { + const newRelatedEdges: Edge[] = edges.filter((edge: Edge) => edge.source == nodeId || edge.target == nodeId); - setRelatedEdges((previousEdges :Edge[]) => { - return hash(newRelatedEdges) == hash(previousEdges) ? previousEdges : newRelatedEdges - }) + if (hash(newRelatedEdges) != hash(relatedEdges)) { + setRelatedEdges(newRelatedEdges) + } + }, [edges, nodeId]) return relatedEdges -} +} export default useGetRelatedEdges; \ No newline at end of file diff --git a/src/hooks/use-relationship-to-edge.tsx b/src/hooks/use-relationship-to-edge.tsx index ef16700..65e24d2 100644 --- a/src/hooks/use-relationship-to-edge.tsx +++ b/src/hooks/use-relationship-to-edge.tsx @@ -23,7 +23,9 @@ export const useRelationshipToEdge = (relationships: RelationshipType[]): void = } as Edge }) + setEdges((edges: any) => { + return relationshipEdges.map((relationshipEdge: any) => { const edge = edges.find((edge: Edge) => edge.id == relationshipEdge.id); if (!edge) diff --git a/src/hooks/use-table-to-node.tsx b/src/hooks/use-table-to-node.tsx index bb33d91..b4a06e9 100644 --- a/src/hooks/use-table-to-node.tsx +++ b/src/hooks/use-table-to-node.tsx @@ -1,25 +1,29 @@ import { TableType } from "@/lib/schemas/table-schema"; import { Node, useReactFlow } from "@xyflow/react"; -import { useEffect } from "react"; -import useGetOverlappingNodes from "./use-get-overlapping-nodes"; +import { useEffect } from "react"; import hash from 'object-hash'; +import { getDefaultTableOverlapping } from "@/utils/tables"; export const useTableToNode = (tables: TableType[]): void => { const { setNodes } = useReactFlow(); + useEffect(() => { const tableNodes = tables.map((table: TableType) => { return { id: table.id, type: "table", - + position: { x: table.posX, y: table.posY }, data: { - table, + table, + overlapping : getDefaultTableOverlapping(table , tables) , + pulsing : false , + highlightedEdges : [] }, style: { width: 224 @@ -27,21 +31,7 @@ export const useTableToNode = (tables: TableType[]): void => { } as Node }) - - setNodes((nodes) => { - - return tableNodes.map((tableNode: Node) => { - const node = nodes.find((node: Node) => node.id == tableNode.id); - if (!node) { - return tableNode; - } - - const hashNode: string = hash((node.data as any).table); - const hashTableNode: string = hash((tableNode.data as any).table); - return hashNode == hashTableNode ? node : tableNode; - - }) - }) + setNodes (tableNodes) ; }, [tables]) } \ No newline at end of file diff --git a/src/pages/database/database-page.tsx b/src/pages/database/database-page.tsx index a279152..c467f5c 100644 --- a/src/pages/database/database-page.tsx +++ b/src/pages/database/database-page.tsx @@ -14,7 +14,7 @@ import { v4 } from "uuid"; import { TARGET_PREFIX } from "./table/field"; import CardinalityMarker from "@/components/cardinality-marker/cardinality-marker"; import { areArraysEqual } from "@/utils/utils"; -import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; +import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; import { addToast, Button, Image } from "@heroui/react"; import { AlertTriangle, LayoutGrid } from "lucide-react"; @@ -23,17 +23,17 @@ import { useTheme } from "next-themes"; import DatabaseControlButtons from "./database-control-buttons"; import useGetOverlappingNodes from "@/hooks/use-get-overlapping-nodes"; import { FieldType } from "@/lib/schemas/field-schema"; - +import hash from "object-hash"; const DatabasePage: React.FC = () => { - const { database } = useDatabase(); - const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship, getField } = useDatabaseOperations(); + const { database, getField } = useDatabase(); + const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabaseOperations(); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); - const { setIsConnectionInProgress } = useDiagram(); + const { setIsConnectionInProgress } = useDiagramOps(); const [selectedNodeIds, setSelectedNodeIds] = useState([]); const [isTableOverlappingPulsing, setIsTableOverlappingPulsing] = useState(false); const { tables, relationships } = database; @@ -48,7 +48,8 @@ const DatabasePage: React.FC = () => { const sourceField: FieldType | undefined = getField(connection.source, sourceFieldId as string); const targetField: FieldType | undefined = getField(connection.target, targetFieldId); - console.log(sourceField, targetField) + + if (sourceField?.typeId == targetField?.typeId) { createRelationship({ @@ -73,7 +74,11 @@ const DatabasePage: React.FC = () => { const handleNodesChanges: OnNodesChange = useCallback((changes: NodeChange[]) => { - const nodePositionChanges: NodePositionChange[] = changes.filter((change: NodeChange) => change.type == "position" && !change.dragging) as NodePositionChange[]; + const nodePositionChanges: NodePositionChange[] = changes.filter((change: NodeChange) => + change.type == "position" && + !change.dragging + ) as NodePositionChange[]; + const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove"); if (nodePositionChanges.length > 0) @@ -115,15 +120,29 @@ const DatabasePage: React.FC = () => { useEffect(() => { + setEdges((edges: any) => { return edges.map((edge: any) => { const selected: boolean = selectedEdgeIds.includes(edge.id); + return (edge.animated == selected) ? edge : { ...edge, animated: selected } as Edge; + }) + }); + + + setNodes((nodes: any) => { + return nodes.map((node: any) => { + + const nodeHighlitedEdges: Edge[] = node.data.highlightedEdges; + const newHighlitedEdges: Edge[] = edges.filter((edge: Edge) => selectedEdgeIds.includes(edge.id) && (edge.source == node.id || edge.target == node.id)); + console.log(nodeHighlitedEdges, newHighlitedEdges); + return { - ...edge, - animated: selected, - } as Edge - - + ...node, + data: { + ...node.data, + highlightedEdges: newHighlitedEdges + } + } }) }) }, [selectedEdgeIds]); @@ -155,7 +174,6 @@ const DatabasePage: React.FC = () => { const overlappingNodes = useGetOverlappingNodes(); - useEffect(() => { const overlappingIds = Array.from(overlappingNodes); diff --git a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx index 6731edb..8a9a4c6 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx @@ -1,7 +1,7 @@ import { useRelationshipName } from "@/hooks/use-relationship-name"; import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; -import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; -import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; +import { useDatabaseOperations } from "@/providers/database-provider/database-provider"; +import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import { Button, cn, Input, Listbox, ListboxItem, Popover, PopoverContent, PopoverTrigger } from "@heroui/react"; import { Check, ChevronRight, EllipsisVertical, Focus, Pencil, Trash } from "lucide-react"; import { useEffect, useState } from "react"; @@ -23,7 +23,7 @@ const RelationshipAccordionHeader: React.FC = const { t } = useTranslation(); const [popOverOpen, setPopOverOpen] = useState(false); const [name, setName] = useState(relationship.name ? relationship.name : defaultName); - const { focusOnRelationship } = useDiagram(); + const { focusOnRelationship } = useDiagramOps(); const editRelationshipName = () => { @@ -43,8 +43,8 @@ const RelationshipAccordionHeader: React.FC = useEffect(() => { - setName(relationship.name ? relationship.name : defaultName) ; - } , [relationship.name]) + setName(relationship.name ? relationship.name : defaultName); + }, [relationship.name]) return (
= ({ table, isOp const { t } = useTranslation(); const [editMode, setEditMode] = useState(false); - const { focusOnTable } = useDiagram(); + const { focusOnTable } = useDiagramOps(); - useEffect(() => { + useEffect(() => { setTableName(table.name); }, [table.name]) - const saveTableName = useCallback(async() => { + const saveTableName = useCallback(async () => { await editTable({ id: table.id, name: tableName } as TableInsertType); setEditMode(false); - } , [tableName]) + }, [tableName]) const onDeleteTable = async () => { deleteTable(table.id) @@ -126,7 +126,7 @@ const TableAccordionHeader: React.FC = ({ table, isOp size="sm" isIconOnly variant="light" - onPressEnd={() => focusOnTable(table.id , true)} + onPressEnd={() => focusOnTable(table.id, true)} > diff --git a/src/pages/database/table/relationship.tsx b/src/pages/database/table/relationship.tsx index 4683861..bdf940d 100644 --- a/src/pages/database/table/relationship.tsx +++ b/src/pages/database/table/relationship.tsx @@ -1,7 +1,7 @@ import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema"; -import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; +import { useDiagram, useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import { card, cn } from "@heroui/react"; import { Edge, EdgeProps, getBezierPath, getSmoothStepPath, InternalNode, Node, Position, useReactFlow } from "@xyflow/react"; import React, { useMemo } from "react"; @@ -18,7 +18,7 @@ const Relationship: React.FC> = (props) => { let { id, sourceX, sourceY, targetX, targetY, source, target, selected, data, animated } = props; const { getInternalNode, getEdge } = useReactFlow(); - const { focusOnRelationship } = useDiagram(); + const { focusOnRelationship } = useDiagramOps(); const sourceNode = getInternalNode(source); diff --git a/src/pages/database/table/table.tsx b/src/pages/database/table/table.tsx index 22f1cfc..9f6d546 100644 --- a/src/pages/database/table/table.tsx +++ b/src/pages/database/table/table.tsx @@ -1,7 +1,7 @@ import { Edge, Node, NodeProps, useConnection, useStore } from "@xyflow/react"; - +import hash from 'object-hash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button, Card, cn, } from "@heroui/react"; import { @@ -17,22 +17,26 @@ import { TableInsertType, TableType } from "@/lib/schemas/table-schema"; import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; import { useTranslation } from "react-i18next"; import { RelationshipType } from "@/lib/schemas/relationship-schema"; -import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; +import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import useGetRelatedEdges from "@/hooks/use-get-related-edges"; export type TableProps = Node<{ table: TableType, overlapping?: boolean, pulsing?: boolean, + highlightedEdges : Edge[] + }> -const Table: React.FC> = (props) => { - const { selected, data: { table, overlapping = false, pulsing = false } } = props; +const Table: React.FC> = ({ selected, data: { table, overlapping = false, pulsing = false , highlightedEdges = [] } }) => { + + const [editMode, setEditMode] = useState(false); const [tableName, setTableName] = useState(table.name); const { editTable } = useDatabaseOperations(); - const edges = useGetRelatedEdges(table.id as string); - const { focusOnTable } = useDiagram(); + + // const edges = useGetRelatedEdges(table.id as string); + const { focusOnTable } = useDiagramOps(); const { t } = useTranslation(); useEffect(() => { @@ -47,11 +51,13 @@ const Table: React.FC> = (props) => { const focus = useCallback(() => { focusOnTable(table.id, false); }, [table]) - + /* const highlightedEdges: Edge[] = useMemo(() => { return edges.filter((edge: Edge) => edge.animated || edge.selected); }, [edges]); + */ + const fields: React.ReactNode[] = useMemo(() => { return table.fields.map((field: FieldType) => { const highlight: boolean = highlightedEdges.find((edge: any) => @@ -66,25 +72,19 @@ const Table: React.FC> = (props) => { />) }) }, [table.fields, selected, highlightedEdges]); - - - useEffect(() => { - console.log("re-render ", table.name) - - }, [useDatabase]) ; - - return ( + + + console.log ("re-render " , table.name) + return ( > = (props) => { ) }; -export default React.memo(Table) +export default React.memo(Table, (previousState: any, newState: any) => { + const previousStateHash: string = hash(previousState.data); + const newStateHash: string = hash(newState.data); + + + + + return previousStateHash == newStateHash && previousState.selected == newState.selected; +}) diff --git a/src/providers/database-provider/database-context.ts b/src/providers/database-provider/database-context.ts index 402a7a4..050b094 100644 --- a/src/providers/database-provider/database-context.ts +++ b/src/providers/database-provider/database-context.ts @@ -15,8 +15,8 @@ interface DatabaseDataContextType { data_types: DataType[], database: DatabaseType, - isLoading: boolean, - // table operations + isLoading: boolean, + getField: (tableId: string, id: string) => FieldType | undefined, } @@ -33,7 +33,6 @@ interface DatabaseOperationsContextType { editField: (field: FieldInsertType) => Promise, deleteField: (id: string) => Promise, orderTableFields: (fields: FieldType[]) => Promise, - getField: (tableId: string, id: string) => FieldType | undefined, // relationship operations createRelationship: (relationship: RelationshipInsertType) => Promise, editRelationship: (relationship: RelationshipInsertType) => Promise, diff --git a/src/providers/database-provider/database-provider.tsx b/src/providers/database-provider/database-provider.tsx index 79351b4..89abd9b 100644 --- a/src/providers/database-provider/database-provider.tsx +++ b/src/providers/database-provider/database-provider.tsx @@ -1,6 +1,6 @@ import { DatabaseDataContext, DatabaseOperationsContext } from "./database-context"; -import { useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { db, powerSyncDb } from "../sync-provider/sync-provider"; import { TableInsertType, tables, TableType } from "@/lib/schemas/table-schema"; import { useQuery } from "@powersync/react"; @@ -223,38 +223,53 @@ const DatabaseProvider: React.FC = ({ children }) => { } }) - }, [db, currentDatabaseId]); + }, [db]); + + + + const databaseOpsValue = useMemo(() => ({ + createTable, + editTable, + deleteTable, + updateTablePositions, + deleteMultiTables, + createField, + editField, + deleteField, + orderTableFields, + + createRelationship, + editRelationship, + deleteRelationship, + deleteMultiRelationships, + executeDbDiffOps, + }), [ + createTable, + editTable, + deleteTable, + updateTablePositions, + deleteMultiTables, + createField, + editField, + deleteField, + orderTableFields, + + createRelationship, + editRelationship, + deleteRelationship, + deleteMultiRelationships, + executeDbDiffOps, + ]); return ( - - + { !isLoading && database && diff --git a/src/providers/diagram-provider/diagram-context.tsx b/src/providers/diagram-provider/diagram-context.tsx index f9f2e97..9260818 100644 --- a/src/providers/diagram-provider/diagram-context.tsx +++ b/src/providers/diagram-provider/diagram-context.tsx @@ -1,18 +1,18 @@ import { createContext, Dispatch, SetStateAction } from "react"; -export interface DiagramContextType { +interface DiagramDataContextType { + focusedTableId: string | undefined; + focusedRelationshipId: string | undefined; + isConnectionInProgress: boolean +} - focusedTableId : string | undefined ; - focusedRelationshipId : string | undefined; - isConnectionInProgress : boolean - - focusOnTable : ( id : string , transition? : boolean ) => void , - focusOnRelationship : ( id : string, transition? : boolean ) => void , - setIsConnectionInProgress : Dispatch - -} +interface DiagramOpsContextType { + focusOnTable: (id: string, transition?: boolean) => void, + focusOnRelationship: (id: string, transition?: boolean) => void, + setIsConnectionInProgress: Dispatch +} - -export default createContext({} as DiagramContextType); \ No newline at end of file +export const DiagramDataContext = createContext({} as DiagramDataContextType); +export const DiagramOpsContext = createContext({} as DiagramOpsContextType); \ No newline at end of file diff --git a/src/providers/diagram-provider/diagram-provider.tsx b/src/providers/diagram-provider/diagram-provider.tsx index 11d7a1f..72bdf89 100644 --- a/src/providers/diagram-provider/diagram-provider.tsx +++ b/src/providers/diagram-provider/diagram-provider.tsx @@ -3,7 +3,7 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { FitViewOptions, useReactFlow } from "@xyflow/react"; import { useNavigate } from "react-router-dom"; import { RelationshipType } from "@/lib/schemas/relationship-schema"; -import DiagramContext from "./diagram-context"; +import { DiagramDataContext, DiagramOpsContext } from "./diagram-context"; @@ -73,26 +73,35 @@ const DiagramProvider: React.FC = ({ children }) => { }, [setFocusedRelationshipId]); - const contextValue = useMemo(() => ({ + const contextDatatValue = useMemo(() => ({ focusedTableId, focusedRelationshipId, isConnectionInProgress, + }), [focusedTableId, focusedRelationshipId, isConnectionInProgress,]); + + const contextOpsValues = useMemo(() => ({ focusOnTable, focusOnRelationship, setIsConnectionInProgress - }), [focusedTableId, focusedRelationshipId, focusOnTable, focusOnRelationship, isConnectionInProgress, setIsConnectionInProgress]); + + }), [focusOnTable, focusOnRelationship, setIsConnectionInProgress]) return ( - - {children} - + + {children} + + + ) } -export const useDiagram = () => useContext(DiagramContext); +export const useDiagram = () => useContext(DiagramDataContext); + +export const useDiagramOps = () => useContext(DiagramOpsContext); export default DiagramProvider; \ No newline at end of file diff --git a/src/utils/tables.ts b/src/utils/tables.ts index d4e0af9..0e75643 100644 --- a/src/utils/tables.ts +++ b/src/utils/tables.ts @@ -51,7 +51,44 @@ const adjustTablesPositions = async ( }; + +const isTablesOverlapping = (tableA: TableType, tableB: TableType) => { + const tableAWidth: number = 224; + const tableAHeight: number = tableA.fields.length * 32 + 36; + + + const tableBWidth: number = 224; + const tableBHeight: number = tableB.fields.length * 32 + 36; + + const a = { + left: tableA.posX, + right: tableA.posX + tableAWidth, + top: tableA.posY, + bottom: tableA.posY + tableAHeight, + }; + + const b = { + left: tableB.posX, + right: tableB.posX + tableBWidth, + top: tableB.posY, + bottom: tableB.posY + tableBHeight, + }; + + return !(a.right <= b.left || a.left >= b.right || a.bottom <= b.top || a.top >= b.bottom); +} + + +const getDefaultTableOverlapping = (table: TableType, tables: TableType[]): boolean => { + for (let index: number = 0; index < tables.length; index++) { + if ( table.id == tables[index].id) continue ; + if (isTablesOverlapping(table, tables[index])) + return true; + } + return false; +} + export { adjustTablesPositions, + getDefaultTableOverlapping } \ No newline at end of file diff --git a/vite.config.ts.timestamp-1747861983061-fc492b9817cf7.mjs b/vite.config.ts.timestamp-1747861983061-fc492b9817cf7.mjs new file mode 100644 index 0000000..b215b24 --- /dev/null +++ b/vite.config.ts.timestamp-1747861983061-fc492b9817cf7.mjs @@ -0,0 +1,25 @@ +// vite.config.ts +import { defineConfig } from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite/dist/node/index.js"; +import react from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/@vitejs/plugin-react/dist/index.mjs"; +import tsconfigPaths from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite-tsconfig-paths/dist/index.mjs"; +import topLevelAwait from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite-plugin-top-level-await/exports/import.mjs"; +var vite_config_default = defineConfig({ + plugins: [react(), tsconfigPaths(), topLevelAwait()], + server: { + port: 3e3 + }, + optimizeDeps: { + // Don't optimize these packages as they contain web workers and WASM files. + // https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673 + exclude: ["@journeyapps/wa-sqlite", "@powersync/web"], + include: ["@powersync/web > js-logger"] + }, + worker: { + format: "es", + plugins: () => [topLevelAwait()] + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx0YW1hblxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXHN0YWNrcmVuZGVyXFxcXHN0YWNrcmVuZGVyXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx0YW1hblxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXHN0YWNrcmVuZGVyXFxcXHN0YWNrcmVuZGVyXFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy90YW1hbi9PbmVEcml2ZS9EZXNrdG9wL3N0YWNrcmVuZGVyL3N0YWNrcmVuZGVyL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSdcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCdcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnXG5pbXBvcnQgdG9wTGV2ZWxBd2FpdCBmcm9tICd2aXRlLXBsdWdpbi10b3AtbGV2ZWwtYXdhaXQnO1xuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtyZWFjdCgpLCB0c2NvbmZpZ1BhdGhzKCkgLCB0b3BMZXZlbEF3YWl0KCldLFxuICBzZXJ2ZXIgOiB7IFxuICAgIHBvcnQgOiAzMDAwXG4gIH0gLCBcbiAgb3B0aW1pemVEZXBzOiB7XG4gICAgLy8gRG9uJ3Qgb3B0aW1pemUgdGhlc2UgcGFja2FnZXMgYXMgdGhleSBjb250YWluIHdlYiB3b3JrZXJzIGFuZCBXQVNNIGZpbGVzLlxuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS92aXRlanMvdml0ZS9pc3N1ZXMvMTE2NzIjaXNzdWVjb21tZW50LTE0MTU4MjA2NzNcbiAgICBleGNsdWRlOiBbJ0Bqb3VybmV5YXBwcy93YS1zcWxpdGUnLCAnQHBvd2Vyc3luYy93ZWInXSxcbiAgICBpbmNsdWRlOiBbJ0Bwb3dlcnN5bmMvd2ViID4ganMtbG9nZ2VyJ11cbiAgfSxcbiAgd29ya2VyOiB7XG4gICAgZm9ybWF0OiAnZXMnLFxuICAgIHBsdWdpbnM6ICgpID0+IFsgdG9wTGV2ZWxBd2FpdCgpXVxuICB9XG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFxVyxTQUFTLG9CQUFvQjtBQUNsWSxPQUFPLFdBQVc7QUFDbEIsT0FBTyxtQkFBbUI7QUFDMUIsT0FBTyxtQkFBbUI7QUFFMUIsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLE1BQU0sR0FBRyxjQUFjLEdBQUksY0FBYyxDQUFDO0FBQUEsRUFDcEQsUUFBUztBQUFBLElBQ1AsTUFBTztBQUFBLEVBQ1Q7QUFBQSxFQUNBLGNBQWM7QUFBQTtBQUFBO0FBQUEsSUFHWixTQUFTLENBQUMsMEJBQTBCLGdCQUFnQjtBQUFBLElBQ3BELFNBQVMsQ0FBQyw0QkFBNEI7QUFBQSxFQUN4QztBQUFBLEVBQ0EsUUFBUTtBQUFBLElBQ04sUUFBUTtBQUFBLElBQ1IsU0FBUyxNQUFNLENBQUUsY0FBYyxDQUFDO0FBQUEsRUFDbEM7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=