Pārlūkot izejas kodu

feat(dashboard): Set up basic functionality for component registry

Michael Bromley 10 mēneši atpakaļ
vecāks
revīzija
4a9d6c97cd

+ 79 - 0
packages/dashboard/src/framework/internal/component-registry/component-registry.tsx

@@ -0,0 +1,79 @@
+import { BooleanDisplayCheckbox } from '@/framework/internal/component-registry/data-types/boolean.js';
+import { DateTime } from './data-types/date-time.js';
+
+export interface ComponentRegistryEntry {
+    component: React.ComponentType<any>;
+}
+
+interface ComponentRegistry {
+    type: {
+        [dataType: string]: {
+            display: {
+                [id: string]: ComponentRegistryEntry;
+            };
+        };
+    };
+}
+
+export const COMPONENT_REGISTRY = {
+    type: {
+        boolean: {
+            display: {
+                default: {
+                    component: BooleanDisplayCheckbox,
+                },
+            },
+        },
+        dateTime: {
+            display: {
+                default: {
+                    component: DateTime,
+                },
+            },
+        },
+    },
+} satisfies ComponentRegistry;
+
+type TypeRegistry = (typeof COMPONENT_REGISTRY)['type'];
+type TypeRegistryTypes = keyof TypeRegistry;
+type TypeRegistryCategories<T extends TypeRegistryTypes> = {
+    [K in keyof TypeRegistry[T]]: K;
+}[keyof TypeRegistry[T]];
+type TypeRegistryComponents<
+    T extends TypeRegistryTypes,
+    U extends TypeRegistryCategories<T> = TypeRegistryCategories<T>,
+> = {
+    [K in keyof TypeRegistry[T][U]]: K;
+}[keyof TypeRegistry[T][U]];
+type NonDefaultComponents<
+    T extends TypeRegistryTypes,
+    U extends TypeRegistryCategories<T> = TypeRegistryCategories<T>,
+> = {
+    [K in TypeRegistryComponents<T, U>]: K extends 'default' ? never : `${T}.${U & string}.${K & string}`;
+}[keyof TypeRegistry[T][U]];
+
+type ComponentTypePath<
+    T extends TypeRegistryTypes,
+    U extends TypeRegistryCategories<T> = TypeRegistryCategories<T>,
+> = `${T}.${U & string}` | `${NonDefaultComponents<T, U>}`;
+
+export function useComponentRegistry() {
+    function getComponent<
+        T extends TypeRegistryTypes,
+        U extends TypeRegistryCategories<T> = TypeRegistryCategories<T>,
+    >(path: ComponentTypePath<T>): React.ComponentType<{ value: any }> {
+        const [type, category, componentKey] = path.split('.') as [T, U, string];
+        const availableComponents = COMPONENT_REGISTRY.type[type][category] as any;
+        const componentEntry = availableComponents[componentKey ?? 'default'] as
+            | ComponentRegistryEntry
+            | undefined;
+        if (!componentEntry) {
+            throw new Error(`Component not found for path: ${path}`);
+        }
+        return componentEntry.component;
+    }
+
+    return {
+        getComponent,
+    };
+}

+ 6 - 0
packages/dashboard/src/framework/internal/component-registry/data-types/boolean.tsx

@@ -0,0 +1,6 @@
+import { CheckIcon, XIcon, LucideIcon } from 'lucide-react';
+
+export function BooleanDisplayCheckbox({ value }: { value: boolean }) {
+    console.log(`value`, value);
+    return value ? <CheckIcon /> : <XIcon />;
+}

+ 13 - 0
packages/dashboard/src/framework/internal/component-registry/data-types/date-time.tsx

@@ -0,0 +1,13 @@
+import { useLingui } from '@lingui/react/macro';
+
+export function DateTime({ value }: { value: string | Date }) {
+    const { i18n } = useLingui();
+    let renderedDate: string;
+    try {
+        renderedDate = i18n.date(value);
+    } catch (e) {
+        renderedDate = value.toString();
+        console.error(e);
+    }
+    return <div>{renderedDate}</div>;
+}

+ 1 - 1
packages/dashboard/src/framework/internal/document-introspection/get-document-structure.ts

@@ -7,7 +7,7 @@ import {
 } from 'graphql';
 import { schemaInfo } from 'virtual:admin-api-schema';
 
-interface FieldInfo {
+export interface FieldInfo {
     name: string;
     type: string;
     nullable: boolean;

+ 11 - 3
packages/dashboard/src/framework/internal/page/list-page.tsx

@@ -1,15 +1,14 @@
+import { useComponentRegistry } from '@/framework/internal/component-registry/component-registry.js';
 import { DataTable } from '@/framework/internal/data-table/data-table.js';
 import {
     getListQueryFields,
     getQueryName,
 } from '@/framework/internal/document-introspection/get-document-structure.js';
-import { DateTime } from '@/framework/internal/type-rendering/date-time.js';
 import { api } from '@/graphql/api.js';
 import { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { useQuery } from '@tanstack/react-query';
 import { createColumnHelper } from '@tanstack/react-table';
 import { ResultOf } from 'gql.tada';
-import { DocumentNode } from 'graphql';
 import React from 'react';
 
 type ListQueryFields<T extends TypedDocumentNode> = {
@@ -37,6 +36,7 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
     listQuery,
     customizeColumns,
 }: ListPageProps<T, U>) {
+    const { getComponent } = useComponentRegistry();
     const { data } = useQuery({
         queryFn: () =>
             api.query(listQuery, {
@@ -59,8 +59,16 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
                 if (field.list && Array.isArray(value)) {
                     return value.join(', ');
                 }
+                let Cmp: React.ComponentType<{ value: any }> | undefined = undefined;
                 if ((field.type === 'DateTime' && typeof value === 'string') || value instanceof Date) {
-                    return <DateTime value={value} />;
+                    Cmp = getComponent('dateTime.display');
+                }
+                if (field.type === 'Boolean') {
+                    Cmp = getComponent('boolean.display');
+                }
+
+                if (Cmp) {
+                    return <Cmp value={value} />;
                 }
                 return value;
             },

+ 0 - 6
packages/dashboard/src/framework/internal/type-rendering/date-time.tsx

@@ -1,6 +0,0 @@
-import { useLingui } from '@lingui/react/macro';
-
-export function DateTime({ value }: { value: string | Date }) {
-    const { i18n } = useLingui();
-    return <div>{i18n.date(value)}</div>;
-}

+ 13 - 11
packages/dashboard/vite/api-schema/vite-plugin-admin-api-schema.ts

@@ -86,17 +86,19 @@ export async function adminApiSchemaPlugin(options: { config: VendureConfig }):
     resetConfig();
     await setConfig(options.config ?? {});
 
-    const runtimeConfig = await runPluginConfigurations(getConfig() as any);
-    const typesLoader = new GraphQLTypesLoader();
-    const finalSchema = await getFinalVendureSchema({
-        config: runtimeConfig,
-        typePaths: VENDURE_ADMIN_API_TYPE_PATHS,
-        typesLoader,
-        apiType: 'admin',
-        output: 'sdl',
-    });
-    const safeSchema = buildSchema(finalSchema);
-    schemaInfo = generateSchemaInfo(safeSchema);
+    if (!schemaInfo) {
+        const runtimeConfig = await runPluginConfigurations(getConfig() as any);
+        const typesLoader = new GraphQLTypesLoader();
+        const finalSchema = await getFinalVendureSchema({
+            config: runtimeConfig,
+            typePaths: VENDURE_ADMIN_API_TYPE_PATHS,
+            typesLoader,
+            apiType: 'admin',
+            output: 'sdl',
+        });
+        const safeSchema = buildSchema(finalSchema);
+        schemaInfo = generateSchemaInfo(safeSchema);
+    }
 
     return {
         name: 'vendure-admin-api-schema',