mirror of https://github.com/ctk-hq/ctk
reverse update
parent
9d4046b3bb
commit
16e4b3d670
@ -1,99 +0,0 @@
|
||||
// Based on https://github.com/codemirror/theme-one-dark
|
||||
// Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||
// MIT License: https://github.com/codemirror/theme-one-dark/blob/main/LICENSE
|
||||
|
||||
import { HighlightStyle, tags as t } from "@codemirror/highlight";
|
||||
|
||||
const chalky = "#e5c07b",
|
||||
coral = "#e06c75",
|
||||
cyan = "#56b6c2",
|
||||
invalid = "#ffffff",
|
||||
ivory = "#abb2bf",
|
||||
stone = "#5c6370",
|
||||
malibu = "#61afef",
|
||||
sage = "#98c379",
|
||||
whiskey = "#d19a66",
|
||||
violet = "#c678dd";
|
||||
|
||||
/// The highlighting style for code in the One Dark theme.
|
||||
export default HighlightStyle.define([
|
||||
{
|
||||
tag: t.keyword,
|
||||
color: violet
|
||||
},
|
||||
{
|
||||
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
|
||||
color: coral
|
||||
},
|
||||
{
|
||||
tag: [t.processingInstruction, t.string, t.inserted],
|
||||
color: sage
|
||||
},
|
||||
{
|
||||
tag: [t.function(t.variableName), t.labelName],
|
||||
color: malibu
|
||||
},
|
||||
{
|
||||
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
||||
color: whiskey
|
||||
},
|
||||
{
|
||||
tag: [t.definition(t.name), t.separator],
|
||||
color: ivory
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
t.typeName,
|
||||
t.className,
|
||||
t.number,
|
||||
t.changed,
|
||||
t.annotation,
|
||||
t.modifier,
|
||||
t.self,
|
||||
t.namespace
|
||||
],
|
||||
color: chalky
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
t.operator,
|
||||
t.operatorKeyword,
|
||||
t.url,
|
||||
t.escape,
|
||||
t.regexp,
|
||||
t.link,
|
||||
t.special(t.string)
|
||||
],
|
||||
color: cyan
|
||||
},
|
||||
{
|
||||
tag: [t.meta, t.comment],
|
||||
color: stone
|
||||
},
|
||||
{
|
||||
tag: t.strong,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
{
|
||||
tag: t.emphasis,
|
||||
fontStyle: "italic"
|
||||
},
|
||||
{
|
||||
tag: t.link,
|
||||
color: stone,
|
||||
textDecoration: "underline"
|
||||
},
|
||||
{
|
||||
tag: t.heading,
|
||||
fontWeight: "bold",
|
||||
color: coral
|
||||
},
|
||||
{
|
||||
tag: [t.atom, t.bool, t.special(t.variableName)],
|
||||
color: whiskey
|
||||
},
|
||||
{
|
||||
tag: t.invalid,
|
||||
color: invalid
|
||||
}
|
||||
]);
|
||||
@ -1,218 +0,0 @@
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { HighlightStyle, tags } from "@codemirror/highlight";
|
||||
|
||||
const base00 = "#1F2937",
|
||||
base01 = "#073642",
|
||||
base02 = "#586e75",
|
||||
base03 = "#657b83",
|
||||
base04 = "#839496",
|
||||
base05 = "#dfdfdf",
|
||||
base06 = "#efefef",
|
||||
base07 = "#fdf6e3",
|
||||
base_red = "#dc322f",
|
||||
base_orange = "#cb4b16",
|
||||
base_yellow = "#e9b100",
|
||||
base_green = "#cfec11",
|
||||
base_cyan = "#44e0d4",
|
||||
base_blue = "#75c6ff",
|
||||
base_violet = "#a1a6ff",
|
||||
base_magenta = "#d33682";
|
||||
const invalid = "#d30102",
|
||||
stone = base04,
|
||||
darkBackground = "#1F2937",
|
||||
highlightBackground = "#173541",
|
||||
background = base00,
|
||||
tooltipBackground = base01,
|
||||
selection = "#173541",
|
||||
cursor = base04;
|
||||
/**
|
||||
The editor theme styles for Solarized Dark.
|
||||
*/
|
||||
const solarizedDarkTheme = /*@__PURE__*/ EditorView.theme(
|
||||
{
|
||||
"&": {
|
||||
fontSize: "10.5pt",
|
||||
color: base05,
|
||||
backgroundColor: background
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: cursor
|
||||
},
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
|
||||
{ backgroundColor: selection },
|
||||
".cm-panels": { backgroundColor: darkBackground, color: base03 },
|
||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "#72a1ff59",
|
||||
outline: "1px solid #457dff"
|
||||
},
|
||||
".cm-searchMatch.cm-searchMatch-selected": {
|
||||
backgroundColor: "#6199ff2f"
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: highlightBackground },
|
||||
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
|
||||
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
|
||||
outline: `1px solid ${base06}`
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: darkBackground,
|
||||
color: stone,
|
||||
border: "none"
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: highlightBackground
|
||||
},
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "#ddd"
|
||||
},
|
||||
".cm-tooltip": {
|
||||
border: "none",
|
||||
backgroundColor: tooltipBackground
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:before": {
|
||||
borderTopColor: "transparent",
|
||||
borderBottomColor: "transparent"
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground
|
||||
},
|
||||
".cm-tooltip-autocomplete": {
|
||||
"& > ul > li[aria-selected]": {
|
||||
backgroundColor: highlightBackground,
|
||||
color: base03
|
||||
}
|
||||
}
|
||||
},
|
||||
{ dark: true }
|
||||
);
|
||||
/**
|
||||
The highlighting style for code in the Solarized Dark theme.
|
||||
*/
|
||||
const solarizedDarkHighlightStyle = /*@__PURE__*/ HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: base_green },
|
||||
{
|
||||
tag: [
|
||||
tags.name,
|
||||
tags.deleted,
|
||||
tags.character,
|
||||
tags.propertyName,
|
||||
tags.macroName
|
||||
],
|
||||
color: base_cyan
|
||||
},
|
||||
{ tag: [tags.variableName], color: base05 },
|
||||
{ tag: [/*@__PURE__*/ tags.function(tags.variableName)], color: base_blue },
|
||||
{ tag: [tags.labelName], color: base_magenta },
|
||||
{
|
||||
tag: [
|
||||
tags.color,
|
||||
/*@__PURE__*/ tags.constant(tags.name),
|
||||
/*@__PURE__*/ tags.standard(tags.name)
|
||||
],
|
||||
color: base_yellow
|
||||
},
|
||||
{
|
||||
tag: [/*@__PURE__*/ tags.definition(tags.name), tags.separator],
|
||||
color: base_cyan
|
||||
},
|
||||
{ tag: [tags.brace], color: base_magenta },
|
||||
{
|
||||
tag: [tags.annotation],
|
||||
color: invalid
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
tags.number,
|
||||
tags.changed,
|
||||
tags.annotation,
|
||||
tags.modifier,
|
||||
tags.self,
|
||||
tags.namespace
|
||||
],
|
||||
color: base_magenta
|
||||
},
|
||||
{
|
||||
tag: [tags.typeName, tags.className],
|
||||
color: base_orange
|
||||
},
|
||||
{
|
||||
tag: [tags.operator, tags.operatorKeyword],
|
||||
color: base_violet
|
||||
},
|
||||
{
|
||||
tag: [tags.tagName],
|
||||
color: base_blue
|
||||
},
|
||||
{
|
||||
tag: [tags.squareBracket],
|
||||
color: base_red
|
||||
},
|
||||
{
|
||||
tag: [tags.angleBracket],
|
||||
color: base02
|
||||
},
|
||||
{
|
||||
tag: [tags.attributeName],
|
||||
color: base05
|
||||
},
|
||||
{
|
||||
tag: [tags.regexp],
|
||||
color: invalid
|
||||
},
|
||||
{
|
||||
tag: [tags.quote],
|
||||
color: base_green
|
||||
},
|
||||
{ tag: [tags.string], color: base_yellow },
|
||||
{
|
||||
tag: [tags.url, tags.escape, /*@__PURE__*/ tags.special(tags.string)],
|
||||
color: base_yellow
|
||||
},
|
||||
{ tag: [tags.meta], color: base_red },
|
||||
{ tag: [tags.comment], color: base02, fontStyle: "italic" },
|
||||
{ tag: tags.strong, fontWeight: "bold", color: base06 },
|
||||
{ tag: tags.emphasis, fontStyle: "italic", color: base_green },
|
||||
{ tag: tags.strikethrough, textDecoration: "line-through" },
|
||||
{
|
||||
tag: tags.link,
|
||||
color: base_cyan,
|
||||
textDecoration: "underline",
|
||||
textUnderlinePosition: "under"
|
||||
},
|
||||
{ tag: tags.heading, fontWeight: "bold", color: base_yellow },
|
||||
{ tag: tags.heading1, fontWeight: "bold", color: base07 },
|
||||
{
|
||||
tag: [tags.heading2, tags.heading3, tags.heading4],
|
||||
fontWeight: "bold",
|
||||
color: base06
|
||||
},
|
||||
{
|
||||
tag: [tags.heading5, tags.heading6],
|
||||
color: base06
|
||||
},
|
||||
{
|
||||
tag: [tags.atom, tags.bool, /*@__PURE__*/ tags.special(tags.variableName)],
|
||||
color: base_magenta
|
||||
},
|
||||
{
|
||||
tag: [tags.processingInstruction, tags.inserted, tags.contentSeparator],
|
||||
color: base_red
|
||||
},
|
||||
{
|
||||
tag: [tags.contentSeparator],
|
||||
color: base_yellow
|
||||
},
|
||||
{ tag: tags.invalid, color: base02, borderBottom: `1px dotted ${base_red}` }
|
||||
]);
|
||||
/**
|
||||
Extension to enable the Solarized Dark theme (both the editor theme and
|
||||
the highlight style).
|
||||
*/
|
||||
const solarizedDark = [solarizedDarkTheme, solarizedDarkHighlightStyle];
|
||||
|
||||
export { solarizedDark, solarizedDarkHighlightStyle, solarizedDarkTheme };
|
||||
@ -1,30 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { Extension } from "@codemirror/state";
|
||||
|
||||
export default function useCodeMirror(extensions: Extension[], doc: any) {
|
||||
const [element, setElement] = useState<HTMLElement>();
|
||||
|
||||
const ref = useCallback((node: HTMLElement | null) => {
|
||||
if (!node) return;
|
||||
|
||||
setElement(node);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!element) return;
|
||||
|
||||
const view = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: doc,
|
||||
extensions: [...extensions]
|
||||
}),
|
||||
parent: element
|
||||
});
|
||||
|
||||
return () => view?.destroy();
|
||||
}, [element, extensions, doc]);
|
||||
|
||||
return { ref };
|
||||
}
|
||||
@ -0,0 +1,388 @@
|
||||
import { Dictionary } from "lodash";
|
||||
import {
|
||||
ensure,
|
||||
flattenLibraries,
|
||||
getClientNodeItem,
|
||||
iconByNodeType,
|
||||
attachUUID
|
||||
} from "./index";
|
||||
import { INetworkNodeItem, IServiceNodeItem, IVolumeNodeItem } from "../types";
|
||||
import { nodeLibraries } from "./data/libraries";
|
||||
|
||||
type ComposeRecord = Record<string, unknown>;
|
||||
type ComposeNodes = Dictionary<IServiceNodeItem | IVolumeNodeItem>;
|
||||
|
||||
interface IComposeGraphResult {
|
||||
version: string;
|
||||
nodes: ComposeNodes;
|
||||
connections: Array<[string, string]>;
|
||||
networks: Record<string, INetworkNodeItem>;
|
||||
}
|
||||
|
||||
const COMPOSE_RESERVED_TOP_LEVEL_KEYS = new Set([
|
||||
"version",
|
||||
"services",
|
||||
"volumes",
|
||||
"networks",
|
||||
"configs",
|
||||
"secrets",
|
||||
"name"
|
||||
]);
|
||||
|
||||
const isRecord = (value: unknown): value is ComposeRecord => {
|
||||
return Boolean(value) && value?.constructor === Object;
|
||||
};
|
||||
|
||||
const toRecord = (value: unknown): ComposeRecord => {
|
||||
if (isRecord(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const getDefaultNodePosition = (index: number) => {
|
||||
const columns = 3;
|
||||
const left = 100 + (index % columns) * 280;
|
||||
const top = 100 + Math.floor(index / columns) * 180;
|
||||
|
||||
return { left, top };
|
||||
};
|
||||
|
||||
const getDependsOnServiceNames = (dependsOn: unknown): string[] => {
|
||||
if (Array.isArray(dependsOn)) {
|
||||
return dependsOn.filter((item): item is string => typeof item === "string");
|
||||
}
|
||||
|
||||
if (isRecord(dependsOn)) {
|
||||
return Object.keys(dependsOn);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const getVolumeMountSourceName = (mount: unknown): string | null => {
|
||||
if (typeof mount === "string") {
|
||||
const source = mount.split(":", 1)[0].trim();
|
||||
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore bind mounts and host paths.
|
||||
if (
|
||||
source.startsWith("/") ||
|
||||
source.startsWith(".") ||
|
||||
source.startsWith("~") ||
|
||||
/^[a-zA-Z]:[\\/]/.test(source)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
if (isRecord(mount) && typeof mount.source === "string") {
|
||||
const source = mount.source.trim();
|
||||
const mountType =
|
||||
typeof mount.type === "string" ? mount.type.toLowerCase() : "";
|
||||
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Long syntax can represent bind mounts as well.
|
||||
if (mountType && mountType !== "volume") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getServiceVolumeNames = (volumes: unknown): string[] => {
|
||||
if (!Array.isArray(volumes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const names: string[] = [];
|
||||
volumes.forEach((mount) => {
|
||||
const sourceName = getVolumeMountSourceName(mount);
|
||||
if (sourceName && !names.includes(sourceName)) {
|
||||
names.push(sourceName);
|
||||
}
|
||||
});
|
||||
|
||||
return names;
|
||||
};
|
||||
|
||||
const getLegacyV1Services = (composeData: ComposeRecord): ComposeRecord => {
|
||||
const services: ComposeRecord = {};
|
||||
|
||||
Object.entries(composeData).forEach(([key, value]) => {
|
||||
if (
|
||||
COMPOSE_RESERVED_TOP_LEVEL_KEYS.has(key) ||
|
||||
key.startsWith("x-") ||
|
||||
!isRecord(value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
services[key] = value;
|
||||
});
|
||||
|
||||
return services;
|
||||
};
|
||||
|
||||
const normalizeVersion = (
|
||||
composeData: ComposeRecord,
|
||||
services: ComposeRecord
|
||||
): string => {
|
||||
const versionValue = composeData.version;
|
||||
|
||||
if (typeof versionValue === "number") {
|
||||
const major = Math.trunc(versionValue).toString();
|
||||
if (["1", "2", "3"].includes(major)) {
|
||||
return major;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof versionValue === "string") {
|
||||
const normalized = versionValue.trim().toLowerCase();
|
||||
|
||||
if (
|
||||
["latest", "latest (spec)", "spec", "compose-spec"].includes(normalized)
|
||||
) {
|
||||
return "latest";
|
||||
}
|
||||
|
||||
const major = normalized.split(".")[0];
|
||||
if (["1", "2", "3"].includes(major)) {
|
||||
return major;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy Compose v1 does not have top-level "version" and "services".
|
||||
if (
|
||||
!composeData.version &&
|
||||
!composeData.services &&
|
||||
Object.keys(services).length
|
||||
) {
|
||||
return "1";
|
||||
}
|
||||
|
||||
return "latest";
|
||||
};
|
||||
|
||||
const getExistingServicesByName = (
|
||||
nodes: ComposeNodes
|
||||
): Record<string, IServiceNodeItem> => {
|
||||
const map: Record<string, IServiceNodeItem> = {};
|
||||
|
||||
Object.values(nodes).forEach((node) => {
|
||||
if (node.type === "SERVICE") {
|
||||
const serviceNode = node as IServiceNodeItem;
|
||||
const serviceName = serviceNode.canvasConfig.node_name;
|
||||
if (serviceName) {
|
||||
map[serviceName] = serviceNode;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
const getExistingVolumesByName = (
|
||||
nodes: ComposeNodes
|
||||
): Record<string, IVolumeNodeItem> => {
|
||||
const map: Record<string, IVolumeNodeItem> = {};
|
||||
|
||||
Object.values(nodes).forEach((node) => {
|
||||
if (node.type === "VOLUME") {
|
||||
const volumeNode = node as IVolumeNodeItem;
|
||||
const volumeName =
|
||||
volumeNode.canvasConfig.node_name || volumeNode.volumeConfig.name;
|
||||
|
||||
if (volumeName) {
|
||||
map[volumeName as string] = volumeNode;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
const getExistingNetworksByName = (
|
||||
networks: Record<string, INetworkNodeItem>
|
||||
): Record<string, INetworkNodeItem> => {
|
||||
const map: Record<string, INetworkNodeItem> = {};
|
||||
|
||||
Object.values(networks).forEach((networkNode) => {
|
||||
const networkName = networkNode.canvasConfig.node_name;
|
||||
if (networkName) {
|
||||
map[networkName] = networkNode;
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
export const composeToCanvasGraph = (
|
||||
composeInput: unknown,
|
||||
existingNodes: ComposeNodes = {},
|
||||
existingNetworks: Record<string, INetworkNodeItem> = {}
|
||||
): IComposeGraphResult => {
|
||||
const composeData = toRecord(composeInput);
|
||||
const composeServices = composeData.services
|
||||
? toRecord(composeData.services)
|
||||
: getLegacyV1Services(composeData);
|
||||
const composeVolumes = toRecord(composeData.volumes);
|
||||
const composeNetworks = toRecord(composeData.networks);
|
||||
const version = normalizeVersion(composeData, composeServices);
|
||||
|
||||
const libraries = flattenLibraries(nodeLibraries);
|
||||
const serviceLibrary = ensure(libraries.find((l) => l.type === "SERVICE"));
|
||||
const volumeLibrary = ensure(libraries.find((l) => l.type === "VOLUME"));
|
||||
|
||||
const existingServiceNodesByName = getExistingServicesByName(existingNodes);
|
||||
const existingVolumeNodesByName = getExistingVolumesByName(existingNodes);
|
||||
const existingNetworkNodesByName =
|
||||
getExistingNetworksByName(existingNetworks);
|
||||
|
||||
const nextNodes: ComposeNodes = {};
|
||||
const serviceNodesByName: Record<string, IServiceNodeItem> = {};
|
||||
const volumeNodesByName: Record<string, IVolumeNodeItem> = {};
|
||||
let autoPositionIndex = 0;
|
||||
|
||||
Object.entries(composeServices).forEach(([serviceName, rawConfig]) => {
|
||||
const existingNode = existingServiceNodesByName[serviceName];
|
||||
const node = {
|
||||
key: existingNode?.key ?? attachUUID("service"),
|
||||
type: "SERVICE",
|
||||
position:
|
||||
existingNode?.position ?? getDefaultNodePosition(autoPositionIndex++),
|
||||
inputs: existingNode?.inputs ?? [],
|
||||
outputs: existingNode?.outputs ?? [],
|
||||
canvasConfig: {
|
||||
node_name: serviceName,
|
||||
node_icon: existingNode?.canvasConfig.node_icon ?? ""
|
||||
},
|
||||
serviceConfig: toRecord(rawConfig)
|
||||
} as IServiceNodeItem;
|
||||
|
||||
const clientNode = getClientNodeItem(
|
||||
node as IServiceNodeItem,
|
||||
serviceLibrary
|
||||
) as IServiceNodeItem;
|
||||
|
||||
serviceNodesByName[serviceName] = clientNode;
|
||||
nextNodes[clientNode.key] = clientNode;
|
||||
});
|
||||
|
||||
Object.entries(composeVolumes).forEach(([volumeName, rawConfig]) => {
|
||||
const existingNode = existingVolumeNodesByName[volumeName];
|
||||
const nextVolumeConfig = {
|
||||
...toRecord(rawConfig),
|
||||
name:
|
||||
(toRecord(rawConfig).name as string | undefined) ??
|
||||
existingNode?.volumeConfig.name ??
|
||||
volumeName
|
||||
};
|
||||
const node = {
|
||||
key: existingNode?.key ?? attachUUID("volume"),
|
||||
type: "VOLUME",
|
||||
position:
|
||||
existingNode?.position ?? getDefaultNodePosition(autoPositionIndex++),
|
||||
inputs: existingNode?.inputs ?? [],
|
||||
outputs: existingNode?.outputs ?? [],
|
||||
canvasConfig: {
|
||||
node_name: volumeName,
|
||||
node_icon:
|
||||
existingNode?.canvasConfig.node_icon ?? iconByNodeType.volume ?? ""
|
||||
},
|
||||
volumeConfig: nextVolumeConfig
|
||||
} as IVolumeNodeItem;
|
||||
|
||||
const clientNode = getClientNodeItem(
|
||||
node as unknown as IServiceNodeItem,
|
||||
volumeLibrary
|
||||
) as unknown as IVolumeNodeItem;
|
||||
|
||||
volumeNodesByName[volumeName] = clientNode;
|
||||
nextNodes[clientNode.key] = clientNode;
|
||||
});
|
||||
|
||||
const connections: Array<[string, string]> = [];
|
||||
const seenConnections = new Set<string>();
|
||||
|
||||
const addConnection = (source: string, target: string) => {
|
||||
const connectionKey = `${source}->${target}`;
|
||||
if (seenConnections.has(connectionKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seenConnections.add(connectionKey);
|
||||
connections.push([source, target]);
|
||||
};
|
||||
|
||||
Object.entries(serviceNodesByName).forEach(([serviceName, serviceNode]) => {
|
||||
const sourceService = serviceNodesByName[serviceName];
|
||||
const dependsOnNames = getDependsOnServiceNames(
|
||||
sourceService.serviceConfig.depends_on
|
||||
);
|
||||
|
||||
dependsOnNames.forEach((dependsOnName) => {
|
||||
const targetService = serviceNodesByName[dependsOnName];
|
||||
if (targetService) {
|
||||
addConnection(sourceService.key, targetService.key);
|
||||
}
|
||||
});
|
||||
|
||||
const volumeNames = getServiceVolumeNames(
|
||||
sourceService.serviceConfig.volumes
|
||||
);
|
||||
volumeNames.forEach((volumeName) => {
|
||||
const sourceVolume = volumeNodesByName[volumeName];
|
||||
if (sourceVolume) {
|
||||
addConnection(sourceVolume.key, serviceNode.key);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const nextNetworks: Record<string, INetworkNodeItem> = {};
|
||||
Object.entries(composeNetworks).forEach(([networkName, rawConfig]) => {
|
||||
const existingNode = existingNetworkNodesByName[networkName];
|
||||
const nextNetworkConfig = {
|
||||
...toRecord(rawConfig),
|
||||
name:
|
||||
(toRecord(rawConfig).name as string | undefined) ??
|
||||
existingNode?.networkConfig.name ??
|
||||
networkName
|
||||
};
|
||||
|
||||
const networkNode: INetworkNodeItem = {
|
||||
key: existingNode?.key ?? attachUUID("network"),
|
||||
type: "NETWORK",
|
||||
position: existingNode?.position ?? { left: 0, top: 0 },
|
||||
inputs: existingNode?.inputs ?? [],
|
||||
outputs: existingNode?.outputs ?? [],
|
||||
canvasConfig: {
|
||||
node_name: networkName,
|
||||
node_icon:
|
||||
existingNode?.canvasConfig.node_icon ?? iconByNodeType.network ?? ""
|
||||
},
|
||||
networkConfig: nextNetworkConfig
|
||||
};
|
||||
|
||||
nextNetworks[networkNode.key] = networkNode;
|
||||
});
|
||||
|
||||
return {
|
||||
version,
|
||||
nodes: nextNodes,
|
||||
connections,
|
||||
networks: nextNetworks
|
||||
};
|
||||
};
|
||||
Loading…
Reference in New Issue