@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import pixelmatch from 'pixelmatch';
|
|
2
|
+
import { PNG } from 'pngjs';
|
|
3
|
+
import {
|
|
4
|
+
BRAND,
|
|
5
|
+
DEFAULTS,
|
|
6
|
+
type Screenshot,
|
|
7
|
+
type DiffResult,
|
|
8
|
+
type BoundingBox,
|
|
9
|
+
} from '../core/index.js';
|
|
10
|
+
import { ServiceError, Timer } from './utils.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for diff comparison
|
|
14
|
+
*/
|
|
15
|
+
export interface DiffOptions {
|
|
16
|
+
/** Percentage threshold for matching (0-100). Default: 5 */
|
|
17
|
+
threshold?: number;
|
|
18
|
+
|
|
19
|
+
/** Color difference sensitivity (0-1). Lower = more sensitive. Default: 0.1 */
|
|
20
|
+
colorThreshold?: number;
|
|
21
|
+
|
|
22
|
+
/** Include anti-aliased pixels in comparison. Default: false */
|
|
23
|
+
includeAA?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Diff engine for comparing screenshots.
|
|
28
|
+
*/
|
|
29
|
+
export class DiffEngine {
|
|
30
|
+
private readonly defaultThreshold: number;
|
|
31
|
+
|
|
32
|
+
constructor(defaultThreshold: number = DEFAULTS.diffThreshold) {
|
|
33
|
+
this.defaultThreshold = defaultThreshold;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compare two screenshots and return diff result
|
|
38
|
+
*/
|
|
39
|
+
compare(
|
|
40
|
+
current: Screenshot,
|
|
41
|
+
baseline: Screenshot,
|
|
42
|
+
options: DiffOptions = {}
|
|
43
|
+
): DiffResult {
|
|
44
|
+
const timer = new Timer();
|
|
45
|
+
|
|
46
|
+
// Parse PNG images
|
|
47
|
+
const img1 = PNG.sync.read(current.data);
|
|
48
|
+
const img2 = PNG.sync.read(baseline.data);
|
|
49
|
+
|
|
50
|
+
// Validate dimensions match
|
|
51
|
+
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
52
|
+
throw new DiffError(
|
|
53
|
+
`Dimension mismatch: current is ${img1.width}x${img1.height}, baseline is ${img2.width}x${img2.height}`,
|
|
54
|
+
'DIMENSION_MISMATCH',
|
|
55
|
+
'Ensure both screenshots use the same viewport size'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { width, height } = img1;
|
|
60
|
+
const totalPixels = width * height;
|
|
61
|
+
|
|
62
|
+
// Create diff image
|
|
63
|
+
const diffPng = new PNG({ width, height });
|
|
64
|
+
|
|
65
|
+
// Run pixelmatch comparison
|
|
66
|
+
const diffPixelCount = pixelmatch(
|
|
67
|
+
img1.data,
|
|
68
|
+
img2.data,
|
|
69
|
+
diffPng.data,
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
{
|
|
73
|
+
threshold: options.colorThreshold ?? 0.1,
|
|
74
|
+
includeAA: options.includeAA ?? false,
|
|
75
|
+
alpha: 0.1,
|
|
76
|
+
diffColor: [255, 0, 0], // Red for differences
|
|
77
|
+
diffColorAlt: [0, 255, 0], // Green for anti-aliased
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Calculate diff percentage
|
|
82
|
+
const diffPercentage = (diffPixelCount / totalPixels) * 100;
|
|
83
|
+
const threshold = options.threshold ?? this.defaultThreshold;
|
|
84
|
+
|
|
85
|
+
// Find changed regions
|
|
86
|
+
const changedRegions = this.findChangedRegions(diffPng);
|
|
87
|
+
|
|
88
|
+
// Only include diff image if there are differences
|
|
89
|
+
const diffImage = diffPixelCount > 0 ? PNG.sync.write(diffPng) : undefined;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
matches: diffPercentage <= threshold,
|
|
93
|
+
diffPercentage: Math.round(diffPercentage * 100) / 100,
|
|
94
|
+
diffPixelCount,
|
|
95
|
+
totalPixels,
|
|
96
|
+
diffImage: diffImage ? Buffer.from(diffImage) : undefined,
|
|
97
|
+
changedRegions,
|
|
98
|
+
diffTimeMs: timer.elapsed(),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Quick check if two screenshots are identical (by hash)
|
|
104
|
+
*/
|
|
105
|
+
areIdentical(current: Screenshot, baseline: Screenshot): boolean {
|
|
106
|
+
return current.hash === baseline.hash;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Find bounding boxes of changed regions in the diff image
|
|
111
|
+
*/
|
|
112
|
+
private findChangedRegions(diffPng: PNG): BoundingBox[] {
|
|
113
|
+
const { width, height, data } = diffPng;
|
|
114
|
+
const regions: BoundingBox[] = [];
|
|
115
|
+
const visited = new Set<number>();
|
|
116
|
+
|
|
117
|
+
// Scan for red pixels (differences)
|
|
118
|
+
for (let y = 0; y < height; y++) {
|
|
119
|
+
for (let x = 0; x < width; x++) {
|
|
120
|
+
const idx = (y * width + x) * 4;
|
|
121
|
+
const pixelKey = y * width + x;
|
|
122
|
+
|
|
123
|
+
// Check if this is a diff pixel (red channel > 200) and not visited
|
|
124
|
+
if (data[idx] > 200 && !visited.has(pixelKey)) {
|
|
125
|
+
// Flood fill to find region bounds
|
|
126
|
+
const region = this.floodFillBounds(diffPng, x, y, visited);
|
|
127
|
+
if (region) {
|
|
128
|
+
regions.push(region);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Merge overlapping regions
|
|
135
|
+
return this.mergeOverlappingRegions(regions);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Flood fill to find bounds of a contiguous changed region
|
|
140
|
+
*/
|
|
141
|
+
private floodFillBounds(
|
|
142
|
+
diffPng: PNG,
|
|
143
|
+
startX: number,
|
|
144
|
+
startY: number,
|
|
145
|
+
visited: Set<number>
|
|
146
|
+
): BoundingBox | null {
|
|
147
|
+
const { width, height, data } = diffPng;
|
|
148
|
+
const stack: Array<[number, number]> = [[startX, startY]];
|
|
149
|
+
|
|
150
|
+
let minX = startX;
|
|
151
|
+
let maxX = startX;
|
|
152
|
+
let minY = startY;
|
|
153
|
+
let maxY = startY;
|
|
154
|
+
let pixelCount = 0;
|
|
155
|
+
|
|
156
|
+
while (stack.length > 0) {
|
|
157
|
+
const [x, y] = stack.pop()!;
|
|
158
|
+
const pixelKey = y * width + x;
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
x < 0 ||
|
|
162
|
+
x >= width ||
|
|
163
|
+
y < 0 ||
|
|
164
|
+
y >= height ||
|
|
165
|
+
visited.has(pixelKey)
|
|
166
|
+
) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const idx = (y * width + x) * 4;
|
|
171
|
+
|
|
172
|
+
// Check if this is a diff pixel
|
|
173
|
+
if (data[idx] <= 200) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
visited.add(pixelKey);
|
|
178
|
+
pixelCount++;
|
|
179
|
+
|
|
180
|
+
// Update bounds
|
|
181
|
+
minX = Math.min(minX, x);
|
|
182
|
+
maxX = Math.max(maxX, x);
|
|
183
|
+
minY = Math.min(minY, y);
|
|
184
|
+
maxY = Math.max(maxY, y);
|
|
185
|
+
|
|
186
|
+
// Add neighbors (4-connectivity)
|
|
187
|
+
stack.push([x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Filter out tiny regions (noise)
|
|
191
|
+
if (pixelCount < 10) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
x: minX,
|
|
197
|
+
y: minY,
|
|
198
|
+
width: maxX - minX + 1,
|
|
199
|
+
height: maxY - minY + 1,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Merge overlapping bounding boxes
|
|
205
|
+
*/
|
|
206
|
+
private mergeOverlappingRegions(regions: BoundingBox[]): BoundingBox[] {
|
|
207
|
+
if (regions.length <= 1) {
|
|
208
|
+
return regions;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Sort by x coordinate
|
|
212
|
+
const sorted = [...regions].sort((a, b) => a.x - b.x);
|
|
213
|
+
const merged: BoundingBox[] = [];
|
|
214
|
+
|
|
215
|
+
let current = sorted[0];
|
|
216
|
+
|
|
217
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
218
|
+
const next = sorted[i];
|
|
219
|
+
|
|
220
|
+
// Check if regions overlap or are adjacent (with 10px margin)
|
|
221
|
+
if (this.regionsOverlap(current, next, 10)) {
|
|
222
|
+
// Merge into current
|
|
223
|
+
current = this.mergeBoxes(current, next);
|
|
224
|
+
} else {
|
|
225
|
+
merged.push(current);
|
|
226
|
+
current = next;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
merged.push(current);
|
|
231
|
+
return merged;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if two bounding boxes overlap (with margin)
|
|
236
|
+
*/
|
|
237
|
+
private regionsOverlap(a: BoundingBox, b: BoundingBox, margin: number): boolean {
|
|
238
|
+
return !(
|
|
239
|
+
a.x + a.width + margin < b.x ||
|
|
240
|
+
b.x + b.width + margin < a.x ||
|
|
241
|
+
a.y + a.height + margin < b.y ||
|
|
242
|
+
b.y + b.height + margin < a.y
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Merge two bounding boxes
|
|
248
|
+
*/
|
|
249
|
+
private mergeBoxes(a: BoundingBox, b: BoundingBox): BoundingBox {
|
|
250
|
+
const minX = Math.min(a.x, b.x);
|
|
251
|
+
const minY = Math.min(a.y, b.y);
|
|
252
|
+
const maxX = Math.max(a.x + a.width, b.x + b.width);
|
|
253
|
+
const maxY = Math.max(a.y + a.height, b.y + b.height);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
x: minX,
|
|
257
|
+
y: minY,
|
|
258
|
+
width: maxX - minX,
|
|
259
|
+
height: maxY - minY,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Error class for diff errors
|
|
266
|
+
*/
|
|
267
|
+
export class DiffError extends ServiceError {
|
|
268
|
+
constructor(message: string, code: string, suggestion?: string) {
|
|
269
|
+
super(message, code, suggestion);
|
|
270
|
+
this.name = `${BRAND.name}DiffError`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Create a diff engine
|
|
276
|
+
*/
|
|
277
|
+
export function createDiffEngine(defaultThreshold?: number): DiffEngine {
|
|
278
|
+
return new DiffEngine(defaultThreshold);
|
|
279
|
+
}
|