refactor: Centralize calculator statistics and categories into a new module, lazy load search functionality, and remove unused font preloads.
This commit is contained in:
@@ -10,20 +10,6 @@
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/inter/Inter-Medium.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/inter/Inter-SemiBold.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/inter/Inter-Bold.woff2"
|
href="/fonts/inter/Inter-Bold.woff2"
|
||||||
@@ -31,34 +17,6 @@
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/inter/Inter-ExtraBold.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const doc = document.documentElement;
|
const doc = document.documentElement;
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { searchCalculators } from '$lib/data/calculators';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let idPrefix = 'search';
|
export let idPrefix = 'search';
|
||||||
|
|
||||||
let query = '';
|
let query = '';
|
||||||
let focused = false;
|
let focused = false;
|
||||||
let selectedIndex = -1;
|
let selectedIndex = -1;
|
||||||
let lastQuery = '';
|
let lastQuery = '';
|
||||||
|
let searchCalculatorsFn: ((q: string) => any[]) | null = null;
|
||||||
|
|
||||||
$: results = query.length >= 2 ? searchCalculators(query).slice(0, 8) : [];
|
$: if (query.length >= 1 && !searchCalculatorsFn) {
|
||||||
|
import('$lib/data/calculators').then(m => {
|
||||||
|
searchCalculatorsFn = m.searchCalculators;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$: results = (query.length >= 2 && searchCalculatorsFn) ? searchCalculatorsFn(query).slice(0, 8) : [];
|
||||||
$: listboxId = `${idPrefix}-listbox`;
|
$: listboxId = `${idPrefix}-listbox`;
|
||||||
$: inputId = `${idPrefix}-input`;
|
$: inputId = `${idPrefix}-input`;
|
||||||
$: isOpen = focused && results.length > 0;
|
$: isOpen = focused && results.length > 0;
|
||||||
|
|||||||
@@ -17,26 +17,26 @@ export interface CalculatorDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const categories: Record<string, { label: string; icon: string }> = {
|
export const categories: Record<string, { label: string; icon: string }> = {
|
||||||
length: { label: 'Length / Distance', icon: '📏' },
|
'length': { "label": "Length / Distance", "icon": "📏" },
|
||||||
weight: { label: 'Weight / Mass', icon: '⚖️' },
|
'weight': { "label": "Weight / Mass", "icon": "⚖️" },
|
||||||
temperature: { label: 'Temperature', icon: '🌡️' },
|
'temperature': { "label": "Temperature", "icon": "🌡️" },
|
||||||
volume: { label: 'Volume', icon: '🧪' },
|
'volume': { "label": "Volume", "icon": "🧪" },
|
||||||
fluids: { label: 'Fluids', icon: '💧' },
|
'fluids': { "label": "Fluids", "icon": "💧" },
|
||||||
area: { label: 'Area', icon: '🔳' },
|
'area': { "label": "Area", "icon": "🔳" },
|
||||||
speed: { label: 'Speed / Velocity', icon: '💨' },
|
'speed': { "label": "Speed / Velocity", "icon": "💨" },
|
||||||
pressure: { label: 'Pressure', icon: '🔽' },
|
'pressure': { "label": "Pressure", "icon": "🔽" },
|
||||||
energy: { label: 'Energy', icon: '⚡' },
|
'energy': { "label": "Energy", "icon": "⚡" },
|
||||||
magnetism: { label: 'Magnetism', icon: '🧲' },
|
'magnetism': { "label": "Magnetism", "icon": "🧲" },
|
||||||
power: { label: 'Power', icon: '🔌' },
|
'power': { "label": "Power", "icon": "🔌" },
|
||||||
data: { label: 'Data Storage', icon: '💾' },
|
'data': { "label": "Data Storage", "icon": "💾" },
|
||||||
time: { label: 'Time', icon: '⏱️' },
|
'time': { "label": "Time", "icon": "⏱️" },
|
||||||
angle: { label: 'Angle', icon: '📐' },
|
'angle': { "label": "Angle", "icon": "📐" },
|
||||||
'number-systems':{ label: 'Number Systems', icon: '🔢' },
|
'number-systems': { "label": "Number Systems", "icon": "🔢" },
|
||||||
radiation: { label: 'Radiation', icon: '☢️' },
|
'radiation': { "label": "Radiation", "icon": "☢️" },
|
||||||
electrical: { label: 'Electrical', icon: '🔋' },
|
'electrical': { "label": "Electrical", "icon": "🔋" },
|
||||||
force: { label: 'Force / Torque', icon: '💪' },
|
'force': { "label": "Force / Torque", "icon": "💪" },
|
||||||
light: { label: 'Light', icon: '💡' },
|
'light': { "label": "Light", "icon": "💡" },
|
||||||
other: { label: 'Other', icon: '🔄' },
|
'other': { "label": "Other", "icon": "🔄" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calculators: CalculatorDef[] = [
|
export const calculators: CalculatorDef[] = [
|
||||||
@@ -3167,8 +3167,6 @@ export const calculators: CalculatorDef[] = [
|
|||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const slugIndex = new Map(calculators.map(c => [c.slug, c]));
|
|
||||||
|
|
||||||
export function getCalculatorBySlug(slug: string): CalculatorDef | undefined {
|
export function getCalculatorBySlug(slug: string): CalculatorDef | undefined {
|
||||||
return slugIndex.get(slug);
|
return slugIndex.get(slug);
|
||||||
}
|
}
|
||||||
|
|||||||
85
hdyc-svelte/src/lib/data/stats.ts
Normal file
85
hdyc-svelte/src/lib/data/stats.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// THIS FILE IS AUTO-GENERATED BY migrate.py
|
||||||
|
export const categories: Record<string, { label: string; icon: string }> = {
|
||||||
|
"length": {
|
||||||
|
"label": "Length / Distance",
|
||||||
|
"icon": "📏"
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"label": "Weight / Mass",
|
||||||
|
"icon": "⚖️"
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"label": "Temperature",
|
||||||
|
"icon": "🌡️"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"label": "Volume",
|
||||||
|
"icon": "🧪"
|
||||||
|
},
|
||||||
|
"fluids": {
|
||||||
|
"label": "Fluids",
|
||||||
|
"icon": "💧"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"label": "Area",
|
||||||
|
"icon": "🔳"
|
||||||
|
},
|
||||||
|
"speed": {
|
||||||
|
"label": "Speed / Velocity",
|
||||||
|
"icon": "💨"
|
||||||
|
},
|
||||||
|
"pressure": {
|
||||||
|
"label": "Pressure",
|
||||||
|
"icon": "🔽"
|
||||||
|
},
|
||||||
|
"energy": {
|
||||||
|
"label": "Energy",
|
||||||
|
"icon": "⚡"
|
||||||
|
},
|
||||||
|
"magnetism": {
|
||||||
|
"label": "Magnetism",
|
||||||
|
"icon": "🧲"
|
||||||
|
},
|
||||||
|
"power": {
|
||||||
|
"label": "Power",
|
||||||
|
"icon": "🔌"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"label": "Data Storage",
|
||||||
|
"icon": "💾"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"label": "Time",
|
||||||
|
"icon": "⏱️"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"label": "Angle",
|
||||||
|
"icon": "📐"
|
||||||
|
},
|
||||||
|
"number-systems": {
|
||||||
|
"label": "Number Systems",
|
||||||
|
"icon": "🔢"
|
||||||
|
},
|
||||||
|
"radiation": {
|
||||||
|
"label": "Radiation",
|
||||||
|
"icon": "☢️"
|
||||||
|
},
|
||||||
|
"electrical": {
|
||||||
|
"label": "Electrical",
|
||||||
|
"icon": "🔋"
|
||||||
|
},
|
||||||
|
"force": {
|
||||||
|
"label": "Force / Torque",
|
||||||
|
"icon": "💪"
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"label": "Light",
|
||||||
|
"icon": "💡"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"label": "Other",
|
||||||
|
"icon": "🔄"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const totalCalculators = 2460;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { categories, calculators } from '$lib/data/calculators';
|
import { categories, totalCalculators } from '$lib/data/stats';
|
||||||
import CategoryCard from '$lib/components/CategoryCard.svelte';
|
import CategoryCard from '$lib/components/CategoryCard.svelte';
|
||||||
import SearchBar from '$lib/components/SearchBar.svelte';
|
import SearchBar from '$lib/components/SearchBar.svelte';
|
||||||
import { buildSeoMeta, SITE_NAME, SITE_URL, toJsonLd } from '$lib/seo';
|
import { buildSeoMeta, SITE_NAME, SITE_URL, toJsonLd } from '$lib/seo';
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
key,
|
key,
|
||||||
...meta,
|
...meta,
|
||||||
}));
|
}));
|
||||||
const totalCalculators = calculators.length;
|
const totalConversions = totalCalculators;
|
||||||
const totalCategories = Object.keys(homepageCategories).length;
|
const totalCategoriesCount = Object.keys(homepageCategories).length;
|
||||||
const pageTitle = `${SITE_NAME} — Free Unit Conversion Calculators`;
|
const pageTitle = `${SITE_NAME} — Free Unit Conversion Calculators`;
|
||||||
const pageDescription = 'Convert between hundreds of units instantly. Free online calculators for length, weight, temperature, volume, area, speed, energy, power, data and more.';
|
const pageDescription = 'Convert between hundreds of units instantly. Free online calculators for length, weight, temperature, volume, area, speed, energy, power, data and more.';
|
||||||
const seo = buildSeoMeta({
|
const seo = buildSeoMeta({
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
|
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-num">{totalCalculators}</div>
|
<div class="stat-num">{totalConversions}</div>
|
||||||
<div class="stat-label">Converters</div>
|
<div class="stat-label">Converters</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-num">{totalCategories}</div>
|
<div class="stat-num">{totalCategoriesCount}</div>
|
||||||
<div class="stat-label">Categories</div>
|
<div class="stat-label">Categories</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
65
migrate.py
65
migrate.py
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
CALCLIST = BASE_DIR / 'calculators_list.md'
|
CALCLIST = BASE_DIR / 'calculators_list.md'
|
||||||
OUTPUT_FILE = BASE_DIR / 'hdyc-svelte/src/lib/data/calculators.ts'
|
OUTPUT_FILE = BASE_DIR / 'hdyc-svelte/src/lib/data/calculators.ts'
|
||||||
|
STATS_FILE = BASE_DIR / 'hdyc-svelte/src/lib/data/stats.ts'
|
||||||
|
|
||||||
CATEGORY_KEYS = [
|
CATEGORY_KEYS = [
|
||||||
'length',
|
'length',
|
||||||
@@ -29,6 +30,29 @@ CATEGORY_KEYS = [
|
|||||||
'other',
|
'other',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CATEGORIES = {
|
||||||
|
'length': {'label': 'Length / Distance', 'icon': '📏'},
|
||||||
|
'weight': {'label': 'Weight / Mass', 'icon': '⚖️'},
|
||||||
|
'temperature': {'label': 'Temperature', 'icon': '🌡️'},
|
||||||
|
'volume': {'label': 'Volume', 'icon': '🧪'},
|
||||||
|
'fluids': {'label': 'Fluids', 'icon': '💧'},
|
||||||
|
'area': {'label': 'Area', 'icon': '🔳'},
|
||||||
|
'speed': {'label': 'Speed / Velocity', 'icon': '💨'},
|
||||||
|
'pressure': {'label': 'Pressure', 'icon': '🔽'},
|
||||||
|
'energy': {'label': 'Energy', 'icon': '⚡'},
|
||||||
|
'magnetism': {'label': 'Magnetism', 'icon': '🧲'},
|
||||||
|
'power': {'label': 'Power', 'icon': '🔌'},
|
||||||
|
'data': {'label': 'Data Storage', 'icon': '💾'},
|
||||||
|
'time': {'label': 'Time', 'icon': '⏱️'},
|
||||||
|
'angle': {'label': 'Angle', 'icon': '📐'},
|
||||||
|
'number-systems': {'label': 'Number Systems', 'icon': '🔢'},
|
||||||
|
'radiation': {'label': 'Radiation', 'icon': '☢️'},
|
||||||
|
'electrical': {'label': 'Electrical', 'icon': '🔋'},
|
||||||
|
'force': {'label': 'Force / Torque', 'icon': '💪'},
|
||||||
|
'light': {'label': 'Light', 'icon': '💡'},
|
||||||
|
'other': {'label': 'Other', 'icon': '🔄'},
|
||||||
|
}
|
||||||
|
|
||||||
CATEGORY_SET = set(CATEGORY_KEYS)
|
CATEGORY_SET = set(CATEGORY_KEYS)
|
||||||
|
|
||||||
# Lightweight label normalization to catch duplicate/identity conversions
|
# Lightweight label normalization to catch duplicate/identity conversions
|
||||||
@@ -418,28 +442,12 @@ export interface CalculatorDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const categories: Record<string, { label: string; icon: string }> = {
|
export const categories: Record<string, { label: string; icon: string }> = {
|
||||||
length: { label: 'Length / Distance', icon: '📏' },
|
"""
|
||||||
weight: { label: 'Weight / Mass', icon: '⚖️' },
|
for k, v in CATEGORIES.items():
|
||||||
temperature: { label: 'Temperature', icon: '🌡️' },
|
out += f" '{k}': {json.dumps(v, ensure_ascii=False).replace('{', '{ ').replace('}', ' }')},\n"
|
||||||
volume: { label: 'Volume', icon: '🧪' },
|
out += "};\n"
|
||||||
fluids: { label: 'Fluids', icon: '💧' },
|
|
||||||
area: { label: 'Area', icon: '🔳' },
|
|
||||||
speed: { label: 'Speed / Velocity', icon: '💨' },
|
|
||||||
pressure: { label: 'Pressure', icon: '🔽' },
|
|
||||||
energy: { label: 'Energy', icon: '⚡' },
|
|
||||||
magnetism: { label: 'Magnetism', icon: '🧲' },
|
|
||||||
power: { label: 'Power', icon: '🔌' },
|
|
||||||
data: { label: 'Data Storage', icon: '💾' },
|
|
||||||
time: { label: 'Time', icon: '⏱️' },
|
|
||||||
angle: { label: 'Angle', icon: '📐' },
|
|
||||||
'number-systems':{ label: 'Number Systems', icon: '🔢' },
|
|
||||||
radiation: { label: 'Radiation', icon: '☢️' },
|
|
||||||
electrical: { label: 'Electrical', icon: '🔋' },
|
|
||||||
force: { label: 'Force / Torque', icon: '💪' },
|
|
||||||
light: { label: 'Light', icon: '💡' },
|
|
||||||
other: { label: 'Other', icon: '🔄' },
|
|
||||||
};
|
|
||||||
|
|
||||||
|
out += """
|
||||||
export const calculators: CalculatorDef[] = [
|
export const calculators: CalculatorDef[] = [
|
||||||
"""
|
"""
|
||||||
for e in calculators_ts_entries:
|
for e in calculators_ts_entries:
|
||||||
@@ -453,9 +461,9 @@ export const calculators: CalculatorDef[] = [
|
|||||||
|
|
||||||
out += """
|
out += """
|
||||||
];
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
const slugIndex = new Map(calculators.map(c => [c.slug, c]));
|
out += """
|
||||||
|
|
||||||
export function getCalculatorBySlug(slug: string): CalculatorDef | undefined {
|
export function getCalculatorBySlug(slug: string): CalculatorDef | undefined {
|
||||||
return slugIndex.get(slug);
|
return slugIndex.get(slug);
|
||||||
}
|
}
|
||||||
@@ -487,5 +495,16 @@ export function searchCalculators(query: string): CalculatorDef[] {
|
|||||||
|
|
||||||
print(f"Generated {len(calculators_ts_entries)} calculators into calculators.ts")
|
print(f"Generated {len(calculators_ts_entries)} calculators into calculators.ts")
|
||||||
|
|
||||||
|
# Generate stats.ts
|
||||||
|
active_count = len([e for e in calculators_ts_entries if not e.get('hidden')])
|
||||||
|
stats_content = f"""// THIS FILE IS AUTO-GENERATED BY migrate.py
|
||||||
|
export const categories: Record<string, {{ label: string; icon: string }}> = {json.dumps(CATEGORIES, indent=2, ensure_ascii=False)};
|
||||||
|
|
||||||
|
export const totalCalculators = {active_count};
|
||||||
|
"""
|
||||||
|
with open(STATS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(stats_content)
|
||||||
|
print(f"Generated stats.ts with {active_count} active calculators")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process()
|
process()
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class HomepageCategoryRegressionTests(unittest.TestCase):
|
|||||||
def test_homepage_uses_canonical_categories_map(self) -> None:
|
def test_homepage_uses_canonical_categories_map(self) -> None:
|
||||||
text = HOMEPAGE_SVELTE.read_text(encoding="utf-8")
|
text = HOMEPAGE_SVELTE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
self.assertIn("import { categories, calculators } from '$lib/data/calculators';", text)
|
self.assertIn("import { categories, totalCalculators } from '$lib/data/stats';", text)
|
||||||
self.assertIn("requiredCategoryFallbacks", text)
|
self.assertIn("requiredCategoryFallbacks", text)
|
||||||
self.assertIn("fluids: { label: 'Fluids', icon: '💧' }", text)
|
self.assertIn("fluids: { label: 'Fluids', icon: '💧' }", text)
|
||||||
self.assertIn("magnetism: { label: 'Magnetism', icon: '🧲' }", text)
|
self.assertIn("magnetism: { label: 'Magnetism', icon: '🧲' }", text)
|
||||||
|
|||||||
Reference in New Issue
Block a user