@jjlmoya/utils-diy 1.1.0

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.
Files changed (135) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +110 -0
  3. package/src/category/i18n/es.ts +110 -0
  4. package/src/category/i18n/fr.ts +101 -0
  5. package/src/category/index.ts +25 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +57 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/balusterCalculator/bibliography.astro +14 -0
  25. package/src/tool/balusterCalculator/component.astro +605 -0
  26. package/src/tool/balusterCalculator/i18n/en.ts +264 -0
  27. package/src/tool/balusterCalculator/i18n/es.ts +264 -0
  28. package/src/tool/balusterCalculator/i18n/fr.ts +264 -0
  29. package/src/tool/balusterCalculator/index.ts +33 -0
  30. package/src/tool/balusterCalculator/seo.astro +15 -0
  31. package/src/tool/balusterCalculator/ui.ts +15 -0
  32. package/src/tool/clayCalculator/bibliography.astro +14 -0
  33. package/src/tool/clayCalculator/component.astro +731 -0
  34. package/src/tool/clayCalculator/i18n/en.ts +183 -0
  35. package/src/tool/clayCalculator/i18n/es.ts +183 -0
  36. package/src/tool/clayCalculator/i18n/fr.ts +183 -0
  37. package/src/tool/clayCalculator/index.ts +33 -0
  38. package/src/tool/clayCalculator/seo.astro +15 -0
  39. package/src/tool/clayCalculator/ui.ts +19 -0
  40. package/src/tool/concreteCalculator/bibliography.astro +13 -0
  41. package/src/tool/concreteCalculator/component.astro +1089 -0
  42. package/src/tool/concreteCalculator/i18n/en.ts +201 -0
  43. package/src/tool/concreteCalculator/i18n/es.ts +201 -0
  44. package/src/tool/concreteCalculator/i18n/fr.ts +201 -0
  45. package/src/tool/concreteCalculator/index.ts +28 -0
  46. package/src/tool/concreteCalculator/seo.astro +14 -0
  47. package/src/tool/concreteCalculator/ui.ts +35 -0
  48. package/src/tool/cutOptimizer/bibliography.astro +13 -0
  49. package/src/tool/cutOptimizer/component.astro +825 -0
  50. package/src/tool/cutOptimizer/i18n/en.ts +178 -0
  51. package/src/tool/cutOptimizer/i18n/es.ts +178 -0
  52. package/src/tool/cutOptimizer/i18n/fr.ts +178 -0
  53. package/src/tool/cutOptimizer/index.ts +28 -0
  54. package/src/tool/cutOptimizer/optimizer.ts +199 -0
  55. package/src/tool/cutOptimizer/seo.astro +14 -0
  56. package/src/tool/cutOptimizer/ui.ts +25 -0
  57. package/src/tool/drillCalculator/bibliography.astro +13 -0
  58. package/src/tool/drillCalculator/component.astro +1210 -0
  59. package/src/tool/drillCalculator/engine.ts +63 -0
  60. package/src/tool/drillCalculator/i18n/en.ts +137 -0
  61. package/src/tool/drillCalculator/i18n/es.ts +137 -0
  62. package/src/tool/drillCalculator/i18n/fr.ts +137 -0
  63. package/src/tool/drillCalculator/index.ts +28 -0
  64. package/src/tool/drillCalculator/seo.astro +14 -0
  65. package/src/tool/drillCalculator/ui.ts +41 -0
  66. package/src/tool/drillSharpener/bibliography.astro +13 -0
  67. package/src/tool/drillSharpener/component.astro +712 -0
  68. package/src/tool/drillSharpener/engine.ts +42 -0
  69. package/src/tool/drillSharpener/i18n/en.ts +121 -0
  70. package/src/tool/drillSharpener/i18n/es.ts +121 -0
  71. package/src/tool/drillSharpener/i18n/fr.ts +121 -0
  72. package/src/tool/drillSharpener/index.ts +31 -0
  73. package/src/tool/drillSharpener/seo.astro +14 -0
  74. package/src/tool/drillSharpener/ui.ts +53 -0
  75. package/src/tool/epoxyCalculator/bibliography.astro +14 -0
  76. package/src/tool/epoxyCalculator/component.astro +888 -0
  77. package/src/tool/epoxyCalculator/i18n/en.ts +195 -0
  78. package/src/tool/epoxyCalculator/i18n/es.ts +195 -0
  79. package/src/tool/epoxyCalculator/i18n/fr.ts +195 -0
  80. package/src/tool/epoxyCalculator/index.ts +33 -0
  81. package/src/tool/epoxyCalculator/seo.astro +15 -0
  82. package/src/tool/epoxyCalculator/ui.ts +22 -0
  83. package/src/tool/furnitureFit/bibliography.astro +13 -0
  84. package/src/tool/furnitureFit/component.astro +552 -0
  85. package/src/tool/furnitureFit/engine.ts +51 -0
  86. package/src/tool/furnitureFit/i18n/en.ts +152 -0
  87. package/src/tool/furnitureFit/i18n/es.ts +152 -0
  88. package/src/tool/furnitureFit/i18n/fr.ts +152 -0
  89. package/src/tool/furnitureFit/index.ts +28 -0
  90. package/src/tool/furnitureFit/seo.astro +14 -0
  91. package/src/tool/furnitureFit/ui.ts +21 -0
  92. package/src/tool/mortarCalculator/bibliography.astro +13 -0
  93. package/src/tool/mortarCalculator/component.astro +1139 -0
  94. package/src/tool/mortarCalculator/i18n/en.ts +219 -0
  95. package/src/tool/mortarCalculator/i18n/es.ts +205 -0
  96. package/src/tool/mortarCalculator/i18n/fr.ts +220 -0
  97. package/src/tool/mortarCalculator/index.ts +24 -0
  98. package/src/tool/mortarCalculator/seo.astro +14 -0
  99. package/src/tool/mortarCalculator/ui.ts +37 -0
  100. package/src/tool/passepartoutCalculator/bibliography.astro +13 -0
  101. package/src/tool/passepartoutCalculator/component.astro +741 -0
  102. package/src/tool/passepartoutCalculator/i18n/en.ts +178 -0
  103. package/src/tool/passepartoutCalculator/i18n/es.ts +178 -0
  104. package/src/tool/passepartoutCalculator/i18n/fr.ts +178 -0
  105. package/src/tool/passepartoutCalculator/index.ts +28 -0
  106. package/src/tool/passepartoutCalculator/seo.astro +14 -0
  107. package/src/tool/passepartoutCalculator/ui.ts +17 -0
  108. package/src/tool/stairCalculator/bibliography.astro +52 -0
  109. package/src/tool/stairCalculator/component.astro +766 -0
  110. package/src/tool/stairCalculator/engine.ts +128 -0
  111. package/src/tool/stairCalculator/i18n/en.ts +149 -0
  112. package/src/tool/stairCalculator/i18n/es.ts +149 -0
  113. package/src/tool/stairCalculator/i18n/fr.ts +149 -0
  114. package/src/tool/stairCalculator/index.ts +31 -0
  115. package/src/tool/stairCalculator/seo.astro +211 -0
  116. package/src/tool/stairCalculator/ui.ts +109 -0
  117. package/src/tool/thermalExpansionCalculator/bibliography.astro +13 -0
  118. package/src/tool/thermalExpansionCalculator/component.astro +771 -0
  119. package/src/tool/thermalExpansionCalculator/engine.ts +23 -0
  120. package/src/tool/thermalExpansionCalculator/i18n/en.ts +174 -0
  121. package/src/tool/thermalExpansionCalculator/i18n/es.ts +174 -0
  122. package/src/tool/thermalExpansionCalculator/i18n/fr.ts +174 -0
  123. package/src/tool/thermalExpansionCalculator/index.ts +28 -0
  124. package/src/tool/thermalExpansionCalculator/seo.astro +14 -0
  125. package/src/tool/thermalExpansionCalculator/ui.ts +41 -0
  126. package/src/tool/voltageDropCalculator/bibliography.astro +13 -0
  127. package/src/tool/voltageDropCalculator/component.astro +1022 -0
  128. package/src/tool/voltageDropCalculator/i18n/en.ts +155 -0
  129. package/src/tool/voltageDropCalculator/i18n/es.ts +155 -0
  130. package/src/tool/voltageDropCalculator/i18n/fr.ts +148 -0
  131. package/src/tool/voltageDropCalculator/index.ts +28 -0
  132. package/src/tool/voltageDropCalculator/seo.astro +14 -0
  133. package/src/tool/voltageDropCalculator/ui.ts +22 -0
  134. package/src/tools.ts +16 -0
  135. package/src/types.ts +72 -0
@@ -0,0 +1,199 @@
1
+ export interface CutRequest {
2
+ id: string;
3
+ length: number;
4
+ width: number;
5
+ quantity: number;
6
+ }
7
+
8
+ export interface Rect {
9
+ x: number;
10
+ y: number;
11
+ w: number;
12
+ h: number;
13
+ }
14
+
15
+ export interface OptimizedPiece {
16
+ stockId: number;
17
+ stockLength: number;
18
+ stockWidth?: number;
19
+ cuts: (number | Rect)[];
20
+ waste: number;
21
+ utilization: number;
22
+ }
23
+
24
+ export interface OptimizationResult {
25
+ totalStockUsed: number;
26
+ totalWaste: number;
27
+ totalCuts: number;
28
+ stockPieces: OptimizedPiece[];
29
+ }
30
+
31
+ interface BestFit {
32
+ areaFit: number;
33
+ shortSide: number;
34
+ index: number;
35
+ rotated: boolean;
36
+ rect: Rect;
37
+ }
38
+
39
+ export interface Co2DCtx {
40
+ stockLen: number;
41
+ stockWid: number;
42
+ kerf: number;
43
+ }
44
+
45
+ type FitResult = { areaFit: number; shortSide: number; rect: Rect } | null;
46
+
47
+ function fitDims(iw: number, ih: number, free: Rect, best: BestFit): FitResult {
48
+ if (iw > free.w || ih > free.h) return null;
49
+ const areaFit = free.w * free.h - iw * ih;
50
+ const shortSide = Math.min(Math.abs(free.w - iw), Math.abs(free.h - ih));
51
+ if (areaFit > best.areaFit) return null;
52
+ if (areaFit === best.areaFit && shortSide >= best.shortSide) return null;
53
+ return { areaFit, shortSide, rect: { x: free.x, y: free.y, w: iw, h: ih } };
54
+ }
55
+
56
+ function findBestFit(item: { w: number; h: number }, freeRects: Rect[]): BestFit {
57
+ const best: BestFit = { areaFit: Infinity, shortSide: Infinity, index: -1, rotated: false, rect: { x: 0, y: 0, w: 0, h: 0 } };
58
+ for (let i = 0; i < freeRects.length; i++) {
59
+ const fitN = fitDims(item.w, item.h, freeRects[i], best);
60
+ if (fitN) Object.assign(best, fitN, { index: i, rotated: false });
61
+ const fitR = fitDims(item.h, item.w, freeRects[i], best);
62
+ if (fitR) Object.assign(best, fitR, { index: i, rotated: true });
63
+ }
64
+ return best;
65
+ }
66
+
67
+ function splitFreeRect(free: Rect, placed: Rect, freeRects: Rect[], kerf: number): void {
68
+ const wRem = free.w - placed.w - kerf;
69
+ const hRem = free.h - placed.h - kerf;
70
+ if (wRem > 0) freeRects.push({ x: free.x + placed.w + kerf, y: free.y, w: wRem, h: placed.h });
71
+ if (hRem > 0) freeRects.push({ x: free.x, y: free.y + placed.h + kerf, w: free.w, h: hRem });
72
+ }
73
+
74
+ function placeItem(freeRects: Rect[], fit: BestFit, kerf: number): Rect {
75
+ const freeRect = freeRects[fit.index];
76
+ freeRects.splice(fit.index, 1);
77
+ splitFreeRect(freeRect, fit.rect, freeRects, kerf);
78
+ return fit.rect;
79
+ }
80
+
81
+ function tryFit2DExisting(
82
+ item: { w: number; h: number; id: string },
83
+ pieces: OptimizedPiece[],
84
+ freeRectsMap: Map<number, Rect[]>,
85
+ kerf: number
86
+ ): boolean {
87
+ for (let i = 0; i < pieces.length; i++) {
88
+ const freeRects = freeRectsMap.get(i)!;
89
+ const fit = findBestFit(item, freeRects);
90
+ if (fit.index !== -1) {
91
+ const placed = placeItem(freeRects, fit, kerf);
92
+ (pieces[i].cuts as Rect[]).push(placed);
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+
99
+ function tryFit2DNew(
100
+ item: { w: number; h: number },
101
+ pieces: OptimizedPiece[],
102
+ freeRectsMap: Map<number, Rect[]>,
103
+ ctx: Co2DCtx
104
+ ): void {
105
+ const fitsNormal = item.w <= ctx.stockLen && item.h <= ctx.stockWid;
106
+ const fitsRotated = item.h <= ctx.stockLen && item.w <= ctx.stockWid;
107
+ if (!fitsNormal && !fitsRotated) return;
108
+ const id = pieces.length;
109
+ const initial: Rect = { x: 0, y: 0, w: ctx.stockLen, h: ctx.stockWid };
110
+ freeRectsMap.set(id, [initial]);
111
+ const piece: OptimizedPiece = { stockId: id + 1, stockLength: ctx.stockLen, stockWidth: ctx.stockWid, cuts: [], waste: 0, utilization: 0 };
112
+ pieces.push(piece);
113
+ const rotated = !fitsNormal && fitsRotated;
114
+ const placed: Rect = { x: 0, y: 0, w: rotated ? item.h : item.w, h: rotated ? item.w : item.h };
115
+ (piece.cuts as Rect[]).push(placed);
116
+ const freeRects = freeRectsMap.get(id)!;
117
+ freeRects.splice(0, 1);
118
+ splitFreeRect(initial, placed, freeRects, ctx.kerf);
119
+ }
120
+
121
+ function expand1DItems(cuts: CutRequest[]): number[] {
122
+ const items: number[] = [];
123
+ for (const req of cuts) {
124
+ for (let i = 0; i < req.quantity; i++) items.push(req.length);
125
+ }
126
+ return items;
127
+ }
128
+
129
+ function find1DBestFit(pieces: OptimizedPiece[], cutLen: number, stockLen: number, kerf: number): number {
130
+ let bestIdx = -1;
131
+ let minWaste = Infinity;
132
+ for (let i = 0; i < pieces.length; i++) {
133
+ const curCuts = pieces[i].cuts as number[];
134
+ const used = curCuts.reduce((a, b) => a + b, 0) + (curCuts.length > 0 ? (curCuts.length - 1) * kerf : 0);
135
+ const rem = stockLen - used;
136
+ const space = curCuts.length > 0 ? kerf + cutLen : cutLen;
137
+ if (rem >= space) {
138
+ const potRem = rem - space;
139
+ if (potRem < minWaste) { minWaste = potRem; bestIdx = i; }
140
+ }
141
+ }
142
+ return bestIdx;
143
+ }
144
+
145
+ function compute1DWaste(pieces: OptimizedPiece[], stockLen: number, kerf: number): number {
146
+ let total = 0;
147
+ for (const piece of pieces) {
148
+ const cuts = piece.cuts as number[];
149
+ const kerfLoss = cuts.length > 1 ? (cuts.length - 1) * kerf : 0;
150
+ const used = cuts.reduce((a, b) => a + b, 0) + kerfLoss;
151
+ piece.waste = piece.stockLength - used;
152
+ piece.utilization = used / piece.stockLength;
153
+ total += piece.waste;
154
+ }
155
+ return total;
156
+ }
157
+
158
+ export function optimize1D(cuts: CutRequest[], stockLen: number, kerf: number): OptimizationResult {
159
+ const allCuts = expand1DItems(cuts);
160
+ allCuts.sort((a, b) => b - a);
161
+ const pieces: OptimizedPiece[] = [];
162
+ for (const cutLen of allCuts) {
163
+ if (cutLen > stockLen) continue;
164
+ const idx = find1DBestFit(pieces, cutLen, stockLen, kerf);
165
+ if (idx !== -1) {
166
+ (pieces[idx].cuts as number[]).push(cutLen);
167
+ } else {
168
+ pieces.push({ stockId: pieces.length + 1, stockLength: stockLen, cuts: [cutLen], waste: 0, utilization: 0 });
169
+ }
170
+ }
171
+ const totalWaste = compute1DWaste(pieces, stockLen, kerf);
172
+ return { totalStockUsed: pieces.length, totalWaste, totalCuts: allCuts.length, stockPieces: pieces };
173
+ }
174
+
175
+ export function optimize2D(cuts: CutRequest[], stockLen: number, stockWid: number, kerf: number): OptimizationResult {
176
+ const ctx: Co2DCtx = { stockLen, stockWid, kerf };
177
+ const items: Array<{ w: number; h: number; id: string }> = [];
178
+ for (const req of cuts) {
179
+ for (let i = 0; i < req.quantity; i++) items.push({ w: req.length, h: req.width, id: req.id });
180
+ }
181
+ items.sort((a, b) => b.w * b.h - a.w * a.h);
182
+ const pieces: OptimizedPiece[] = [];
183
+ const freeRectsMap: Map<number, Rect[]> = new Map();
184
+ for (const item of items) {
185
+ if (!tryFit2DExisting(item, pieces, freeRectsMap, kerf)) {
186
+ tryFit2DNew(item, pieces, freeRectsMap, ctx);
187
+ }
188
+ }
189
+ let totalWaste = 0;
190
+ for (const piece of pieces) {
191
+ const curCuts = piece.cuts as Rect[];
192
+ const usedArea = curCuts.reduce((acc, c) => acc + c.w * c.h, 0);
193
+ const totalArea = stockLen * stockWid;
194
+ piece.utilization = usedArea / totalArea;
195
+ piece.waste = totalArea - usedArea;
196
+ totalWaste += piece.waste;
197
+ }
198
+ return { totalStockUsed: pieces.length, totalCuts: items.length, totalWaste, stockPieces: pieces };
199
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { cutOptimizer } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+ const { locale = 'es' } = Astro.props;
10
+ const content = await cutOptimizer.i18n[locale]?.();
11
+ if (!content) return null;
12
+ ---
13
+
14
+ <SEORenderer content={{ locale: locale as string, sections: content.seo }} />
@@ -0,0 +1,25 @@
1
+ export interface CutOptimizerUI extends Record<string, string> {
2
+ modeLinearLabel: string;
3
+ modePanelLabel: string;
4
+ configTitle: string;
5
+ labelStockLength: string;
6
+ labelStockWidth: string;
7
+ labelKerf: string;
8
+ cutsTitle: string;
9
+ placeholderLength: string;
10
+ placeholderWidth: string;
11
+ placeholderQty: string;
12
+ btnRemoveTitle: string;
13
+ statStockUsed: string;
14
+ statTotalCuts: string;
15
+ statWaste: string;
16
+ statEfficiency: string;
17
+ vizTitle: string;
18
+ emptyState: string;
19
+ noValidCuts: string;
20
+ noValidPanels: string;
21
+ stockLabel: string;
22
+ panelLabel: string;
23
+ wasteLabel: string;
24
+ effLabel: string;
25
+ }
@@ -0,0 +1,13 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { drillCalculator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+ const { locale = 'es' } = Astro.props;
10
+ const content = await drillCalculator.i18n[locale]?.();
11
+ ---
12
+
13
+ {content && <SharedBibliography links={content.bibliography} />}