@dilipod/ui 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import { clsx } from 'clsx';
10
10
  import { twMerge } from 'tailwind-merge';
11
11
  import * as SheetPrimitive from '@radix-ui/react-dialog';
12
12
  import * as react_star from '@phosphor-icons/react';
13
- import { X, CaretDown, Circle, CaretLeft, DotsThree, CaretRight, Check, House, Info, WarningCircle, Play, Download, Folder, ArrowSquareOut, CircleNotch, File, FileVideo, Lightning, Plus, CheckCircle, PaperPlaneTilt, CaretUp, Eye, TreeStructure, Code, PencilSimple, WebhooksLogo, Copy, CloudArrowUp, CloudArrowDown, ArrowsClockwise, DownloadSimple, ClockCounterClockwise, FileImage, FilePdf, FileDoc, Question, Warning, Trash, Robot, Globe, GitBranch, Package, Timer } from '@phosphor-icons/react';
13
+ import { X, CaretDown, Circle, CaretLeft, DotsThree, CaretRight, Check, House, Info, WarningCircle, Play, Download, Folder, ArrowSquareOut, CircleNotch, File, FileVideo, Lightning, Plus, CheckCircle, PaperPlaneTilt, CaretUp, Eye, TreeStructure, Code, PencilSimple, WebhooksLogo, Copy, CloudArrowUp, CloudArrowDown, ArrowsClockwise, DownloadSimple, ClockCounterClockwise, ArrowsLeftRight, Minus, Pencil, FileImage, FilePdf, FileDoc, Question, Warning, Trash, Robot, Globe, GitBranch, Package, Timer } from '@phosphor-icons/react';
14
14
  import 'react-dom';
15
15
  import * as SwitchPrimitive from '@radix-ui/react-switch';
16
16
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
@@ -4852,6 +4852,55 @@ function defaultFormatDistance(date, options) {
4852
4852
  else result = `${diffDays} day${diffDays > 1 ? "s" : ""}`;
4853
4853
  return options?.addSuffix ? `${result} ago` : result;
4854
4854
  }
4855
+ function computeWorkflowDiff(stateA, stateB) {
4856
+ if (!stateA || !stateB) {
4857
+ return {
4858
+ blocksAdded: [],
4859
+ blocksRemoved: [],
4860
+ blocksModified: [],
4861
+ edgesAdded: 0,
4862
+ edgesRemoved: 0,
4863
+ summary: "Unable to compare - missing workflow data"
4864
+ };
4865
+ }
4866
+ const blocksA = stateA.blocks || {};
4867
+ const blocksB = stateB.blocks || {};
4868
+ const edgesA = stateA.edges || [];
4869
+ const edgesB = stateB.edges || [];
4870
+ const blockIdsA = new Set(Object.keys(blocksA));
4871
+ const blockIdsB = new Set(Object.keys(blocksB));
4872
+ const blocksAdded = [...blockIdsB].filter((id) => !blockIdsA.has(id));
4873
+ const blocksRemoved = [...blockIdsA].filter((id) => !blockIdsB.has(id));
4874
+ const blocksModified = [];
4875
+ for (const id of blockIdsA) {
4876
+ if (blockIdsB.has(id)) {
4877
+ const blockA = blocksA[id];
4878
+ const blockB = blocksB[id];
4879
+ if (JSON.stringify(blockA) !== JSON.stringify(blockB)) {
4880
+ blocksModified.push(id);
4881
+ }
4882
+ }
4883
+ }
4884
+ const edgeSignature = (e) => `${e.source || e.from}->${e.target || e.to}`;
4885
+ const edgeSigsA = new Set(edgesA.map(edgeSignature));
4886
+ const edgeSigsB = new Set(edgesB.map(edgeSignature));
4887
+ const edgesAdded = [...edgeSigsB].filter((sig) => !edgeSigsA.has(sig)).length;
4888
+ const edgesRemoved = [...edgeSigsA].filter((sig) => !edgeSigsB.has(sig)).length;
4889
+ const changes = [];
4890
+ if (blocksAdded.length > 0) changes.push(`+${blocksAdded.length} block${blocksAdded.length > 1 ? "s" : ""}`);
4891
+ if (blocksRemoved.length > 0) changes.push(`-${blocksRemoved.length} block${blocksRemoved.length > 1 ? "s" : ""}`);
4892
+ if (blocksModified.length > 0) changes.push(`~${blocksModified.length} modified`);
4893
+ if (edgesAdded > 0) changes.push(`+${edgesAdded} edge${edgesAdded > 1 ? "s" : ""}`);
4894
+ if (edgesRemoved > 0) changes.push(`-${edgesRemoved} edge${edgesRemoved > 1 ? "s" : ""}`);
4895
+ return {
4896
+ blocksAdded,
4897
+ blocksRemoved,
4898
+ blocksModified,
4899
+ edgesAdded,
4900
+ edgesRemoved,
4901
+ summary: changes.length > 0 ? changes.join(", ") : "No changes detected"
4902
+ };
4903
+ }
4855
4904
  function N8nWorkflowSummary({ workflow, showFlow = false }) {
4856
4905
  const nodes = workflow.nodes || [];
4857
4906
  const triggerNode = nodes.find(
@@ -5073,6 +5122,12 @@ function WorkflowViewer({
5073
5122
  const [pushingToSim, setPushingToSim] = useState(false);
5074
5123
  const [pullingFromSim, setPullingFromSim] = useState(false);
5075
5124
  const [switchingPlatform, setSwitchingPlatform] = useState(false);
5125
+ const [diffMode, setDiffMode] = useState(false);
5126
+ const [selectedBackupA, setSelectedBackupA] = useState(null);
5127
+ const [selectedBackupB, setSelectedBackupB] = useState(null);
5128
+ const [backupStateA, setBackupStateA] = useState(null);
5129
+ const [backupStateB, setBackupStateB] = useState(null);
5130
+ const [loadingDiff, setLoadingDiff] = useState(false);
5076
5131
  const [localPlatform, setLocalPlatform] = useState(platform);
5077
5132
  const [localIsActive, setLocalIsActive] = useState(isActive ?? true);
5078
5133
  const hasUnsavedChanges = localPlatform !== platform || localIsActive !== (isActive ?? true);
@@ -5275,6 +5330,42 @@ function WorkflowViewer({
5275
5330
  setViewMode("backups");
5276
5331
  loadBackups();
5277
5332
  }
5333
+ async function loadBackupForDiff(backupId, slot) {
5334
+ if (!workflowDefinitionId || !apiHandlers?.getBackupState) {
5335
+ return;
5336
+ }
5337
+ setLoadingDiff(true);
5338
+ try {
5339
+ const result = await apiHandlers.getBackupState(workflowDefinitionId, backupId);
5340
+ if (result.success && result.backup) {
5341
+ if (slot === "A") {
5342
+ setBackupStateA(result.backup.state);
5343
+ setSelectedBackupA(backupId);
5344
+ } else {
5345
+ setBackupStateB(result.backup.state);
5346
+ setSelectedBackupB(backupId);
5347
+ }
5348
+ }
5349
+ } catch {
5350
+ console.error("Failed to load backup for diff");
5351
+ } finally {
5352
+ setLoadingDiff(false);
5353
+ }
5354
+ }
5355
+ function startDiffMode() {
5356
+ setDiffMode(true);
5357
+ setSelectedBackupA(null);
5358
+ setSelectedBackupB(null);
5359
+ setBackupStateA(null);
5360
+ setBackupStateB(null);
5361
+ }
5362
+ function exitDiffMode() {
5363
+ setDiffMode(false);
5364
+ setSelectedBackupA(null);
5365
+ setSelectedBackupB(null);
5366
+ setBackupStateA(null);
5367
+ setBackupStateB(null);
5368
+ }
5278
5369
  function openInSimStudio() {
5279
5370
  if (simStudioUrl && simWorkflowId) {
5280
5371
  window.open(`${simStudioUrl}/w/${simWorkflowId}`, "_blank");
@@ -5655,14 +5746,15 @@ function WorkflowViewer({
5655
5746
  children: pushingToSim ? "Pushing..." : "Push to Sim"
5656
5747
  }
5657
5748
  ),
5658
- simWorkflowId && apiHandlers?.pullFromSim && /* @__PURE__ */ jsx(
5749
+ apiHandlers?.pullFromSim && /* @__PURE__ */ jsx(
5659
5750
  Button,
5660
5751
  {
5661
5752
  onClick: pullFromSim,
5662
- disabled: pullingFromSim,
5753
+ disabled: pullingFromSim || !simWorkflowId,
5663
5754
  variant: "outline",
5664
5755
  size: "sm",
5665
5756
  icon: /* @__PURE__ */ jsx(CloudArrowDown, { size: 16 }),
5757
+ title: !simWorkflowId ? "No Sim workflow linked yet. Push to Sim first." : void 0,
5666
5758
  children: pullingFromSim ? "Pulling..." : "Pull from Sim"
5667
5759
  }
5668
5760
  ),
@@ -5730,41 +5822,194 @@ function WorkflowViewer({
5730
5822
  /* @__PURE__ */ jsx("pre", { className: "p-4 bg-[var(--black)] text-gray-100 text-xs overflow-auto max-h-[500px] font-mono", children: JSON.stringify(workflow, null, 2) })
5731
5823
  ] }) : viewMode === "flow" ? /* @__PURE__ */ jsx(Suspense, { fallback: loadingComponent || DefaultLoading, children: /* @__PURE__ */ jsx(WorkflowFlow2, { workflow, height: 380, platform }) }) : viewMode === "backups" ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5732
5824
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
5733
- /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: "Backup History" }),
5734
- /* @__PURE__ */ jsx(
5735
- Button,
5736
- {
5737
- onClick: () => setViewMode("summary"),
5738
- variant: "outline",
5739
- size: "sm",
5740
- children: "Back to Summary"
5741
- }
5742
- )
5825
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: diffMode ? "Compare Versions" : "Backup History" }),
5826
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5827
+ !diffMode && backups.length >= 2 && apiHandlers?.getBackupState && /* @__PURE__ */ jsx(
5828
+ Button,
5829
+ {
5830
+ onClick: startDiffMode,
5831
+ variant: "outline",
5832
+ size: "sm",
5833
+ icon: /* @__PURE__ */ jsx(ArrowsLeftRight, { size: 14 }),
5834
+ children: "Compare"
5835
+ }
5836
+ ),
5837
+ diffMode && /* @__PURE__ */ jsx(
5838
+ Button,
5839
+ {
5840
+ onClick: exitDiffMode,
5841
+ variant: "outline",
5842
+ size: "sm",
5843
+ icon: /* @__PURE__ */ jsx(X, { size: 14 }),
5844
+ children: "Cancel"
5845
+ }
5846
+ ),
5847
+ /* @__PURE__ */ jsx(
5848
+ Button,
5849
+ {
5850
+ onClick: () => {
5851
+ exitDiffMode();
5852
+ setViewMode("summary");
5853
+ },
5854
+ variant: "outline",
5855
+ size: "sm",
5856
+ children: "Back to Summary"
5857
+ }
5858
+ )
5859
+ ] })
5743
5860
  ] }),
5744
- loadingBackups ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-muted-foreground", children: "Loading backups..." }) : backups.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-muted-foreground", children: [
5861
+ diffMode && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5862
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
5863
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-muted/50 rounded border border-border", children: [
5864
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: "From (older)" }),
5865
+ /* @__PURE__ */ jsxs(
5866
+ Select,
5867
+ {
5868
+ value: selectedBackupA || "",
5869
+ onChange: (e) => loadBackupForDiff(e.target.value, "A"),
5870
+ disabled: loadingDiff,
5871
+ className: "w-full",
5872
+ children: [
5873
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select version..." }),
5874
+ backups.map((b) => /* @__PURE__ */ jsxs("option", { value: b.id, disabled: b.id === selectedBackupB, children: [
5875
+ "v",
5876
+ b.version,
5877
+ " - ",
5878
+ b.versionLabel || b.workflowName
5879
+ ] }, b.id))
5880
+ ]
5881
+ }
5882
+ )
5883
+ ] }),
5884
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-muted/50 rounded border border-border", children: [
5885
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: "To (newer)" }),
5886
+ /* @__PURE__ */ jsxs(
5887
+ Select,
5888
+ {
5889
+ value: selectedBackupB || "",
5890
+ onChange: (e) => loadBackupForDiff(e.target.value, "B"),
5891
+ disabled: loadingDiff,
5892
+ className: "w-full",
5893
+ children: [
5894
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select version..." }),
5895
+ backups.map((b) => /* @__PURE__ */ jsxs("option", { value: b.id, disabled: b.id === selectedBackupA, children: [
5896
+ "v",
5897
+ b.version,
5898
+ " - ",
5899
+ b.versionLabel || b.workflowName
5900
+ ] }, b.id))
5901
+ ]
5902
+ }
5903
+ )
5904
+ ] })
5905
+ ] }),
5906
+ loadingDiff ? /* @__PURE__ */ jsx("div", { className: "py-4 text-center text-muted-foreground", children: "Loading versions..." }) : backupStateA && backupStateB ? (() => {
5907
+ const diff = computeWorkflowDiff(backupStateA, backupStateB);
5908
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5909
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-muted/30 rounded border border-border", children: [
5910
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-2", children: "Changes Summary" }),
5911
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: diff.summary })
5912
+ ] }),
5913
+ diff.blocksAdded.length > 0 && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-green-500/10 rounded border border-green-500/30", children: [
5914
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-green-600 dark:text-green-400 mb-2", children: [
5915
+ /* @__PURE__ */ jsx(Plus, { size: 14 }),
5916
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
5917
+ "Blocks Added (",
5918
+ diff.blocksAdded.length,
5919
+ ")"
5920
+ ] })
5921
+ ] }),
5922
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: diff.blocksAdded.map((id) => {
5923
+ const block = backupStateB?.blocks?.[id];
5924
+ return /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", children: block?.name || id }, id);
5925
+ }) })
5926
+ ] }),
5927
+ diff.blocksRemoved.length > 0 && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-red-500/10 rounded border border-red-500/30", children: [
5928
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400 mb-2", children: [
5929
+ /* @__PURE__ */ jsx(Minus, { size: 14 }),
5930
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
5931
+ "Blocks Removed (",
5932
+ diff.blocksRemoved.length,
5933
+ ")"
5934
+ ] })
5935
+ ] }),
5936
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: diff.blocksRemoved.map((id) => {
5937
+ const block = backupStateA?.blocks?.[id];
5938
+ return /* @__PURE__ */ jsx(Badge, { variant: "error", size: "sm", children: block?.name || id }, id);
5939
+ }) })
5940
+ ] }),
5941
+ diff.blocksModified.length > 0 && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-amber-500/10 rounded border border-amber-500/30", children: [
5942
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-amber-600 dark:text-amber-400 mb-2", children: [
5943
+ /* @__PURE__ */ jsx(Pencil, { size: 14 }),
5944
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
5945
+ "Blocks Modified (",
5946
+ diff.blocksModified.length,
5947
+ ")"
5948
+ ] })
5949
+ ] }),
5950
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: diff.blocksModified.map((id) => {
5951
+ const block = backupStateB?.blocks?.[id];
5952
+ return /* @__PURE__ */ jsx(Badge, { variant: "warning", size: "sm", children: block?.name || id }, id);
5953
+ }) })
5954
+ ] }),
5955
+ (diff.edgesAdded > 0 || diff.edgesRemoved > 0) && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-muted/30 rounded border border-border", children: [
5956
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Connection Changes" }),
5957
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 text-sm", children: [
5958
+ diff.edgesAdded > 0 && /* @__PURE__ */ jsxs("span", { className: "text-green-600 dark:text-green-400", children: [
5959
+ "+",
5960
+ diff.edgesAdded,
5961
+ " added"
5962
+ ] }),
5963
+ diff.edgesRemoved > 0 && /* @__PURE__ */ jsxs("span", { className: "text-red-600 dark:text-red-400", children: [
5964
+ "-",
5965
+ diff.edgesRemoved,
5966
+ " removed"
5967
+ ] })
5968
+ ] })
5969
+ ] })
5970
+ ] });
5971
+ })() : selectedBackupA || selectedBackupB ? /* @__PURE__ */ jsx("div", { className: "py-4 text-center text-muted-foreground text-sm", children: "Select both versions to compare" }) : null
5972
+ ] }),
5973
+ !diffMode && /* @__PURE__ */ jsx(Fragment, { children: loadingBackups ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-muted-foreground", children: "Loading backups..." }) : backups.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-muted-foreground", children: [
5745
5974
  /* @__PURE__ */ jsx(ClockCounterClockwise, { size: 32, className: "mx-auto mb-2 opacity-50" }),
5746
5975
  /* @__PURE__ */ jsx("p", { children: "No backups yet" }),
5747
5976
  /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: 'Click "Export from Sim" to create a backup' })
5748
- ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: backups.map((backup) => /* @__PURE__ */ jsx(
5977
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: backups.map((backup, index) => /* @__PURE__ */ jsxs(
5749
5978
  "div",
5750
5979
  {
5751
5980
  className: "flex items-center justify-between p-3 bg-muted/50 rounded border border-border",
5752
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5753
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center w-8 h-8 rounded bg-primary/10 text-primary text-sm font-semibold", children: [
5754
- "v",
5755
- backup.version
5756
- ] }),
5757
- /* @__PURE__ */ jsxs("div", { children: [
5758
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: backup.versionLabel || backup.workflowName }),
5759
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
5760
- formatDistance(new Date(backup.exportedAt), { addSuffix: true }),
5761
- backup.isDeployed && /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", className: "ml-2", children: "Deployed" })
5981
+ children: [
5982
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5983
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center w-8 h-8 rounded bg-primary/10 text-primary text-sm font-semibold", children: [
5984
+ "v",
5985
+ backup.version
5986
+ ] }),
5987
+ /* @__PURE__ */ jsxs("div", { children: [
5988
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: backup.versionLabel || backup.workflowName }),
5989
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
5990
+ formatDistance(new Date(backup.exportedAt), { addSuffix: true }),
5991
+ backup.isDeployed && /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", className: "ml-2", children: "Deployed" })
5992
+ ] })
5762
5993
  ] })
5763
- ] })
5764
- ] })
5994
+ ] }),
5995
+ index < backups.length - 1 && apiHandlers?.getBackupState && /* @__PURE__ */ jsx(
5996
+ Button,
5997
+ {
5998
+ variant: "ghost",
5999
+ size: "sm",
6000
+ onClick: () => {
6001
+ startDiffMode();
6002
+ loadBackupForDiff(backups[index + 1].id, "A");
6003
+ loadBackupForDiff(backup.id, "B");
6004
+ },
6005
+ icon: /* @__PURE__ */ jsx(ArrowsLeftRight, { size: 14 }),
6006
+ children: "Diff"
6007
+ }
6008
+ )
6009
+ ]
5765
6010
  },
5766
6011
  backup.id
5767
- )) })
6012
+ )) }) })
5768
6013
  ] }) : platform === "n8n" ? /* @__PURE__ */ jsx(N8nWorkflowSummary, { workflow, showFlow: false }) : /* @__PURE__ */ jsx(SimWorkflowSummary, { workflow }) })
5769
6014
  ] })
5770
6015
  ] });