From 4cd227d1a10e98bd96ba4fd2d5e2bcb65e830ff9 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Mon, 19 Jan 2026 15:32:54 +0800 Subject: [PATCH 01/13] refactor: update sitemap --- public/sitemap.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/public/sitemap.xml b/public/sitemap.xml index 7dd0b5b..7662efa 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -7,8 +7,6 @@ 2024-01-01 weekly 1.0 - - @@ -17,8 +15,6 @@ 2024-01-01 monthly 0.9 - - @@ -27,8 +23,6 @@ 2024-01-01 monthly 0.9 - - @@ -37,8 +31,6 @@ 2024-01-01 monthly 0.7 - - @@ -47,7 +39,5 @@ 2024-01-01 monthly 0.6 - - From 046c8ea01f71b0fc4397bd1e11c492825d791866 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Tue, 24 Feb 2026 09:41:33 +0800 Subject: [PATCH 02/13] feat: add json grid page and update home layout --- src/init/i18n/locales/BritishEnglish.json | 17 ++- src/init/i18n/locales/SimplifiedChinese.json | 17 ++- src/page/home/index.tsx | 57 +++---- src/page/json-grid/index.tsx | 152 +++++++++++++++++++ src/router/index.tsx | 4 + 5 files changed, 214 insertions(+), 33 deletions(-) create mode 100644 src/page/json-grid/index.tsx diff --git a/src/init/i18n/locales/BritishEnglish.json b/src/init/i18n/locales/BritishEnglish.json index 478d513..478e08c 100644 --- a/src/init/i18n/locales/BritishEnglish.json +++ b/src/init/i18n/locales/BritishEnglish.json @@ -18,10 +18,12 @@ "title": "DevLab", "description": "A collection of powerful, privacy-focused developer tools. All processing happens locally in your browser.", "getStarted": "Get Started", - "getStartedDescription": "Start visualising and querying your JSON data with our intuitive JSONPath-based tool.", - "openJsonViewer": "Open JSON Viewer", - "openBmiCalculator": "Open BMI Calculator", + "jsonViewerDescription": "Start visualising and querying your JSON data with our intuitive JSONPath-based tool.", + "jsonViewer": "JSON Viewer", + "bmiCalculator": "BMI Calculator", + "jsonGrid": "JSON Grid", "bmiCalculatorDescription": "Calculate your Body Mass Index (BMI) to assess your weight status and health. Get instant results with personalised advice based on your BMI category.", + "jsonGridDescription": "Convert JSON arrays into a clean table view for quick inspection and comparison.", "features": { "tools": { "title": "🛠️ Developer Tools", @@ -48,6 +50,15 @@ "copyAsCsv": "Copy as CSV", "error": "Error:" }, + "jsonGrid": { + "jsonInput": "JSON Input", + "tableResult": "Table Result", + "rows": "rows", + "exportCsv": "Export CSV", + "empty": "No data to display.", + "parseError": "Invalid JSON:", + "arrayOnlyError": "Please provide a JSON array." + }, "about": { "title": "About DevLab", "description": "A powerful, privacy-focused tool for debugging and visualising complex JSON data structures.", diff --git a/src/init/i18n/locales/SimplifiedChinese.json b/src/init/i18n/locales/SimplifiedChinese.json index 59f2009..c0dfd1b 100644 --- a/src/init/i18n/locales/SimplifiedChinese.json +++ b/src/init/i18n/locales/SimplifiedChinese.json @@ -18,10 +18,12 @@ "title": "DevLab", "description": "一系列强大的、注重隐私的开发者工具集合。所有处理都在您的浏览器本地进行。", "getStarted": "开始使用", - "getStartedDescription": "使用我们直观的基于 JSONPath 的工具开始可视化和查询您的 JSON 数据。", - "openJsonViewer": "打开 JSON 查看器", - "openBmiCalculator": "打开 BMI 计算器", + "jsonViewerDescription": "使用我们直观的基于 JSONPath 的工具开始可视化和查询您的 JSON 数据。", + "jsonViewer": "JSON 查看器", + "bmiCalculator": "BMI 计算器", + "jsonGrid": "JSON Grid", "bmiCalculatorDescription": "计算您的身体质量指数(BMI)以评估您的体重状态和健康状况。根据您的 BMI 分类获得即时结果和个性化建议。", + "jsonGridDescription": "将 JSON 数组转换为清晰的表格视图,便于快速查看和对比。", "features": { "tools": { "title": "🛠️ 开发者工具", @@ -48,6 +50,15 @@ "copyAsCsv": "复制为 CSV", "error": "错误:" }, + "jsonGrid": { + "jsonInput": "JSON 输入", + "tableResult": "表格结果", + "rows": "行", + "exportCsv": "导出 CSV", + "empty": "暂无可展示的数据。", + "parseError": "JSON 无效:", + "arrayOnlyError": "请输入 JSON 数组。" + }, "about": { "title": "关于 DevLab", "description": "一个强大的、注重隐私的工具,用于调试和可视化复杂的 JSON 数据结构。", diff --git a/src/page/home/index.tsx b/src/page/home/index.tsx index 3fea8b5..60d7c76 100644 --- a/src/page/home/index.tsx +++ b/src/page/home/index.tsx @@ -12,34 +12,37 @@ export default function Home() { {/* Page Header */}

{t("home.title")}

-

- {t("home.description")} -

+

{t("home.description")}

- {/* Main CTA - Two columns */} -
+
-

{t("home.getStarted")}

-

- {t("home.getStartedDescription")} -

+

{t("home.jsonViewer")}

+

{t("home.jsonViewerDescription")}

- {t("home.openJsonViewer")} + {t("home.getStarted")}
-

{t("home.getStarted")}

-

- {t("home.bmiCalculatorDescription")} -

+

{t("home.bmiCalculator")}

+

{t("home.bmiCalculatorDescription")}

- {t("home.openBmiCalculator")} + {t("home.getStarted")} + +
+ +
+

{t("home.jsonGrid")}

+

{t("home.jsonGridDescription")}

+ + {t("home.getStarted")}
@@ -47,22 +50,22 @@ export default function Home() { {/* Features */}
-

{t("home.features.tools.title")}

-

- {t("home.features.tools.description")} -

+

+ {t("home.features.tools.title")} +

+

{t("home.features.tools.description")}

-

{t("home.features.privacy.title")}

-

- {t("home.features.privacy.description")} -

+

+ {t("home.features.privacy.title")} +

+

{t("home.features.privacy.description")}

-

{t("home.features.free.title")}

-

- {t("home.features.free.description")} -

+

+ {t("home.features.free.title")} +

+

{t("home.features.free.description")}

diff --git a/src/page/json-grid/index.tsx b/src/page/json-grid/index.tsx new file mode 100644 index 0000000..a91d456 --- /dev/null +++ b/src/page/json-grid/index.tsx @@ -0,0 +1,152 @@ +import { useCallback, useMemo, useState } from "react" +import { useTranslation } from "react-i18next" + +type RowRecord = Record + +export default function JsonGrid() { + const { t } = useTranslation() + + const initialData = [ + { id: 1, name: "Alice", role: "Developer", active: true }, + { id: 2, name: "Bob", role: "Designer", active: false }, + { id: 3, name: "Charlie", role: "Product Manager", active: true }, + ] + + const [jsonInput, setJsonInput] = useState(JSON.stringify(initialData, null, 2)) + + const result = useMemo(() => { + try { + const parsed = JSON.parse(jsonInput) + + if (!Array.isArray(parsed)) { + return { rows: [] as RowRecord[], columns: [] as string[], error: t("jsonTable.arrayOnlyError") } + } + + const rows: RowRecord[] = parsed.map((item) => { + if (item !== null && typeof item === "object" && !Array.isArray(item)) { + return item as RowRecord + } + return { value: item } + }) + + const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row)))) + + return { rows, columns, error: null } + } catch (e) { + return { + rows: [] as RowRecord[], + columns: [] as string[], + error: `${t("jsonTable.parseError")} ${(e as Error).message}`, + } + } + }, [jsonInput, t]) + + const formatCell = (value: unknown): string => { + if (value === null || value === undefined) return "" + if (typeof value === "object") return JSON.stringify(value) + return String(value) + } + + const exportCsv = useCallback(() => { + if (result.error || result.rows.length === 0 || result.columns.length === 0) return + + const escapeCsv = (value: string) => { + if (value.includes(",") || value.includes('"') || value.includes("\n")) { + return `"${value.replace(/"/g, '""')}"` + } + return value + } + + const header = result.columns.map(escapeCsv).join(",") + const lines = result.rows.map((row) => + result.columns + .map((column) => escapeCsv(formatCell(row[column]))) + .join(",") + ) + const csvContent = [header, ...lines].join("\n") + + const blob = new Blob([`\uFEFF${csvContent}`], { type: "text/csv;charset=utf-8;" }) + const url = URL.createObjectURL(blob) + const link = document.createElement("a") + link.href = url + link.setAttribute("download", "json-table.csv") + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + }, [result, formatCell]) + + return ( +
+
+
+ + {t("jsonTable.jsonInput")} + +
+