From e257d1cc172e022384aa7671ddab741b5d58bdf5 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Mon, 26 Jan 2026 17:13:16 +0100 Subject: [PATCH] refactor dynamic tables to accept initial data as props for improved data handling --- .../commands/commands-table-rsc.tsx | 7 ++ .../commands/dynamic-commands-table.tsx | 9 +- .../commands/hooks/use-commands.ts | 62 ++-------- .../dynamic-placeholders-table.tsx | 9 +- .../placeholder/hooks/use-placeholders.ts | 27 +--- .../placeholder/placeholders-table-rsc.tsx | 7 ++ components/ui/mdx/mdx-components.tsx | 14 ++- lib/docs/eternalcore-data.ts | 116 ++++++++++++++++++ 8 files changed, 168 insertions(+), 83 deletions(-) create mode 100644 components/docs/eternalcore/commands/commands-table-rsc.tsx create mode 100644 components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx create mode 100644 lib/docs/eternalcore-data.ts diff --git a/components/docs/eternalcore/commands/commands-table-rsc.tsx b/components/docs/eternalcore/commands/commands-table-rsc.tsx new file mode 100644 index 00000000..521159ba --- /dev/null +++ b/components/docs/eternalcore/commands/commands-table-rsc.tsx @@ -0,0 +1,7 @@ +import { fetchEternalCoreData } from "@/lib/docs/eternalcore-data"; +import DynamicCommandsTable from "./dynamic-commands-table"; + +export default async function CommandsTableRSC() { + const { commands } = await fetchEternalCoreData(); + return ; +} diff --git a/components/docs/eternalcore/commands/dynamic-commands-table.tsx b/components/docs/eternalcore/commands/dynamic-commands-table.tsx index a6fbad70..3b815707 100644 --- a/components/docs/eternalcore/commands/dynamic-commands-table.tsx +++ b/components/docs/eternalcore/commands/dynamic-commands-table.tsx @@ -1,12 +1,17 @@ "use client"; import { useCommands } from "@/components/docs/eternalcore/commands/hooks/use-commands"; +import type { CommandData } from "@/components/docs/eternalcore/commands/types"; import { CommandsSearchBar } from "./commands-search-bar"; import { CommandsTable } from "./commands-table"; -export default function DynamicCommandsTable() { +interface DynamicCommandsTableProps { + initialData: CommandData[]; +} + +export default function DynamicCommandsTable({ initialData }: DynamicCommandsTableProps) { const { commands, filteredCommands, searchQuery, loading, error, handleSearchChange } = - useCommands(); + useCommands(initialData); if (error) { return
Error: {error}
; diff --git a/components/docs/eternalcore/commands/hooks/use-commands.ts b/components/docs/eternalcore/commands/hooks/use-commands.ts index 6b45a621..1cb485a9 100644 --- a/components/docs/eternalcore/commands/hooks/use-commands.ts +++ b/components/docs/eternalcore/commands/hooks/use-commands.ts @@ -2,8 +2,7 @@ import { create, insert, type Orama, search } from "@orama/orama"; import { type ChangeEvent, useEffect, useState } from "react"; - -import type { CommandData, EternalCoreData } from "@/components/docs/eternalcore/commands/types"; +import type { CommandData } from "@/components/docs/eternalcore/commands/types"; const commandSchema = { name: "string", @@ -14,41 +13,34 @@ const commandSchema = { type CommandDB = Orama; -const REGEX_LEADING_SLASH = /^\//; - -export function useCommands() { - const [commands, setCommands] = useState([]); - const [filteredCommands, setFilteredCommands] = useState([]); +export function useCommands(initialData: CommandData[]) { + const [commands] = useState(initialData); + const [filteredCommands, setFilteredCommands] = useState(initialData); const [searchQuery, setSearchQuery] = useState(""); const [db, setDb] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { - const load = async (): Promise => { + const initDb = async () => { try { setLoading(true); - const all = await fetchAndParseCommands(); - - setCommands(all); - setFilteredCommands(all); - const orama = create({ schema: commandSchema, }); - await insertCommands(orama, all); + await insertCommands(orama, initialData); setDb(orama); } catch (err) { - const message = err instanceof Error ? err.message : "Unknown error"; + const message = err instanceof Error ? err.message : "Failed to initialize search"; setError(message); } finally { setLoading(false); } }; - load(); - }, []); + initDb(); + }, [initialData]); const handleSearch = async (q: string): Promise => { setSearchQuery(q); @@ -84,42 +76,6 @@ export function useCommands() { }; } -async function fetchAndParseCommands(): Promise { - const res = await fetch( - "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_documentation.json" - ); - - if (!res.ok) { - throw new Error("Failed to fetch data"); - } - - const data = (await res.json()) as unknown as EternalCoreData; - - const commandsList = - data.commands?.map((c) => ({ - name: `/${c.name.trim()}`, - permission: c.permissions?.[0] ?? "-", - description: c.descriptions?.[0] ?? "-", - arguments: c.arguments?.join(", ") ?? "-", - })) ?? []; - - const permsList = - data.permissions?.map((p) => ({ - name: p.name || "Unknown", - permission: p.permissions?.[0] ?? "-", - description: p.descriptions?.[0] ?? "-", - arguments: "-", - })) ?? []; - - return [...commandsList, ...permsList].sort((a, b) => - a.name - .replace(REGEX_LEADING_SLASH, "") - .localeCompare(b.name.replace(REGEX_LEADING_SLASH, ""), "pl", { - sensitivity: "base", - }) - ); -} - async function insertCommands(orama: CommandDB, commands: CommandData[]) { for (const c of commands) { await insert(orama, c); diff --git a/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx b/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx index fe34e119..136e1757 100644 --- a/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx +++ b/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx @@ -1,12 +1,17 @@ "use client"; import { usePlaceholders } from "@/components/docs/eternalcore/placeholder/hooks/use-placeholders"; +import type { Placeholder } from "@/components/docs/eternalcore/placeholder/types"; import { DynamicNoPlaceholderMessage } from "./dynamic-no-placeholder-message"; import { PlaceholderCategoryButtons } from "./placeholder-category-buttons"; import { PlaceholderSearchBar } from "./placeholder-search-bar"; import { PlaceholderTable } from "./placeholder-table"; -export default function DynamicPlaceholdersTable() { +interface DynamicPlaceholdersTableProps { + initialData: Placeholder[]; +} + +export default function DynamicPlaceholdersTable({ initialData }: DynamicPlaceholdersTableProps) { const { allPlaceholders, viewablePlaceholders, @@ -17,7 +22,7 @@ export default function DynamicPlaceholdersTable() { loading, handleSearchChange, handleCategoryClick, - } = usePlaceholders(); + } = usePlaceholders(initialData); if (error) { return
Error: {error}
; diff --git a/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts b/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts index ada2f83c..d24f2ded 100644 --- a/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts +++ b/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts @@ -16,9 +16,9 @@ const placeholderSchema = { type PlaceholderDB = Orama; -export function usePlaceholders() { - const [allPlaceholders, setAllPlaceholders] = useState([]); - const [viewablePlaceholders, setViewablePlaceholders] = useState([]); +export function usePlaceholders(initialData: Placeholder[]) { + const [allPlaceholders] = useState(initialData); + const [viewablePlaceholders, setViewablePlaceholders] = useState(initialData); const [categories, setCategories] = useState([]); const [activeCategory, setActiveCategory] = useState("All"); const [searchQuery, setSearchQuery] = useState(""); @@ -31,26 +31,11 @@ export function usePlaceholders() { try { setLoading(true); - const response = await fetch( - "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_placeholders.json" - ); - - if (!response.ok) { - setError(`Failed to fetch data: ${response.statusText}`); - return; - } - - const data = (await response.json()) as Placeholder[]; - const sortedData = [...data].sort((a, b) => a.name.localeCompare(b.name, "pl")); - - setAllPlaceholders(sortedData); - setViewablePlaceholders(sortedData); - - const uniqueCategories = Array.from(new Set(sortedData.map((p) => p.category))).sort(); + const uniqueCategories = Array.from(new Set(initialData.map((p) => p.category))).sort(); setCategories(["All", ...uniqueCategories]); const oramaDb = create({ schema: placeholderSchema }); - await Promise.all(sortedData.map((p) => insert(oramaDb, p))); + await Promise.all(initialData.map((p) => insert(oramaDb, p))); setDb(oramaDb); } catch (exception) { @@ -61,7 +46,7 @@ export function usePlaceholders() { }; initializeData(); - }, []); + }, [initialData]); const filterPlaceholders = useCallback( async (query = searchQuery, category = activeCategory) => { diff --git a/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx b/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx new file mode 100644 index 00000000..036e3b7b --- /dev/null +++ b/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx @@ -0,0 +1,7 @@ +import { fetchEternalCoreData } from "@/lib/docs/eternalcore-data"; +import DynamicPlaceholdersTable from "./dynamic-placeholders-table"; + +export default async function PlaceholdersTableRSC() { + const { placeholders } = await fetchEternalCoreData(); + return ; +} diff --git a/components/ui/mdx/mdx-components.tsx b/components/ui/mdx/mdx-components.tsx index 599601e6..ec883f24 100644 --- a/components/ui/mdx/mdx-components.tsx +++ b/components/ui/mdx/mdx-components.tsx @@ -1,8 +1,9 @@ import type { MDXComponents } from "mdx/types"; +import dynamic from "next/dynamic"; import type { ComponentProps, HTMLAttributes } from "react"; -import DynamicCommandsTable from "@/components/docs/eternalcore/commands/dynamic-commands-table"; -import DynamicPlaceholdersTable from "@/components/docs/eternalcore/placeholder/dynamic-placeholders-table"; +import CommandsTableRSC from "@/components/docs/eternalcore/commands/commands-table-rsc"; +import PlaceholdersTableRSC from "@/components/docs/eternalcore/placeholder/placeholders-table-rsc"; import { AlertBox } from "@/components/ui/alert-box"; import { Badge } from "@/components/ui/mdx/badge"; import { BeforeAfter, BeforeAfterItem } from "@/components/ui/mdx/before-after"; @@ -15,11 +16,14 @@ import { Divider } from "@/components/ui/mdx/divider"; import { FileTree, FileTreeItem } from "@/components/ui/mdx/file-tree"; import { Heading } from "@/components/ui/mdx/heading"; import { Inline } from "@/components/ui/mdx/inline"; -import { LinkPreview } from "@/components/ui/mdx/link-preview"; import { MdxImage } from "@/components/ui/mdx/mdx-image"; import { MdxLink } from "@/components/ui/mdx/mdx-link"; import { Step, Steps } from "@/components/ui/mdx/steps"; +const LinkPreview = dynamic(() => + import("@/components/ui/mdx/link-preview").then((mod) => mod.LinkPreview) +); + type HeadingProps = HTMLAttributes; export const components: MDXComponents = { @@ -75,8 +79,8 @@ export const components: MDXComponents = { FileTree, FileTreeItem, LinkPreview, - DynamicCommandsTable, - DynamicPlaceholdersTable, + DynamicCommandsTable: CommandsTableRSC, + DynamicPlaceholdersTable: PlaceholdersTableRSC, code: ({ children, ...props }: ComponentProps<"code">) => { if (typeof children !== "string") { diff --git a/lib/docs/eternalcore-data.ts b/lib/docs/eternalcore-data.ts new file mode 100644 index 00000000..f3cf5806 --- /dev/null +++ b/lib/docs/eternalcore-data.ts @@ -0,0 +1,116 @@ +import "server-only"; + +export interface CommandData { + name: string; + permission: string; + description: string; + arguments: string; +} + +export interface PlaceholderData { + name: string; + description: string; + category: string; + example: string; + returnType: string; + requiresPlayer: boolean; +} + +interface RawEternalCoreData { + commands?: Array<{ + name: string; + permissions?: string[]; + descriptions?: string[]; + arguments?: string[]; + }>; + permissions?: Array<{ + name: string; + permissions?: string[]; + descriptions?: string[]; + }>; + placeholders?: Array<{ + name: string; + description: string; + category?: string; + }>; +} + +const REGEX_LEADING_SLASH = /^\//; + +export async function fetchEternalCoreData() { + const [commandsRes, placeholdersRes] = await Promise.all([ + fetch( + "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_documentation.json", + { + next: { + revalidate: 3600, + tags: ["eternalcore-data"], + }, + } + ), + fetch( + "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_placeholders.json", + { + next: { + revalidate: 3600, + tags: ["eternalcore-placeholders"], + }, + } + ), + ]); + + if (!commandsRes.ok) { + throw new Error("Failed to fetch EternalCore commands data"); + } + + if (!placeholdersRes.ok) { + throw new Error("Failed to fetch EternalCore placeholders data"); + } + + const commandsData = (await commandsRes.json()) as RawEternalCoreData; + // biome-ignore lint/suspicious/noExplicitAny: External data source + const placeholdersData = (await placeholdersRes.json()) as any[]; + + const commandsList = + commandsData.commands?.map((c) => ({ + name: `/${c.name.trim()}`, + permission: c.permissions?.[0] ?? "-", + description: c.descriptions?.[0] ?? "-", + arguments: c.arguments?.join(", ") ?? "-", + })) ?? []; + + const permsList = + commandsData.permissions?.map((p) => ({ + name: p.name || "Unknown", + permission: p.permissions?.[0] ?? "-", + description: p.descriptions?.[0] ?? "-", + arguments: "-", + })) ?? []; + + const sortedCommands = [...commandsList, ...permsList].sort((a, b) => + a.name + .replace(REGEX_LEADING_SLASH, "") + .localeCompare(b.name.replace(REGEX_LEADING_SLASH, ""), "pl", { + sensitivity: "base", + }) + ); + + const placeholders = + placeholdersData?.map((p) => ({ + name: p.name, + description: p.description, + category: p.category ?? "General", + example: p.example ?? "", + returnType: p.returnType ?? "String", + requiresPlayer: p.requiresPlayer ?? false, + })) ?? []; + + const sortedPlaceholders = placeholders.sort((a, b: { name: string }) => + a.name.localeCompare(b.name) + ); + + return { + commands: sortedCommands, + placeholders: sortedPlaceholders, + }; +}