@jjlmoya/utils-home 1.25.0 → 1.26.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.
- package/package.json +1 -1
- package/src/entries.ts +4 -1
- package/src/index.ts +1 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/wallPaintingCalculator/bibliography.astro +14 -0
- package/src/tool/wallPaintingCalculator/bibliography.ts +14 -0
- package/src/tool/wallPaintingCalculator/component.astro +340 -0
- package/src/tool/wallPaintingCalculator/entry.ts +29 -0
- package/src/tool/wallPaintingCalculator/i18n/de.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/en.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/es.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/fr.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/id.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/it.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/ja.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/ko.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/nl.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/pl.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/pt.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/ru.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/sv.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/tr.ts +226 -0
- package/src/tool/wallPaintingCalculator/i18n/zh.ts +226 -0
- package/src/tool/wallPaintingCalculator/index.ts +9 -0
- package/src/tool/wallPaintingCalculator/logic.ts +27 -0
- package/src/tool/wallPaintingCalculator/seo.astro +15 -0
- package/src/tool/wallPaintingCalculator/ui.ts +42 -0
- package/src/tool/wallPaintingCalculator/wall-painting-calculator.css +326 -0
- package/src/tools.ts +2 -0
package/package.json
CHANGED
package/src/entries.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { wifiRangeSimulator } from './tool/wifiRangeSimulator/entry';
|
|
|
16
16
|
export type { WifiRangeSimulatorLocaleContent } from './tool/wifiRangeSimulator/entry';
|
|
17
17
|
export { acTonnageCalculator } from './tool/acTonnageCalculator/entry';
|
|
18
18
|
export type { AcTonnageCalculatorLocaleContent } from './tool/acTonnageCalculator/entry';
|
|
19
|
+
export { wallPaintingCalculator } from './tool/wallPaintingCalculator/entry';
|
|
20
|
+
export type { WallPaintingCalculatorLocaleContent } from './tool/wallPaintingCalculator/entry';
|
|
19
21
|
export { homeCategory } from './category';
|
|
20
22
|
import { dewPointCalculator } from './tool/dewPointCalculator/entry';
|
|
21
23
|
import { heatingComparator } from './tool/heatingComparator/entry';
|
|
@@ -26,4 +28,5 @@ import { solarCalculator } from './tool/solarCalculator/entry';
|
|
|
26
28
|
import { tariffComparator } from './tool/tariffComparator/entry';
|
|
27
29
|
import { wifiRangeSimulator } from './tool/wifiRangeSimulator/entry';
|
|
28
30
|
import { acTonnageCalculator } from './tool/acTonnageCalculator/entry';
|
|
29
|
-
|
|
31
|
+
import { wallPaintingCalculator } from './tool/wallPaintingCalculator/entry';
|
|
32
|
+
export const ALL_ENTRIES = [dewPointCalculator, heatingComparator, ledSavingCalculator, projectorCalculator, qrGenerator, solarCalculator, tariffComparator, wifiRangeSimulator, acTonnageCalculator, wallPaintingCalculator];
|
package/src/index.ts
CHANGED
|
@@ -24,4 +24,5 @@ export { DEW_POINT_CALCULATOR_TOOL } from './tool/dewPointCalculator';
|
|
|
24
24
|
export { LED_SAVING_CALCULATOR_TOOL } from './tool/ledSavingCalculator';
|
|
25
25
|
export { TARIFF_COMPARATOR_TOOL } from './tool/tariffComparator';
|
|
26
26
|
export { HEATING_COMPARATOR_TOOL } from './tool/heatingComparator';
|
|
27
|
+
export { WALL_PAINTING_CALCULATOR_TOOL } from './tool/wallPaintingCalculator';
|
|
27
28
|
|
|
@@ -17,8 +17,8 @@ describe('Locale Completeness Validation', () => {
|
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it('should have
|
|
21
|
-
expect(ALL_TOOLS.length).toBe(
|
|
20
|
+
it('should have 10 tools registered', () => {
|
|
21
|
+
expect(ALL_TOOLS.length).toBe(10);
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -4,8 +4,8 @@ import { homeCategory } from '../data';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 10 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(10);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('homeCategory should be defined', () => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { wallPaintingCalculator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await wallPaintingCalculator.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const bibliography = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Paint Coverage and Application Guide: Sherwin-Williams',
|
|
4
|
+
url: 'https://www.sherwin-williams.com/homeowners/how-to/paint-coverage-and-application',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
name: 'How Much Paint Do I Need: Benjamin Moore',
|
|
8
|
+
url: 'https://www.benjaminmoore.com/en-us/how-to/how-much-paint-do-i-need',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'Paint Calculator and Coverage Estimator: Dulux',
|
|
12
|
+
url: 'https://www.dulux.co.uk/en/diy/paint-calculator',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { WallPaintingCalculatorUI } from './ui';
|
|
3
|
+
import { calculateWallPainting, fmt2 } from './logic';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui = {} } = Astro.props;
|
|
10
|
+
const wUI = ui as WallPaintingCalculatorUI;
|
|
11
|
+
|
|
12
|
+
const M2_TO_FT2 = 10.7639;
|
|
13
|
+
const LITRE_TO_GALLON = 0.264172;
|
|
14
|
+
const M2PERL_TO_FT2PERGAL = 40.745;
|
|
15
|
+
|
|
16
|
+
const INITIAL_AREA = 30;
|
|
17
|
+
const INITIAL_YIELD = 12;
|
|
18
|
+
const INITIAL_COATS = 2;
|
|
19
|
+
const INITIAL_PRICE = 15;
|
|
20
|
+
const INITIAL_DILUTION = 5;
|
|
21
|
+
|
|
22
|
+
const initial = calculateWallPainting({
|
|
23
|
+
area: INITIAL_AREA,
|
|
24
|
+
yieldM2PerLitre: INITIAL_YIELD,
|
|
25
|
+
coats: INITIAL_COATS,
|
|
26
|
+
pricePerLitre: INITIAL_PRICE,
|
|
27
|
+
dilutionPercent: INITIAL_DILUTION,
|
|
28
|
+
});
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<div class="wall-wrapper">
|
|
32
|
+
<div class="wall-card"
|
|
33
|
+
data-label-area={wUI.labelArea}
|
|
34
|
+
data-label-area-ft={wUI.labelAreaFt}
|
|
35
|
+
data-unit-area={wUI.unitArea}
|
|
36
|
+
data-unit-area-ft={wUI.unitAreaFt}
|
|
37
|
+
data-label-yield={wUI.labelYield}
|
|
38
|
+
data-label-yield-ft={wUI.labelYieldFt}
|
|
39
|
+
data-unit-yield={wUI.unitYield}
|
|
40
|
+
data-unit-yield-ft={wUI.unitYieldFt}
|
|
41
|
+
data-label-price={wUI.labelPrice}
|
|
42
|
+
data-label-price-ft={wUI.labelPriceFt}
|
|
43
|
+
data-unit-price={wUI.unitPrice}
|
|
44
|
+
data-unit-price-gal={wUI.unitPriceGal}
|
|
45
|
+
data-label-paint={wUI.labelPaint}
|
|
46
|
+
data-label-paint-gal={wUI.labelPaintGal}
|
|
47
|
+
data-label-water={wUI.labelWater}
|
|
48
|
+
data-label-water-gal={wUI.labelWaterGal}
|
|
49
|
+
data-label-total={wUI.labelTotalVolume}
|
|
50
|
+
data-label-total-gal={wUI.labelTotalVolumeGal}
|
|
51
|
+
data-currency-sign={wUI.currencySign}
|
|
52
|
+
data-curr-usd={wUI.btnCurrUSD}
|
|
53
|
+
data-curr-eur={wUI.btnCurrEUR}
|
|
54
|
+
data-curr-gbp={wUI.btnCurrGBP}
|
|
55
|
+
data-curr-jpy={wUI.btnCurrJPY}
|
|
56
|
+
data-m2-to-ft2={M2_TO_FT2}
|
|
57
|
+
data-l-to-gal={LITRE_TO_GALLON}
|
|
58
|
+
data-m2pl-to-ft2pgal={M2PERL_TO_FT2PERGAL}
|
|
59
|
+
data-unit-coats={wUI.unitCoats}
|
|
60
|
+
>
|
|
61
|
+
<div class="wall-left">
|
|
62
|
+
<p class="wall-section-title">{wUI.sectionTitle}</p>
|
|
63
|
+
|
|
64
|
+
<div class="wall-unit-toggle">
|
|
65
|
+
<button class="wall-unit-btn wall-unit-active" data-unit="metric">{wUI.labelMetric}</button>
|
|
66
|
+
<button class="wall-unit-btn" data-unit="imperial">{wUI.labelImperial}</button>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="wall-field">
|
|
70
|
+
<label class="wall-label" for="wall-area" id="wall-area-label">{wUI.labelArea}</label>
|
|
71
|
+
<div class="wall-number-row">
|
|
72
|
+
<input type="number" id="wall-area" value={INITIAL_AREA} min="1" max="5000" step="1" class="wall-number-input" />
|
|
73
|
+
<span class="wall-number-unit" id="wall-area-unit">{wUI.unitArea}</span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="wall-field">
|
|
78
|
+
<label class="wall-label" for="wall-yield" id="wall-yield-label">{wUI.labelYield}</label>
|
|
79
|
+
<div class="wall-number-row">
|
|
80
|
+
<input type="number" id="wall-yield" value={INITIAL_YIELD} min="1" max="5000" step="0.5" class="wall-number-input" />
|
|
81
|
+
<span class="wall-number-unit" id="wall-yield-unit">{wUI.unitYield}</span>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="wall-field">
|
|
86
|
+
<label class="wall-label">{wUI.labelType}</label>
|
|
87
|
+
<div class="wall-type-grid">
|
|
88
|
+
<button class="wall-type-btn wall-type-active" data-metric-yield="10" data-imperial-yield="406.8" data-dilution="5">
|
|
89
|
+
<span class="wall-type-title">{wUI.btnPlasticMatTitle}</span>
|
|
90
|
+
<span class="wall-type-sub">{wUI.btnPlasticMatSub}</span>
|
|
91
|
+
</button>
|
|
92
|
+
<button class="wall-type-btn" data-metric-yield="12" data-imperial-yield="488.9" data-dilution="5">
|
|
93
|
+
<span class="wall-type-title">{wUI.btnPlasticSatinTitle}</span>
|
|
94
|
+
<span class="wall-type-sub">{wUI.btnPlasticSatinSub}</span>
|
|
95
|
+
</button>
|
|
96
|
+
<button class="wall-type-btn" data-metric-yield="11" data-imperial-yield="448.2" data-dilution="10">
|
|
97
|
+
<span class="wall-type-title">{wUI.btnEnamelTitle}</span>
|
|
98
|
+
<span class="wall-type-sub">{wUI.btnEnamelSub}</span>
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="wall-field">
|
|
104
|
+
<label class="wall-label" for="wall-coats">{wUI.labelCoats}</label>
|
|
105
|
+
<input type="range" id="wall-coats" min="1" max="4" value={INITIAL_COATS} step="1" class="wall-slider" />
|
|
106
|
+
<p id="wall-coats-desc" class="wall-coats-desc">{INITIAL_COATS} {wUI.unitCoats}</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="wall-field">
|
|
110
|
+
<label class="wall-label" for="wall-price" id="wall-price-label">{wUI.labelPrice}</label>
|
|
111
|
+
<div class="wall-number-row">
|
|
112
|
+
<input type="number" id="wall-price" value={INITIAL_PRICE} min="0.01" step="0.01" class="wall-price-input" />
|
|
113
|
+
<span class="wall-number-unit" id="wall-price-unit">{wUI.currencySign}{wUI.unitPrice}</span>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="wall-field">
|
|
118
|
+
<label class="wall-label" for="wall-dilution">{wUI.labelDilution}</label>
|
|
119
|
+
<div class="wall-number-row">
|
|
120
|
+
<input type="number" id="wall-dilution" value={INITIAL_DILUTION} min="0" max="50" step="1" class="wall-number-input" />
|
|
121
|
+
<span class="wall-number-unit">{wUI.unitDilution}</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div class="wall-right">
|
|
127
|
+
<div class="wall-result-badge">{wUI.resultBadge}</div>
|
|
128
|
+
|
|
129
|
+
<div class="wall-paint-section">
|
|
130
|
+
<p class="wall-paint-label" id="wall-paint-label">{wUI.labelPaint}</p>
|
|
131
|
+
<p class="wall-paint-value">
|
|
132
|
+
<span id="wall-paint-num">{fmt2(initial.paintLitres)}</span>
|
|
133
|
+
<span id="wall-paint-unit" class="wall-paint-unit">{wUI.unitYield.split('/')[1]}</span>
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="wall-stats">
|
|
138
|
+
<div class="wall-stat">
|
|
139
|
+
<p class="wall-stat-label">{wUI.labelCost}</p>
|
|
140
|
+
<p id="wall-cost" class="wall-stat-value">{wUI.currencySign}{fmt2(initial.totalCost)}</p>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="wall-stat-divider"></div>
|
|
143
|
+
<div class="wall-stat">
|
|
144
|
+
<p class="wall-stat-label" id="wall-water-label">{wUI.labelWater}</p>
|
|
145
|
+
<p id="wall-water" class="wall-stat-value">{fmt2(initial.waterLitres)} {wUI.unitYield.split('/')[1]}</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="wall-stat-divider"></div>
|
|
148
|
+
<div class="wall-stat">
|
|
149
|
+
<p class="wall-stat-label" id="wall-total-label">{wUI.labelTotalVolume}</p>
|
|
150
|
+
<p id="wall-total" class="wall-stat-value">{fmt2(initial.totalVolume)} {wUI.unitYield.split('/')[1]}</p>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="wall-currency-toggle">
|
|
155
|
+
<span class="wall-currency-label">{wUI.labelCurrency}</span>
|
|
156
|
+
<div class="wall-currency-btns">
|
|
157
|
+
<button class="wall-currency-btn wall-currency-active" data-currency="usd">{wUI.btnCurrUSD}</button>
|
|
158
|
+
<button class="wall-currency-btn" data-currency="eur">{wUI.btnCurrEUR}</button>
|
|
159
|
+
<button class="wall-currency-btn" data-currency="gbp">{wUI.btnCurrGBP}</button>
|
|
160
|
+
<button class="wall-currency-btn" data-currency="jpy">{wUI.btnCurrJPY}</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<script>
|
|
168
|
+
import { calculateWallPainting, fmt2 } from './logic';
|
|
169
|
+
|
|
170
|
+
const LS_KEY_UNIT = 'wall-painting-unit-system';
|
|
171
|
+
const LS_KEY_CURRENCY = 'wall-painting-currency';
|
|
172
|
+
|
|
173
|
+
let unitSystem: 'metric' | 'imperial' = 'metric';
|
|
174
|
+
let currency: 'usd' | 'eur' | 'gbp' | 'jpy' = 'usd';
|
|
175
|
+
|
|
176
|
+
function el(id: string) { return document.getElementById(id); }
|
|
177
|
+
function setTxt(id: string, val: string) { const e = el(id); if (e) e.textContent = val; }
|
|
178
|
+
function getNum(id: string): number { const e = el(id) as HTMLInputElement | null; return e ? parseFloat(e.value) || 0 : 0; }
|
|
179
|
+
function getDataset(card: HTMLElement, key: string): string { return card.dataset[key] ?? ''; }
|
|
180
|
+
function getFactor(card: HTMLElement, key: string): number { return parseFloat(getDataset(card, key)) || 1; }
|
|
181
|
+
function toMetric(v: number, f: number) { return unitSystem === 'imperial' ? v / f : v; }
|
|
182
|
+
function fromMetric(v: number, f: number) { return unitSystem === 'imperial' ? v * f : v; }
|
|
183
|
+
|
|
184
|
+
function getCurrencySign(card: HTMLElement): string {
|
|
185
|
+
const key = `curr${currency.charAt(0).toUpperCase()}${currency.slice(1)}`;
|
|
186
|
+
return getDataset(card, key) || getDataset(card, 'currencySign') || '$';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function calculate(card: HTMLElement) {
|
|
190
|
+
const m2ToFt2 = getFactor(card, 'm2ToFt2');
|
|
191
|
+
const lToGal = getFactor(card, 'lToGal');
|
|
192
|
+
const m2plToFt2pgal = getFactor(card, 'm2plToFt2pgal');
|
|
193
|
+
const currencySign = getCurrencySign(card);
|
|
194
|
+
const area = toMetric(getNum('wall-area'), m2ToFt2);
|
|
195
|
+
const yieldVal = toMetric(getNum('wall-yield'), m2plToFt2pgal);
|
|
196
|
+
const coats = getNum('wall-coats');
|
|
197
|
+
const price = toMetric(getNum('wall-price'), 1 / lToGal);
|
|
198
|
+
const dilution = getNum('wall-dilution');
|
|
199
|
+
const r = calculateWallPainting({ area, yieldM2PerLitre: yieldVal, coats, pricePerLitre: price, dilutionPercent: dilution });
|
|
200
|
+
setTxt('wall-paint-num', fmt2(fromMetric(r.paintLitres, lToGal)));
|
|
201
|
+
setTxt('wall-cost', `${currencySign}${fmt2(r.totalCost)}`);
|
|
202
|
+
setTxt('wall-water', `${fmt2(fromMetric(r.waterLitres, lToGal))} ${unitSystem === 'imperial' ? 'gal' : 'L'}`);
|
|
203
|
+
setTxt('wall-total', `${fmt2(fromMetric(r.totalVolume, lToGal))} ${unitSystem === 'imperial' ? 'gal' : 'L'}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function setAreaLabels(card: HTMLElement, i: boolean) {
|
|
207
|
+
setTxt('wall-area-label', i ? getDataset(card, 'labelAreaFt') : getDataset(card, 'labelArea'));
|
|
208
|
+
setTxt('wall-area-unit', i ? getDataset(card, 'unitAreaFt') : getDataset(card, 'unitArea'));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setYieldLabels(card: HTMLElement, i: boolean) {
|
|
212
|
+
setTxt('wall-yield-label', i ? getDataset(card, 'labelYieldFt') : getDataset(card, 'labelYield'));
|
|
213
|
+
setTxt('wall-yield-unit', i ? getDataset(card, 'unitYieldFt') : getDataset(card, 'unitYield'));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function setPriceLabels(card: HTMLElement, i: boolean) {
|
|
217
|
+
const currencySign = getCurrencySign(card);
|
|
218
|
+
setTxt('wall-price-label', i ? getDataset(card, 'labelPriceFt') : getDataset(card, 'labelPrice'));
|
|
219
|
+
setTxt('wall-price-unit', `${currencySign}${i ? getDataset(card, 'unitPriceGal') : getDataset(card, 'unitPrice')}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function setResultLabels(card: HTMLElement, i: boolean) {
|
|
223
|
+
setTxt('wall-paint-label', i ? getDataset(card, 'labelPaintGal') : getDataset(card, 'labelPaint'));
|
|
224
|
+
setTxt('wall-paint-unit', i ? 'gal' : 'L');
|
|
225
|
+
setTxt('wall-water-label', i ? getDataset(card, 'labelWaterGal') : getDataset(card, 'labelWater'));
|
|
226
|
+
setTxt('wall-total-label', i ? getDataset(card, 'labelTotalGal') : getDataset(card, 'labelTotal'));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function updateLabels(card: HTMLElement) {
|
|
230
|
+
const i = unitSystem === 'imperial';
|
|
231
|
+
setAreaLabels(card, i);
|
|
232
|
+
setYieldLabels(card, i);
|
|
233
|
+
setPriceLabels(card, i);
|
|
234
|
+
setResultLabels(card, i);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function toggleClass(sel: string, attr: string, val: string, cls: string) {
|
|
238
|
+
document.querySelectorAll(sel).forEach((b) => b.classList.toggle(cls, (b as HTMLElement).dataset[attr] === val));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function applyUnitSystem(sys: 'metric' | 'imperial', card: HTMLElement) {
|
|
242
|
+
unitSystem = sys;
|
|
243
|
+
localStorage.setItem(LS_KEY_UNIT, sys);
|
|
244
|
+
toggleClass('.wall-unit-btn', 'unit', sys, 'wall-unit-active');
|
|
245
|
+
const m2ToFt2 = getFactor(card, 'm2ToFt2');
|
|
246
|
+
const m2plToFt2pgal = getFactor(card, 'm2plToFt2pgal');
|
|
247
|
+
const lToGal = getFactor(card, 'lToGal');
|
|
248
|
+
const areaEl = el('wall-area') as HTMLInputElement | null;
|
|
249
|
+
const yieldEl = el('wall-yield') as HTMLInputElement | null;
|
|
250
|
+
const priceEl = el('wall-price') as HTMLInputElement | null;
|
|
251
|
+
if (areaEl) areaEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-area') * m2ToFt2) : Math.round(getNum('wall-area') / m2ToFt2));
|
|
252
|
+
if (yieldEl) yieldEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-yield') * m2plToFt2pgal * 10) / 10 : Math.round(getNum('wall-yield') / m2plToFt2pgal * 10) / 10);
|
|
253
|
+
if (priceEl) priceEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-price') / lToGal * 100) / 100 : Math.round(getNum('wall-price') * lToGal * 100) / 100);
|
|
254
|
+
updateLabels(card);
|
|
255
|
+
calculate(card);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function applyCurrency(curr: 'usd' | 'eur' | 'gbp' | 'jpy', card: HTMLElement) {
|
|
259
|
+
currency = curr;
|
|
260
|
+
localStorage.setItem(LS_KEY_CURRENCY, curr);
|
|
261
|
+
toggleClass('.wall-currency-btn', 'currency', curr, 'wall-currency-active');
|
|
262
|
+
const i = unitSystem === 'imperial';
|
|
263
|
+
const currencySign = getCurrencySign(card);
|
|
264
|
+
setTxt('wall-price-unit', `${currencySign}${i ? getDataset(card, 'unitPriceGal') : getDataset(card, 'unitPrice')}`);
|
|
265
|
+
calculate(card);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function attachTypeButtons(card: HTMLElement) {
|
|
269
|
+
document.querySelectorAll<HTMLElement>('.wall-type-btn').forEach((btn) => {
|
|
270
|
+
btn.addEventListener('click', () => {
|
|
271
|
+
document.querySelectorAll('.wall-type-btn').forEach((b) => b.classList.remove('wall-type-active'));
|
|
272
|
+
btn.classList.add('wall-type-active');
|
|
273
|
+
const yieldEl = el('wall-yield') as HTMLInputElement | null;
|
|
274
|
+
const dilEl = el('wall-dilution') as HTMLInputElement | null;
|
|
275
|
+
if (!yieldEl || !dilEl) return;
|
|
276
|
+
const isImperial = unitSystem === 'imperial';
|
|
277
|
+
const yieldKey = isImperial ? 'imperialYield' : 'metricYield';
|
|
278
|
+
if (btn.dataset[yieldKey]) yieldEl.value = btn.dataset[yieldKey]!;
|
|
279
|
+
if (btn.dataset.dilution) dilEl.value = btn.dataset.dilution;
|
|
280
|
+
calculate(card);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function restoreState() {
|
|
286
|
+
const savedUnit = localStorage.getItem(LS_KEY_UNIT) as 'metric' | 'imperial' | null;
|
|
287
|
+
if (savedUnit) unitSystem = savedUnit;
|
|
288
|
+
const savedCurr = localStorage.getItem(LS_KEY_CURRENCY) as 'usd' | 'eur' | 'gbp' | 'jpy' | null;
|
|
289
|
+
if (savedCurr) currency = savedCurr;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function attachUnitToggle(card: HTMLElement) {
|
|
293
|
+
document.querySelectorAll('.wall-unit-btn').forEach((btn) => {
|
|
294
|
+
btn.addEventListener('click', () => {
|
|
295
|
+
const s = (btn as HTMLElement).dataset.unit as 'metric' | 'imperial';
|
|
296
|
+
if (s) applyUnitSystem(s, card);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function attachCurrencyToggle(card: HTMLElement) {
|
|
302
|
+
document.querySelectorAll('.wall-currency-btn').forEach((btn) => {
|
|
303
|
+
btn.addEventListener('click', () => {
|
|
304
|
+
const c = (btn as HTMLElement).dataset.currency as 'usd' | 'eur' | 'gbp' | 'jpy';
|
|
305
|
+
if (c) applyCurrency(c, card);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function attachInputListeners(card: HTMLElement) {
|
|
311
|
+
['wall-area', 'wall-yield', 'wall-price', 'wall-dilution'].forEach((id) => {
|
|
312
|
+
const e = el(id);
|
|
313
|
+
if (e) e.addEventListener('input', () => calculate(card));
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function init() {
|
|
318
|
+
const card = document.querySelector('.wall-card') as HTMLElement | null;
|
|
319
|
+
if (!card) return;
|
|
320
|
+
restoreState();
|
|
321
|
+
toggleClass('.wall-unit-btn', 'unit', unitSystem, 'wall-unit-active');
|
|
322
|
+
toggleClass('.wall-currency-btn', 'currency', currency, 'wall-currency-active');
|
|
323
|
+
updateLabels(card);
|
|
324
|
+
attachUnitToggle(card);
|
|
325
|
+
attachCurrencyToggle(card);
|
|
326
|
+
attachTypeButtons(card);
|
|
327
|
+
const coatsSlider = el('wall-coats') as HTMLInputElement | null;
|
|
328
|
+
if (coatsSlider) {
|
|
329
|
+
coatsSlider.addEventListener('input', () => {
|
|
330
|
+
setTxt('wall-coats-desc', `${coatsSlider.value} ${getDataset(card, 'unitCoats')}`);
|
|
331
|
+
calculate(card);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
attachInputListeners(card);
|
|
335
|
+
calculate(card);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
document.addEventListener('astro:page-load', init);
|
|
339
|
+
init();
|
|
340
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HomeToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
import type { WallPaintingCalculatorUI } from './ui';
|
|
3
|
+
|
|
4
|
+
export type WallPaintingCalculatorLocaleContent = ToolLocaleContent<WallPaintingCalculatorUI>;
|
|
5
|
+
|
|
6
|
+
export const wallPaintingCalculator: HomeToolEntry<WallPaintingCalculatorUI> = {
|
|
7
|
+
id: 'wall-painting-calculator',
|
|
8
|
+
icons: {
|
|
9
|
+
bg: 'mdi:palette',
|
|
10
|
+
fg: 'mdi:format-paint',
|
|
11
|
+
},
|
|
12
|
+
i18n: {
|
|
13
|
+
de: async () => (await import('./i18n/de')).content,
|
|
14
|
+
en: async () => (await import('./i18n/en')).content,
|
|
15
|
+
es: async () => (await import('./i18n/es')).content,
|
|
16
|
+
fr: async () => (await import('./i18n/fr')).content,
|
|
17
|
+
id: async () => (await import('./i18n/id')).content,
|
|
18
|
+
it: async () => (await import('./i18n/it')).content,
|
|
19
|
+
ja: async () => (await import('./i18n/ja')).content,
|
|
20
|
+
ko: async () => (await import('./i18n/ko')).content,
|
|
21
|
+
nl: async () => (await import('./i18n/nl')).content,
|
|
22
|
+
pl: async () => (await import('./i18n/pl')).content,
|
|
23
|
+
pt: async () => (await import('./i18n/pt')).content,
|
|
24
|
+
ru: async () => (await import('./i18n/ru')).content,
|
|
25
|
+
sv: async () => (await import('./i18n/sv')).content,
|
|
26
|
+
tr: async () => (await import('./i18n/tr')).content,
|
|
27
|
+
zh: async () => (await import('./i18n/zh')).content,
|
|
28
|
+
},
|
|
29
|
+
};
|