diff --git a/.changeset/famous-pianos-happen.md b/.changeset/famous-pianos-happen.md
deleted file mode 100644
index 5253083ba9..0000000000
--- a/.changeset/famous-pianos-happen.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@comet/admin": patch
----
-
-Fix display of warnings for forms that use both form-level and field-level validation
diff --git a/.changeset/metal-bees-fix.md b/.changeset/metal-bees-fix.md
deleted file mode 100644
index f1250c1c36..0000000000
--- a/.changeset/metal-bees-fix.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@comet/cms-api": patch
----
-
-Add fallback values for users created via ID token
diff --git a/.cspell.json b/.cspell.json
new file mode 100644
index 0000000000..0484a97840
--- /dev/null
+++ b/.cspell.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
+ "version": "0.2",
+ "dictionaryDefinitions": [
+ {
+ "name": "cspellignore",
+ "path": "./.cspellignore",
+ "addWords": true
+ }
+ ],
+ "dictionaries": ["cspellignore"],
+ "ignorePaths": ["node_modules", "/.cspellignore"]
+}
diff --git a/project-words.txt b/.cspellignore
similarity index 93%
rename from project-words.txt
rename to .cspellignore
index 3b7f4f0fc0..b06874f41b 100644
--- a/project-words.txt
+++ b/.cspellignore
@@ -1,24 +1,25 @@
Aktuelles
Betrieb
blockname
+brevo
codemod
codemods
Embeddables
+exceljs
+exif
GraphQLJSONObject
imgproxy
Logische
Mikro
Mimetypes
-NestJS
nestjs
+NestJS
+ormconfig
pagetree
pkey
prebuild
rgba
subcomponent
subpage
+Traefik
typesafe
-exceljs
-ormconfig
-exif
-brevo
\ No newline at end of file
diff --git a/.github/workflows/main-into-next-pr.yml b/.github/workflows/main-into-next-pr.yml
index 990189733e..44b1f60f3b 100644
--- a/.github/workflows/main-into-next-pr.yml
+++ b/.github/workflows/main-into-next-pr.yml
@@ -3,6 +3,7 @@ on:
push:
branches:
- main
+ workflow_dispatch:
jobs:
main-into-next:
name: Create "Merge main into next" PR
diff --git a/cspell.config.yaml b/cspell.config.yaml
deleted file mode 100644
index c81b7d52f2..0000000000
--- a/cspell.config.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-$schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json
-version: "0.2"
-dictionaryDefinitions:
- - name: project-words
- path: "./project-words.txt"
- addWords: true
-dictionaries:
- - project-words
-ignorePaths:
- - "node_modules"
- - "/project-words.txt"
diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx
index 9099e3939f..bedb0ee65b 100644
--- a/demo/admin/src/common/MasterMenu.tsx
+++ b/demo/admin/src/common/MasterMenu.tsx
@@ -27,6 +27,7 @@ import ProductCategoriesPage from "@src/products/categories/ProductCategoriesPag
import { CombinationFieldsTestProductsPage } from "@src/products/future/CombinationFieldsTestProductsPage";
import { CreateCapProductPage as FutureCreateCapProductPage } from "@src/products/future/CreateCapProductPage";
import { ManufacturersPage as FutureManufacturersPage } from "@src/products/future/ManufacturersPage";
+import { ProductCategoriesHandmadePage } from "@src/products/future/ProductCategoriesPage";
import { ProductsPage as FutureProductsPage, ProductsPage } from "@src/products/future/ProductsPage";
import { ProductsWithLowPricePage as FutureProductsWithLowPricePage } from "@src/products/future/ProductsWithLowPricePage";
import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/ManufacturersPage";
@@ -310,6 +311,14 @@ export const masterMenuData: MasterMenuData = [
component: ManufacturersHandmadePage,
},
},
+ {
+ type: "route",
+ primary: ,
+ route: {
+ path: "/product-category-handmade",
+ component: ProductCategoriesHandmadePage,
+ },
+ },
],
},
],
diff --git a/demo/admin/src/products/ManufacturerForm.tsx b/demo/admin/src/products/ManufacturerForm.tsx
index 8bd7ae3228..ae1f6ff272 100644
--- a/demo/admin/src/products/ManufacturerForm.tsx
+++ b/demo/admin/src/products/ManufacturerForm.tsx
@@ -6,15 +6,15 @@ import {
FinalForm,
FinalFormInput,
type FinalFormSubmitEvent,
- FinalFormSwitch,
Loading,
messages,
+ SwitchField,
TextField,
useFormApiRef,
useStackSwitchApi,
} from "@comet/admin";
import { queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin";
-import { Collapse, Divider, FormControlLabel } from "@mui/material";
+import { Collapse, Divider } from "@mui/material";
import { type FormApi } from "final-form";
import isEqual from "lodash.isequal";
import { useMemo } from "react";
@@ -195,183 +195,171 @@ export function ManufacturerForm({ id }: FormProps) {
initialValuesEqual={isEqual} //required to compare block data correctly
subscription={{}}
>
- {() => (
+ <>
+ {saveConflict.dialogs}
<>
- {saveConflict.dialogs}
- <>
-
- }
- supportText={}
- collapsible={true}
- initiallyExpanded={true}
- >
- }
- />
- }
- />
- }
- />
- }
- />
-
- }
- >
- {(props) => (
- }
- label={props.input.checked ? : }
- />
- )}
-
-
- {({ input: { value } }) => (
-
- <>
-
- }
- />
-
- }
- />
-
- }
- />
-
- }
- />
- >
-
- )}
-
-
- }
- >
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
-
- }
- />
- }
- />
- }
- />
-
- >
+
+ }
+ supportText={}
+ collapsible={true}
+ initiallyExpanded={true}
+ >
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ }
+ label={(checked) => (checked ? : )}
+ />
+
+ {({ input: { value } }) => (
+
+ <>
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+ >
+
+ )}
+
+
+ }
+ >
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ }
+ />
+ }
+ />
+ }
+ />
+
>
- )}
+ >
);
}
diff --git a/demo/admin/src/products/categories/ProductCategoriesGrid.tsx b/demo/admin/src/products/categories/ProductCategoriesGrid.tsx
new file mode 100644
index 0000000000..2560af8a1c
--- /dev/null
+++ b/demo/admin/src/products/categories/ProductCategoriesGrid.tsx
@@ -0,0 +1,209 @@
+import { gql, useApolloClient, useQuery } from "@apollo/client";
+import {
+ CrudContextMenu,
+ DataGridToolbar,
+ filterByFragment,
+ type GridColDef,
+ ToolbarActions,
+ useBufferedRowCount,
+ useDataGridRemote,
+ usePersistentColumnState,
+} from "@comet/admin";
+import { useTheme } from "@mui/material";
+import { DataGridPro, type GridRenderCellParams, type GridRowOrderChangeParams, type GridToolbarProps } from "@mui/x-data-grid-pro";
+import { type ReactNode } from "react";
+import { useIntl } from "react-intl";
+
+import {
+ type GQLCreateProductCategoryMutation,
+ type GQLCreateProductCategoryMutationVariables,
+ type GQLDeleteProductCategoryMutation,
+ type GQLDeleteProductCategoryMutationVariables,
+ type GQLProductCategoriesGridQuery,
+ type GQLProductCategoriesGridQueryVariables,
+ type GQLProductCategoryGridFutureFragment,
+ type GQLUpdateProductCategoryPositionMutation,
+ type GQLUpdateProductCategoryPositionMutationVariables,
+} from "./ProductCategoriesGrid.generated";
+
+const productCategoriesFragment = gql`
+ fragment ProductCategoryGridFuture on ProductCategory {
+ id
+ title
+ slug
+ position
+ }
+`;
+
+const productCategoriesQuery = gql`
+ query ProductCategoriesGrid($offset: Int!, $limit: Int!, $sort: [ProductCategorySort!]) {
+ productCategories(offset: $offset, limit: $limit, sort: $sort) {
+ nodes {
+ ...ProductCategoryGridFuture
+ }
+ totalCount
+ }
+ }
+ ${productCategoriesFragment}
+`;
+
+const deleteProductCategoryMutation = gql`
+ mutation DeleteProductCategory($id: ID!) {
+ deleteProductCategory(id: $id)
+ }
+`;
+
+const createProductCategoryMutation = gql`
+ mutation CreateProductCategory($input: ProductCategoryInput!) {
+ createProductCategory(input: $input) {
+ id
+ }
+ }
+`;
+
+const updateProductCategoryPositionMutation = gql`
+ mutation UpdateProductCategoryPosition($id: ID!, $input: ProductCategoryUpdateInput!) {
+ updateProductCategory(id: $id, input: $input) {
+ id
+ updatedAt
+ position
+ }
+ }
+`;
+
+interface ProductCategoriesGridToolbarProps extends GridToolbarProps {
+ toolbarAction?: ReactNode;
+}
+
+function ProductCategoriesGridToolbar({ toolbarAction }: ProductCategoriesGridToolbarProps) {
+ return (
+
+ {toolbarAction}
+
+ );
+}
+
+type Props = {
+ toolbarAction?: ReactNode;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ rowAction?: (params: GridRenderCellParams) => ReactNode;
+ actionsColumnWidth?: number;
+};
+
+export function ProductCategoriesGrid({ toolbarAction, rowAction, actionsColumnWidth = 52 }: Props) {
+ const client = useApolloClient();
+ const intl = useIntl();
+ const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("ProductCategoriesGrid") };
+
+ const handleRowOrderChange = async ({ row: { id }, targetIndex }: GridRowOrderChangeParams) => {
+ await client.mutate({
+ mutation: updateProductCategoryPositionMutation,
+ variables: { id, input: { position: targetIndex + 1 } },
+ awaitRefetchQueries: true,
+ refetchQueries: [productCategoriesQuery],
+ });
+ };
+
+ const theme = useTheme();
+
+ const columns: GridColDef[] = [
+ {
+ field: "title",
+ headerName: intl.formatMessage({ id: "productCategory.title", defaultMessage: "Titel" }),
+ flex: 1,
+ visible: theme.breakpoints.up("md"),
+ minWidth: 150,
+ filterable: false,
+ sortable: false,
+ },
+ {
+ field: "slug",
+ headerName: intl.formatMessage({ id: "productCategory.slug", defaultMessage: "Slug" }),
+ flex: 1,
+ minWidth: 150,
+ filterable: false,
+ sortable: false,
+ },
+ {
+ field: "position",
+ headerName: intl.formatMessage({ id: "productCategory.position", defaultMessage: "Position" }),
+ type: "number",
+ filterable: false,
+ sortable: false,
+ flex: 1,
+ minWidth: 150,
+ },
+ {
+ field: "actions",
+ headerName: "",
+ sortable: false,
+ filterable: false,
+ type: "actions",
+ align: "right",
+ pinned: "right",
+ width: actionsColumnWidth,
+ renderCell: (params) => {
+ return (
+ <>
+ {rowAction && rowAction(params)}
+ {
+ // Don't copy id, because we want to create a new entity with this data
+ const { id, ...filteredData } = filterByFragment(productCategoriesFragment, params.row);
+ return filteredData;
+ }}
+ onPaste={async ({ input }) => {
+ await client.mutate({
+ mutation: createProductCategoryMutation,
+ variables: { input },
+ });
+ }}
+ onDelete={async () => {
+ await client.mutate({
+ mutation: deleteProductCategoryMutation,
+ variables: { id: params.row.id },
+ });
+ }}
+ refetchQueries={[productCategoriesQuery]}
+ />
+ >
+ );
+ },
+ },
+ ];
+
+ const { data, loading, error } = useQuery(productCategoriesQuery, {
+ variables: {
+ offset: 0,
+ limit: 100,
+ sort: { field: "position", direction: "ASC" },
+ },
+ });
+ const rowCount = useBufferedRowCount(data?.productCategories.totalCount);
+ if (error) throw error;
+ const rows =
+ data?.productCategories.nodes.map((node) => ({
+ ...node,
+ __reorder__: node.title,
+ })) ?? [];
+
+ return (
+
+ );
+}
diff --git a/demo/admin/src/products/future/ProductCategoriesPage.tsx b/demo/admin/src/products/future/ProductCategoriesPage.tsx
new file mode 100644
index 0000000000..5727179dbc
--- /dev/null
+++ b/demo/admin/src/products/future/ProductCategoriesPage.tsx
@@ -0,0 +1,23 @@
+import { Stack, StackMainContent, StackPage, StackSwitch, StackToolbar } from "@comet/admin";
+import { ContentScopeIndicator } from "@comet/cms-admin";
+import { ProductCategoriesGrid } from "@src/products/categories/ProductCategoriesGrid";
+import { useIntl } from "react-intl";
+
+export function ProductCategoriesHandmadePage() {
+ const intl = useIntl();
+ return (
+
+
+
+ } />
+
+
+
+
+
+ Add product category
+
+
+
+ );
+}
diff --git a/demo/admin/src/products/future/generated/ProductForm.tsx b/demo/admin/src/products/future/generated/ProductForm.tsx
index 005ed964df..cce199edc9 100644
--- a/demo/admin/src/products/future/generated/ProductForm.tsx
+++ b/demo/admin/src/products/future/generated/ProductForm.tsx
@@ -191,7 +191,7 @@ export function ProductForm({ id }: FormProps) {
{saveConflict.dialogs}
<>
}
supportText={
mode === "edit" && (
diff --git a/demo/admin/src/theme.ts b/demo/admin/src/theme.ts
index 97979b9cdc..93bc781f05 100644
--- a/demo/admin/src/theme.ts
+++ b/demo/admin/src/theme.ts
@@ -1,3 +1,15 @@
-import { createCometTheme } from "@comet/admin";
+import { createCometTheme, DataGridPanel } from "@comet/admin";
+import type {} from "@mui/x-data-grid/themeAugmentation";
-export const theme = createCometTheme();
+export const theme = createCometTheme({
+ components: {
+ MuiDataGrid: {
+ defaultProps: {
+ slots: {
+ // @ts-expect-error @jamesricky fix this please
+ panel: DataGridPanel,
+ },
+ },
+ },
+ },
+});
diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json
index 2471edd7ab..9b5ec165b6 100644
--- a/demo/api/block-meta.json
+++ b/demo/api/block-meta.json
@@ -653,15 +653,15 @@
"blocks": {
"accordion": "Accordion",
"anchor": "Anchor",
- "space": "Space",
- "teaser": "Teaser",
- "richtext": "RichText",
- "heading": "StandaloneHeading",
- "columns": "Columns",
"callToActionList": "StandaloneCallToActionList",
+ "columns": "Columns",
+ "heading": "StandaloneHeading",
"keyFacts": "KeyFacts",
"media": "StandaloneMedia",
- "mediaGallery": "MediaGallery"
+ "mediaGallery": "MediaGallery",
+ "richtext": "RichText",
+ "space": "Space",
+ "teaser": "Teaser"
},
"nullable": false
}
@@ -697,15 +697,15 @@
"blocks": {
"accordion": "Accordion",
"anchor": "Anchor",
- "space": "Space",
- "teaser": "Teaser",
- "richtext": "RichText",
- "heading": "StandaloneHeading",
- "columns": "Columns",
"callToActionList": "StandaloneCallToActionList",
+ "columns": "Columns",
+ "heading": "StandaloneHeading",
"keyFacts": "KeyFacts",
"media": "StandaloneMedia",
- "mediaGallery": "MediaGallery"
+ "mediaGallery": "MediaGallery",
+ "richtext": "RichText",
+ "space": "Space",
+ "teaser": "Teaser"
},
"nullable": false
}
diff --git a/demo/api/package.json b/demo/api/package.json
index dfd1cd1484..96a059de0d 100644
--- a/demo/api/package.json
+++ b/demo/api/package.json
@@ -20,8 +20,6 @@
"lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'",
"lint:tsc": "tsc --project ./tsconfig.lint.json",
"mikro-orm": "dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- mikro-orm",
- "mikro-orm:drop": "mikro-orm schema:drop -r",
- "mikro-orm:migration:generate": "mikro-orm migration:create",
"start": "$npm_execpath prebuild && dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- nest start --debug --watch --preserveWatchOutput",
"start:dev": "run-p dev:*",
"start:prod": "node dist/main"
@@ -50,6 +48,7 @@
"@sentry/node": "^9.2.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
+ "cli-progress": "^3.12.0",
"compression": "^1.8.0",
"cookie-parser": "^1.4.7",
"express": "^5.0.1",
@@ -69,6 +68,7 @@
"@comet/eslint-config": "workspace:*",
"@nestjs/cli": "^11.0.4",
"@nestjs/schematics": "^11.0.1",
+ "@types/cli-progress": "^3.11.6",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.0",
diff --git a/demo/api/schema.gql b/demo/api/schema.gql
index 02499a1ae5..cb14bb634d 100644
--- a/demo/api/schema.gql
+++ b/demo/api/schema.gql
@@ -204,21 +204,53 @@ type UserPermissionPaginatedUserList {
totalCount: Int!
}
-type Link implements DocumentInterface {
+type DamScope {
+ domain: String!
+}
+
+type DamFolder {
id: ID!
- updatedAt: DateTime!
- content: LinkBlockData!
+ name: String!
+ numberOfChildFolders: Int!
+ numberOfFiles: Int!
+ mpath: [ID!]!
+ archived: Boolean!
+ isInboxFromOtherScope: Boolean!
createdAt: DateTime!
- pageTreeNode: PageTreeNode
+ updatedAt: DateTime!
+ scope: DamScope!
+ parent: DamFolder
+ parents: [DamFolder!]!
}
-interface DocumentInterface {
+type DamFile {
id: ID!
+ folder: DamFolder
+ name: String!
+ size: Int!
+ mimetype: String!
+ contentHash: String!
+ title: String
+ altText: String
+ archived: Boolean!
+ image: DamFileImage
+ license: DamFileLicense
+ createdAt: DateTime!
updatedAt: DateTime!
+ importSourceId: String
+ importSourceType: String
+ scope: DamScope!
+ fileUrl: String!
+ duplicates: [DamFile!]!
+ damPath: String!
+ dependents(offset: Int! = 0, limit: Int! = 25, filter: DependentFilter, forceRefresh: Boolean! = false): PaginatedDependencies!
}
-"""Link root block data"""
-scalar LinkBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+input DependentFilter {
+ rootGraphqlObjectType: String
+ rootId: String
+ rootColumnName: String
+}
type Page implements DocumentInterface {
id: ID!
@@ -230,6 +262,11 @@ type Page implements DocumentInterface {
pageTreeNode: PageTreeNode
}
+interface DocumentInterface {
+ id: ID!
+ updatedAt: DateTime!
+}
+
"""PageContent root block data"""
scalar PageContentBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
@@ -396,6 +433,17 @@ A date string, such as 2007-12-03, compliant with the `full-date` format outline
"""
scalar Date
+type Link implements DocumentInterface {
+ id: ID!
+ updatedAt: DateTime!
+ content: LinkBlockData!
+ createdAt: DateTime!
+ pageTreeNode: PageTreeNode
+}
+
+"""Link root block data"""
+scalar LinkBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+
type PageTreeNodeScope {
domain: String!
language: String!
@@ -442,54 +490,6 @@ enum UserGroup {
union PageContentUnion = Page | Link | PredefinedPage
-input DependentFilter {
- rootGraphqlObjectType: String
- rootId: String
- rootColumnName: String
-}
-
-type DamScope {
- domain: String!
-}
-
-type DamFolder {
- id: ID!
- name: String!
- numberOfChildFolders: Int!
- numberOfFiles: Int!
- mpath: [ID!]!
- archived: Boolean!
- isInboxFromOtherScope: Boolean!
- createdAt: DateTime!
- updatedAt: DateTime!
- scope: DamScope!
- parent: DamFolder
- parents: [DamFolder!]!
-}
-
-type DamFile {
- id: ID!
- folder: DamFolder
- name: String!
- size: Int!
- mimetype: String!
- contentHash: String!
- title: String
- altText: String
- archived: Boolean!
- image: DamFileImage
- license: DamFileLicense
- createdAt: DateTime!
- updatedAt: DateTime!
- importSourceId: String
- importSourceType: String
- scope: DamScope!
- fileUrl: String!
- duplicates: [DamFile!]!
- damPath: String!
- dependents(offset: Int! = 0, limit: Int! = 25, filter: DependentFilter, forceRefresh: Boolean! = false): PaginatedDependencies!
-}
-
type PredefinedPage implements DocumentInterface {
id: ID!
updatedAt: DateTime!
@@ -686,6 +686,10 @@ type PaginatedDamFolders {
totalCount: Int!
}
+input DamScopeInput {
+ domain: String!
+}
+
input AlternativeAddressInput {
street: String!
streetNumber: Float
@@ -737,10 +741,6 @@ input PageTreeNodeScopeInput {
language: String!
}
-input DamScopeInput {
- domain: String!
-}
-
input FooterScopeInput {
domain: String!
language: String!
diff --git a/demo/api/src/common/blocks/accordion-item.block.ts b/demo/api/src/common/blocks/accordion-item.block.ts
index 0004165170..97fa489b83 100644
--- a/demo/api/src/common/blocks/accordion-item.block.ts
+++ b/demo/api/src/common/blocks/accordion-item.block.ts
@@ -17,7 +17,7 @@ import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-c
import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block";
import { IsBoolean, IsString } from "class-validator";
-const AccordionContentBlock = createBlocksBlock(
+export const AccordionContentBlock = createBlocksBlock(
{
supportedBlocks: {
richtext: RichTextBlock,
diff --git a/demo/api/src/common/blocks/call-to-action.block.ts b/demo/api/src/common/blocks/call-to-action.block.ts
index 9dc54ca60b..3adc1791a1 100644
--- a/demo/api/src/common/blocks/call-to-action.block.ts
+++ b/demo/api/src/common/blocks/call-to-action.block.ts
@@ -13,7 +13,7 @@ import { IsEnum } from "class-validator";
import { TextLinkBlock } from "./text-link.block";
-enum Variant {
+export enum Variant {
Contained = "Contained",
Outlined = "Outlined",
Text = "Text",
diff --git a/demo/api/src/common/blocks/heading.block.ts b/demo/api/src/common/blocks/heading.block.ts
index f1aab09df9..d5a253d840 100644
--- a/demo/api/src/common/blocks/heading.block.ts
+++ b/demo/api/src/common/blocks/heading.block.ts
@@ -13,7 +13,7 @@ import { IsEnum } from "class-validator";
import { RichTextBlock } from "./rich-text.block";
-enum HeadlineTag {
+export enum HeadlineTag {
H1 = "H1",
H2 = "H2",
H3 = "H3",
diff --git a/demo/api/src/common/blocks/media-gallery.block.ts b/demo/api/src/common/blocks/media-gallery.block.ts
index 0b8238cb9c..3736d2e84c 100644
--- a/demo/api/src/common/blocks/media-gallery.block.ts
+++ b/demo/api/src/common/blocks/media-gallery.block.ts
@@ -14,7 +14,7 @@ import { MediaGalleryItemBlock } from "@src/common/blocks/media-gallery-item.blo
import { MediaAspectRatios } from "@src/util/mediaAspectRatios";
import { IsEnum } from "class-validator";
-const MediaGalleryListBlock = createListBlock({ block: MediaGalleryItemBlock }, "MediaGalleryList");
+export const MediaGalleryListBlock = createListBlock({ block: MediaGalleryItemBlock }, "MediaGalleryList");
class MediaGalleryBlockData extends BlockData {
@ChildBlock(MediaGalleryListBlock)
diff --git a/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts
index 241f27d850..34c1e37a2f 100644
--- a/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts
+++ b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts
@@ -12,7 +12,7 @@ import {
import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block";
import { IsEnum } from "class-validator";
-enum Alignment {
+export enum Alignment {
left = "left",
center = "center",
right = "right",
diff --git a/demo/api/src/common/blocks/standalone-heading.block.ts b/demo/api/src/common/blocks/standalone-heading.block.ts
index 08f6d785aa..8b0a8eaddc 100644
--- a/demo/api/src/common/blocks/standalone-heading.block.ts
+++ b/demo/api/src/common/blocks/standalone-heading.block.ts
@@ -12,7 +12,7 @@ import {
import { HeadingBlock } from "@src/common/blocks/heading.block";
import { IsEnum } from "class-validator";
-enum TextAlignment {
+export enum TextAlignment {
left = "left",
center = "center",
}
diff --git a/demo/api/src/db/fixtures/generators/images/01.jpg b/demo/api/src/db/fixtures/assets/images/01.jpg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/01.jpg
rename to demo/api/src/db/fixtures/assets/images/01.jpg
diff --git a/demo/api/src/db/fixtures/generators/images/02.jpg b/demo/api/src/db/fixtures/assets/images/02.jpg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/02.jpg
rename to demo/api/src/db/fixtures/assets/images/02.jpg
diff --git a/demo/api/src/db/fixtures/generators/images/03.jpg b/demo/api/src/db/fixtures/assets/images/03.jpg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/03.jpg
rename to demo/api/src/db/fixtures/assets/images/03.jpg
diff --git a/demo/api/src/db/fixtures/generators/images/04.jpg b/demo/api/src/db/fixtures/assets/images/04.jpg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/04.jpg
rename to demo/api/src/db/fixtures/assets/images/04.jpg
diff --git a/demo/api/src/db/fixtures/generators/images/05.jpg b/demo/api/src/db/fixtures/assets/images/05.jpg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/05.jpg
rename to demo/api/src/db/fixtures/assets/images/05.jpg
diff --git a/demo/api/src/db/fixtures/generators/images/astronaut.png b/demo/api/src/db/fixtures/assets/images/astronaut.png
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/astronaut.png
rename to demo/api/src/db/fixtures/assets/images/astronaut.png
diff --git a/demo/api/src/db/fixtures/generators/images/comet.png b/demo/api/src/db/fixtures/assets/images/comet.png
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/comet.png
rename to demo/api/src/db/fixtures/assets/images/comet.png
diff --git a/demo/api/src/db/fixtures/generators/images/planet.png b/demo/api/src/db/fixtures/assets/images/planet.png
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/planet.png
rename to demo/api/src/db/fixtures/assets/images/planet.png
diff --git a/demo/api/src/db/fixtures/generators/images/rocket.png b/demo/api/src/db/fixtures/assets/images/rocket.png
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/rocket.png
rename to demo/api/src/db/fixtures/assets/images/rocket.png
diff --git a/demo/api/src/db/fixtures/generators/images/sun.png b/demo/api/src/db/fixtures/assets/images/sun.png
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/sun.png
rename to demo/api/src/db/fixtures/assets/images/sun.png
diff --git a/demo/api/src/db/fixtures/generators/images/comet-logo-claim.svg b/demo/api/src/db/fixtures/assets/svg/comet-logo-claim.svg
similarity index 100%
rename from demo/api/src/db/fixtures/generators/images/comet-logo-claim.svg
rename to demo/api/src/db/fixtures/assets/svg/comet-logo-claim.svg
diff --git a/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4 b/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4
new file mode 100644
index 0000000000..eb6bfea9d1
Binary files /dev/null and b/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4 differ
diff --git a/demo/api/src/db/fixtures/fixtures.command.ts b/demo/api/src/db/fixtures/fixtures.command.ts
index d2f8a4e7ec..9403f7d537 100644
--- a/demo/api/src/db/fixtures/fixtures.command.ts
+++ b/demo/api/src/db/fixtures/fixtures.command.ts
@@ -2,26 +2,27 @@ import { BlobStorageBackendService, DependenciesService, PageTreeNodeInterface,
import { faker } from "@faker-js/faker";
import { InjectRepository } from "@mikro-orm/nestjs";
import { CreateRequestContext, EntityManager, EntityRepository, MikroORM } from "@mikro-orm/postgresql";
-import { Inject } from "@nestjs/common";
+import { Inject, Logger } from "@nestjs/common";
import { Config } from "@src/config/config";
import { CONFIG } from "@src/config/config.module";
import { generateSeoBlock } from "@src/db/fixtures/generators/blocks/seo.generator";
-import { Link } from "@src/documents/links/entities/link.entity";
import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block";
import { StageBlock } from "@src/documents/pages/blocks/stage.block";
import { PageInput } from "@src/documents/pages/dto/page.input";
import { Page } from "@src/documents/pages/entities/page.entity";
-import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope";
import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category";
import { UserGroup } from "@src/user-groups/user-group";
+import { MultiBar, Options, Presets } from "cli-progress";
import { Command, CommandRunner } from "nest-commander";
import slugify from "slugify";
+import { DocumentGeneratorService } from "./generators/document-generator.service";
import { FileUploadsFixtureService } from "./generators/file-uploads-fixture.service";
-import { generateLinks } from "./generators/links.generator";
+import { ImageFixtureService } from "./generators/image-fixture.service";
import { ManyImagesTestPageFixtureService } from "./generators/many-images-test-page-fixture.service";
import { ProductsFixtureService } from "./generators/products-fixture.service";
import { RedirectsFixtureService } from "./generators/redirects-fixture.service";
+import { VideoFixtureService } from "./generators/video-fixture.service";
export interface PageTreeNodesFixtures {
home?: PageTreeNodeInterface;
@@ -45,190 +46,92 @@ const getDefaultPageInput = (): PageInput => {
description: "Create fixtures with faker.js",
})
export class FixturesCommand extends CommandRunner {
+ private readonly logger = new Logger(FixturesCommand.name);
+
+ barOptions: Options = {
+ format: `{bar} {percentage}% | {value}/{total} {title} | ETA: {eta_formatted} | Duration: {duration_formatted}`,
+ noTTYOutput: true,
+ };
+
constructor(
@Inject(CONFIG) private readonly config: Config,
private readonly blobStorageBackendService: BlobStorageBackendService,
- private readonly pageTreeService: PageTreeService,
- private readonly orm: MikroORM,
- @InjectRepository(Page) private readonly pagesRepository: EntityRepository,
- @InjectRepository(Link) private readonly linksRepository: EntityRepository,
- private readonly manyImagesTestPageFixtureService: ManyImagesTestPageFixtureService,
- private readonly fileUploadsFixtureService: FileUploadsFixtureService,
- private readonly redirectsFixtureService: RedirectsFixtureService,
+ private readonly documentGeneratorService: DocumentGeneratorService,
private readonly dependenciesService: DependenciesService,
private readonly entityManager: EntityManager,
private readonly productsFixtureService: ProductsFixtureService,
+ private readonly fileUploadsFixtureService: FileUploadsFixtureService,
+ private readonly imageFixtureService: ImageFixtureService,
+ private readonly manyImagesTestPageFixtureService: ManyImagesTestPageFixtureService,
+ private readonly orm: MikroORM,
+ @InjectRepository(Page) private readonly pagesRepository: EntityRepository,
+ private readonly pageTreeService: PageTreeService,
+ private readonly redirectsFixtureService: RedirectsFixtureService,
+ private readonly videoFixtureService: VideoFixtureService,
) {
super();
}
@CreateRequestContext()
async run(): Promise {
- const pageTreeNodes: PageTreeNodesFixtures = {};
// ensure repeatable runs
faker.seed(123456);
+ this.logger.log("Drop tables...");
+ const connection = this.orm.em.getConnection();
+ const tables = await connection.execute(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' ORDER BY tablename;`);
+
+ for (const table of tables) {
+ await connection.execute(`DROP TABLE IF EXISTS "${table.tablename}" CASCADE`);
+ }
+
+ this.logger.log("Clear storage...");
const damFilesDirectory = `${this.config.blob.storageDirectoryPrefix}-files`;
if (await this.blobStorageBackendService.folderExists(damFilesDirectory)) {
await this.blobStorageBackendService.removeFolder(damFilesDirectory);
}
await this.blobStorageBackendService.createFolder(damFilesDirectory);
- console.log("Storage cleared");
- const generator = this.orm.getSchemaGenerator();
- console.log(`Drop and recreate schema...`);
- await generator.dropSchema({ dropDb: false, dropMigrationsTable: true });
-
- console.log(`Run migrations...`);
+ this.logger.log("Run migrations...");
const migrator = this.orm.getMigrator();
await migrator.up();
- const scope: PageTreeNodeScope = {
- domain: "main",
- language: "en",
- };
+ const scope = { domain: "main", language: "en" };
- const attachedDocumentIds = [
- "b0bf3927-e080-4d4e-997b-d7f18b051e0f",
- "4ff8707a-4bb7-45ab-b06d-72a3e94286c7",
- "729c37f5-c28b-45d4-88a8-a89abc579c07",
- "46ce964c-f029-46f0-9961-ef436e2391f2",
- "dc956c9a-729b-4d8e-9ed4-f1be1dad6dd3",
- ];
+ const multiBar = new MultiBar(this.barOptions, Presets.shades_classic);
- let node = await this.pageTreeService.createNode(
- {
- name: "Home",
- slug: "home",
- attachedDocument: {
- id: attachedDocumentIds[0],
- type: "Page",
- },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
- scope,
- );
- pageTreeNodes.home = node;
+ this.logger.log("Generate Images...");
+ await this.imageFixtureService.generateImages(5, { domain: "main" });
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
+ this.logger.log("Generate Videos...");
+ await this.videoFixtureService.generateVideos({ domain: "main" });
- node = await this.pageTreeService.createNode(
- {
- id: "aaa585d3-eca1-47c9-8852-9370817b49ac",
- name: "Sub",
- slug: "sub",
- parentId: node.id,
- attachedDocument: { id: attachedDocumentIds[1], type: "Page" },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
- scope,
- );
- pageTreeNodes.sub = node;
-
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
-
- node = await this.pageTreeService.createNode(
- {
- name: "Test 2",
- slug: "test2",
- attachedDocument: {
- id: attachedDocumentIds[2],
- type: "Page",
- },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
- scope,
- );
- pageTreeNodes.test2 = node;
-
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
+ this.logger.log("Generate Pages...");
+ await this.documentGeneratorService.generatePage({ name: "Home", scope });
+ const blockCategoriesPage = await this.documentGeneratorService.generatePage({ name: "Fixtures: Blocks", scope });
- node = await this.pageTreeService.createNode(
- {
- name: "Test 3",
- slug: "test3",
- attachedDocument: {
- type: "Page",
- },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
+ await this.documentGeneratorService.generatePage({ name: "Layout", scope, blockCategory: "layout", parentId: blockCategoriesPage.id });
+ await this.documentGeneratorService.generatePage({ name: "Media", scope, blockCategory: "media", parentId: blockCategoriesPage.id });
+ await this.documentGeneratorService.generatePage({
+ name: "Navigation",
scope,
- );
- pageTreeNodes.test3 = node;
-
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
-
- node = await this.pageTreeService.createNode(
- {
- name: "Link1",
- slug: "link",
- attachedDocument: {
- id: attachedDocumentIds[3],
- type: "Link",
- },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
- scope,
- );
- pageTreeNodes.link1 = node;
-
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
-
- node = await this.pageTreeService.createNode(
- {
- name: "Test Site visibility",
- slug: "test-site-visibility",
- attachedDocument: {
- id: attachedDocumentIds[4],
- type: "Page",
- },
- // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
- userGroup: UserGroup.All,
- },
- PageTreeNodeCategory.MainNavigation,
+ blockCategory: "navigation",
+ parentId: blockCategoriesPage.id,
+ });
+ await this.documentGeneratorService.generatePage({ name: "Teaser", scope, blockCategory: "teaser", parentId: blockCategoriesPage.id });
+ await this.documentGeneratorService.generatePage({
+ name: "Text and Content",
scope,
- );
- pageTreeNodes.testSiteVisibility = node;
-
- await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
-
- for (const attachedDocumentId of attachedDocumentIds) {
- const pageInput = getDefaultPageInput();
-
- await this.entityManager.persistAndFlush(
- this.pagesRepository.create({
- id: attachedDocumentId,
- createdAt: new Date(),
- updatedAt: new Date(),
- content: pageInput.content.transformToBlockData(),
- seo: pageInput.seo.transformToBlockData(),
- stage: pageInput.stage.transformToBlockData(),
- }),
- );
- }
+ blockCategory: "textAndContent",
+ parentId: blockCategoriesPage.id,
+ });
- console.log("generate links");
- await generateLinks(this.linksRepository, pageTreeNodes);
- console.log("links generated");
-
- console.log("generate many images test page");
+ this.logger.log("Generate Many Images Test Page...");
await this.manyImagesTestPageFixtureService.execute();
- console.log("many images test page created");
-
- console.log("generate lorem ispum fixtures");
+ this.logger.log("Many Images Test Page created");
+ this.logger.log("Generate Lorem Ispum Fixtures...");
const NUMBER_OF_DOMAINS_WITH_LORUM_IPSUM_CONTENT = 0; // Increase number to generate lorum ipsum fixtures
-
for (let domainNum = 0; domainNum < NUMBER_OF_DOMAINS_WITH_LORUM_IPSUM_CONTENT; domainNum++) {
const domain = domainNum === 0 ? "secondary" : `${faker.lorem.word().toLowerCase()}.com`;
let pagesCount = 0;
@@ -282,13 +185,17 @@ export class FixturesCommand extends CommandRunner {
pages.push(pagesForLevel);
}
- console.log(`Generated ${pagesCount} lorem ipsum pages for ${domain}`);
+ this.logger.log(`Generated ${pagesCount} lorem ipsum pages for ${domain}`);
}
+ this.logger.log("Generate File Uploads...");
await this.fileUploadsFixtureService.generateFileUploads();
+ this.logger.log("Generate Redirects...");
await this.redirectsFixtureService.generateRedirects();
+ multiBar.stop();
+
await this.dependenciesService.createViews();
await this.productsFixtureService.generate();
diff --git a/demo/api/src/db/fixtures/fixtures.module.ts b/demo/api/src/db/fixtures/fixtures.module.ts
index 92b7e752c8..fb562dd249 100644
--- a/demo/api/src/db/fixtures/fixtures.module.ts
+++ b/demo/api/src/db/fixtures/fixtures.module.ts
@@ -2,6 +2,7 @@ import { DependenciesModule } from "@comet/cms-api";
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { Module } from "@nestjs/common";
import { ConfigModule } from "@src/config/config.module";
+import { DamFile } from "@src/dam/entities/dam-file.entity";
import { FixturesCommand } from "@src/db/fixtures/fixtures.command";
import { Link } from "@src/documents/links/entities/link.entity";
import { LinksModule } from "@src/documents/links/links.module";
@@ -10,23 +11,96 @@ import { PagesModule } from "@src/documents/pages/pages.module";
import { Manufacturer } from "@src/products/entities/manufacturer.entity";
import { Product } from "@src/products/entities/product.entity";
+import { AccordionBlockFixtureService } from "./generators/blocks/layout/accordion-block-fixture.service";
+import { ColumnsBlockFixtureService } from "./generators/blocks/layout/columns-block-fixture.service";
+import { ContentGroupBlockFixtureService } from "./generators/blocks/layout/content-group-block-fixture.service";
+import { LayoutBlockFixtureService } from "./generators/blocks/layout/layout-block-fixture.service";
+import { SpaceBlockFixtureService } from "./generators/blocks/layout/space-block-fixture.service";
+import { DamImageBlockFixtureService } from "./generators/blocks/media/dam-image-block-fixture.service";
+import { DamVideoBlockFixtureService } from "./generators/blocks/media/dam-video-block-fixture.service";
+import { FullWidthImageBlockFixtureService } from "./generators/blocks/media/full-width-image-block-fixture.service";
+import { MediaBlockFixtureService } from "./generators/blocks/media/media-block.fixture.service";
+import { MediaGalleryBlockFixtureService } from "./generators/blocks/media/media-gallery-block-fixture.service";
+import { PixelImageBlockFixtureService } from "./generators/blocks/media/pixel-image-block-fixture.service";
+import { StandaloneMediaBlockFixtureService } from "./generators/blocks/media/standalone-media-block-fixture.service";
+import { SvgImageBlockFixtureService } from "./generators/blocks/media/svg-image-block-fixture.service";
+import { VimeoVideoBlockFixtureService } from "./generators/blocks/media/vimeo-video-block-fixture.service";
+import { YouTubeVideoBlockFixtureService } from "./generators/blocks/media/youtube-video-block-fixture.service";
+import { AnchorBlockFixtureService } from "./generators/blocks/navigation/anchor-block-fixture.service";
+import { CallToActionBlockFixtureService } from "./generators/blocks/navigation/call-to-action-block-fixture.service";
+import { CallToActionListBlockFixtureService } from "./generators/blocks/navigation/call-to-action-list-block.service";
+import { LinkBlockFixtureService } from "./generators/blocks/navigation/link-block-fixture.service";
+import { LinkListBlockFixtureService } from "./generators/blocks/navigation/link-list-block-fixture.service";
+import { StandaloneCallToActionListBlockFixtureService } from "./generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service";
+import { TextLinkBlockFixtureService } from "./generators/blocks/navigation/text-link-block-fixture.service";
+import { BasicStageBlockFixtureService } from "./generators/blocks/stage/basic-stage-block-fixture.service";
+import { BillboardTeaserBlockFixtureService } from "./generators/blocks/teaser/billboard-teaser-block-fixture.service";
+import { TeaserBlockFixtureService } from "./generators/blocks/teaser/teaser-block-fixture.service";
+import { HeadingBlockFixtureService } from "./generators/blocks/text-and-content/heading-block-fixture.service";
+import { KeyFactsBlockFixtureService } from "./generators/blocks/text-and-content/key-facts-block-fixture.service";
+import { RichTextBlockFixtureService } from "./generators/blocks/text-and-content/rich-text-block-fixture.service";
+import { StandaloneHeadingBlockFixtureService } from "./generators/blocks/text-and-content/standalone-heading-block-fixture.service";
+import { TextImageBlockFixtureService } from "./generators/blocks/text-and-content/text-image-block-fixture.service";
+import { DocumentGeneratorService } from "./generators/document-generator.service";
import { FileUploadsFixtureService } from "./generators/file-uploads-fixture.service";
import { ImageFileFixtureService } from "./generators/image-file-fixture.service";
+import { ImageFixtureService } from "./generators/image-fixture.service";
import { ManyImagesTestPageFixtureService } from "./generators/many-images-test-page-fixture.service";
+import { PageContentBlockFixtureService } from "./generators/page-content-block-fixture.service";
import { ProductsFixtureService } from "./generators/products-fixture.service";
import { RedirectsFixtureService } from "./generators/redirects-fixture.service";
+import { SeoBlockFixtureService } from "./generators/seo-block-fixture.service";
+import { StageBlockFixtureService } from "./generators/stage-block-fixture.service";
import { SvgImageFileFixtureService } from "./generators/svg-image-file-fixture.service";
+import { VideoFixtureService } from "./generators/video-fixture.service";
@Module({
- imports: [ConfigModule, PagesModule, LinksModule, DependenciesModule, MikroOrmModule.forFeature([Page, Link, Product, Manufacturer])],
+ imports: [ConfigModule, PagesModule, LinksModule, DependenciesModule, MikroOrmModule.forFeature([DamFile, Page, Link, Product, Manufacturer])],
providers: [
FixturesCommand,
- ManyImagesTestPageFixtureService,
- ImageFileFixtureService,
- SvgImageFileFixtureService,
+ AccordionBlockFixtureService,
+ AnchorBlockFixtureService,
+ BasicStageBlockFixtureService,
+ BillboardTeaserBlockFixtureService,
+ CallToActionBlockFixtureService,
+ CallToActionListBlockFixtureService,
+ ColumnsBlockFixtureService,
+ ContentGroupBlockFixtureService,
+ DamImageBlockFixtureService,
+ DamVideoBlockFixtureService,
+ DocumentGeneratorService,
FileUploadsFixtureService,
+ FullWidthImageBlockFixtureService,
+ HeadingBlockFixtureService,
+ ImageFileFixtureService,
+ ImageFixtureService,
+ KeyFactsBlockFixtureService,
+ LayoutBlockFixtureService,
+ LinkBlockFixtureService,
+ LinkListBlockFixtureService,
+ ManyImagesTestPageFixtureService,
+ MediaGalleryBlockFixtureService,
+ MediaBlockFixtureService,
+ PageContentBlockFixtureService,
+ PixelImageBlockFixtureService,
RedirectsFixtureService,
ProductsFixtureService,
+ RichTextBlockFixtureService,
+ SeoBlockFixtureService,
+ SpaceBlockFixtureService,
+ StageBlockFixtureService,
+ SvgImageBlockFixtureService,
+ SvgImageFileFixtureService,
+ StageBlockFixtureService,
+ StandaloneCallToActionListBlockFixtureService,
+ StandaloneHeadingBlockFixtureService,
+ StandaloneMediaBlockFixtureService,
+ TeaserBlockFixtureService,
+ TextImageBlockFixtureService,
+ TextLinkBlockFixtureService,
+ VideoFixtureService,
+ VimeoVideoBlockFixtureService,
+ YouTubeVideoBlockFixtureService,
],
})
export class FixturesModule {}
diff --git a/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts b/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts
new file mode 100644
index 0000000000..d94a458287
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts
@@ -0,0 +1,3 @@
+import { type Block, type ExtractBlockInputFactoryProps } from "@comet/cms-api";
+
+export type BlockFixture = { generateBlockInput: () => Promise> };
diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts
new file mode 100644
index 0000000000..94091cebcb
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts
@@ -0,0 +1,73 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { AccordionBlock } from "@src/common/blocks/accordion.block";
+import { AccordionContentBlock, AccordionItemBlock } from "@src/common/blocks/accordion-item.block";
+
+import { BlockFixture } from "../block-fixture";
+import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service";
+import { SpaceBlockFixtureService } from "./space-block-fixture.service";
+
+@Injectable()
+export class AccordionBlockFixtureService {
+ constructor(
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService,
+ private readonly spaceBlockFixtureService: SpaceBlockFixtureService,
+ private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService,
+ ) {}
+
+ async generateAccordionContentBlock(): Promise> {
+ const blocks: ExtractBlockInputFactoryProps["blocks"] = [];
+ const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = {
+ richtext: this.richTextBlockFixtureService,
+ heading: this.headingBlockFixtureService,
+ space: this.spaceBlockFixtureService,
+ callToActionList: this.callToActionListBlockFixtureService,
+ };
+
+ for (const block of Object.entries(blockCfg)) {
+ const [type, generator] = block;
+ const props = await generator.generateBlockInput();
+
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ type: type as keyof typeof blockCfg,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ props: props as any,
+ });
+ }
+
+ return {
+ blocks,
+ };
+ }
+
+ async generateAccordionItemBlock(): Promise> {
+ return {
+ title: faker.lorem.words({ min: 3, max: 9 }),
+ content: await this.generateAccordionContentBlock(),
+ openByDefault: faker.datatype.boolean(),
+ };
+ }
+
+ async generateBlockInput(min = 2, max = 6): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.generateAccordionItemBlock(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts
new file mode 100644
index 0000000000..5c4561e13f
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts
@@ -0,0 +1,75 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { ColumnsBlock, ColumnsContentBlock } from "@src/documents/pages/blocks/columns.block";
+
+import { BlockFixture } from "../block-fixture";
+import { MediaGalleryBlockFixtureService } from "../media/media-gallery-block-fixture.service";
+import { StandaloneMediaBlockFixtureService } from "../media/standalone-media-block-fixture.service";
+import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service";
+import { AccordionBlockFixtureService } from "./accordion-block-fixture.service";
+import { SpaceBlockFixtureService } from "./space-block-fixture.service";
+
+const oneColumnLayouts = [{ name: "2-20-2" }];
+
+@Injectable()
+export class ColumnsBlockFixtureService {
+ constructor(
+ private readonly accordionBlockFixtureService: AccordionBlockFixtureService,
+ private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService,
+ private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService,
+ private readonly richtextBlockFixtureService: RichTextBlockFixtureService,
+ private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService,
+ private readonly spaceBlockFixtureService: SpaceBlockFixtureService,
+ private readonly standaloneMediaBlockFixtureService: StandaloneMediaBlockFixtureService,
+ ) {}
+
+ async generateColumnsContentBlock(): Promise> {
+ const blocks: ExtractBlockInputFactoryProps["blocks"] = [];
+
+ const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = {
+ accordion: this.accordionBlockFixtureService,
+ anchor: this.headingBlockFixtureService,
+ callToActionList: this.callToActionListBlockFixtureService,
+ heading: this.headingBlockFixtureService,
+ media: this.standaloneMediaBlockFixtureService,
+ mediaGallery: this.mediaGalleryBlockFixtureService,
+ richtext: this.richtextBlockFixtureService,
+ space: this.spaceBlockFixtureService,
+ };
+
+ for (const block of Object.entries(blockCfg)) {
+ const [type, generator] = block;
+ const props = await generator.generateBlockInput();
+
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ type: type as keyof typeof blockCfg,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ props: props as any,
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+
+ async generateBlockInput(): Promise> {
+ const content = await this.generateColumnsContentBlock();
+
+ return {
+ layout: faker.helpers.arrayElement(oneColumnLayouts).name,
+ columns: [
+ {
+ key: faker.string.uuid(),
+ visible: true,
+ props: content,
+ },
+ ],
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts
new file mode 100644
index 0000000000..eb65d2aa70
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts
@@ -0,0 +1,76 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { BackgroundColor as ContentGroupBackgroundColor, ContentBlock, ContentGroupBlock } from "@src/documents/pages/blocks/content-group.block";
+
+import { BlockFixture } from "../block-fixture";
+import { MediaGalleryBlockFixtureService } from "../media/media-gallery-block-fixture.service";
+import { StandaloneMediaBlockFixtureService } from "../media/standalone-media-block-fixture.service";
+import { AnchorBlockFixtureService } from "../navigation/anchor-block-fixture.service";
+import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service";
+import { TeaserBlockFixtureService } from "../teaser/teaser-block-fixture.service";
+import { KeyFactsBlockFixtureService } from "../text-and-content/key-facts-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service";
+import { AccordionBlockFixtureService } from "./accordion-block-fixture.service";
+import { ColumnsBlockFixtureService } from "./columns-block-fixture.service";
+import { SpaceBlockFixtureService } from "./space-block-fixture.service";
+
+@Injectable()
+export class ContentGroupBlockFixtureService {
+ constructor(
+ private readonly accordionBlockFixtureService: AccordionBlockFixtureService,
+ private readonly anchorBlockFixtureService: AnchorBlockFixtureService,
+ private readonly columnsBlockFixtureService: ColumnsBlockFixtureService,
+ private readonly keyFactsBlockFixtureService: KeyFactsBlockFixtureService,
+ private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ private readonly spaceBlockFixtureService: SpaceBlockFixtureService,
+ private readonly standaloneMediaBlockFixtureService: StandaloneMediaBlockFixtureService,
+ private readonly standaloneHeadingBlockFixtureService: StandaloneHeadingBlockFixtureService,
+ private readonly standaloneCallToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService,
+ private readonly teaserBlockFixtureService: TeaserBlockFixtureService,
+ ) {}
+
+ async generateContentGroupContentBlock(): Promise> {
+ const blocks: ExtractBlockInputFactoryProps["blocks"] = [];
+
+ const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = {
+ accordion: this.accordionBlockFixtureService,
+ anchor: this.anchorBlockFixtureService,
+ callToActionList: this.standaloneCallToActionListBlockFixtureService,
+ columns: this.columnsBlockFixtureService,
+ heading: this.standaloneHeadingBlockFixtureService,
+ keyFacts: this.keyFactsBlockFixtureService,
+ media: this.standaloneMediaBlockFixtureService,
+ mediaGallery: this.mediaGalleryBlockFixtureService,
+ richtext: this.richTextBlockFixtureService,
+ space: this.spaceBlockFixtureService,
+ teaser: this.teaserBlockFixtureService,
+ };
+
+ for (const block of Object.entries(blockCfg)) {
+ const [type, generator] = block;
+ const props = await generator.generateBlockInput();
+
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ type: type as keyof typeof blockCfg,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ props: props as any,
+ });
+ }
+
+ return {
+ blocks,
+ };
+ }
+
+ async generateBlockInput(): Promise> {
+ return {
+ backgroundColor: faker.helpers.arrayElement(Object.values(ContentGroupBackgroundColor)),
+ content: await this.generateContentGroupContentBlock(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts
new file mode 100644
index 0000000000..4cb287d12f
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts
@@ -0,0 +1,25 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { LayoutBlock, LayoutBlockLayout } from "@src/documents/pages/blocks/layout.block";
+
+import { MediaBlockFixtureService } from "../media/media-block.fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+
+@Injectable()
+export class LayoutBlockFixtureService {
+ constructor(
+ private readonly mediaBlockFixtureService: MediaBlockFixtureService,
+ private readonly richTextFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ layout: faker.helpers.arrayElement(Object.values(LayoutBlockLayout)),
+ media1: await this.mediaBlockFixtureService.generateBlockInput(),
+ text1: await this.richTextFixtureService.generateBlockInput(),
+ media2: await this.mediaBlockFixtureService.generateBlockInput(),
+ text2: await this.richTextFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts
new file mode 100644
index 0000000000..810c827d30
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts
@@ -0,0 +1,11 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { SpaceBlock, Spacing } from "@src/common/blocks/space.block";
+
+@Injectable()
+export class SpaceBlockFixtureService {
+ async generateBlockInput(): Promise> {
+ return { spacing: faker.helpers.arrayElement(Object.values(Spacing)) };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts
new file mode 100644
index 0000000000..9db356eb9f
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts
@@ -0,0 +1,36 @@
+import { DamImageBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service";
+import { SvgImageBlockFixtureService } from "./svg-image-block-fixture.service";
+
+interface GenerateDamImageBlockInputProps {
+ generateSvgImage?: boolean;
+}
+
+@Injectable()
+export class DamImageBlockFixtureService {
+ constructor(
+ private readonly svgImageBlockFixtureService: SvgImageBlockFixtureService,
+ private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(settings?: GenerateDamImageBlockInputProps): Promise> {
+ const types = ["pixelImage", "svgImage"] as const;
+ const type = settings?.generateSvgImage === true ? faker.helpers.arrayElement(types) : "pixelImage";
+
+ switch (type) {
+ case "svgImage":
+ return {
+ attachedBlocks: [{ type, props: await this.svgImageBlockFixtureService.generateBlockInput() }],
+ activeType: type,
+ };
+ case "pixelImage":
+ return {
+ attachedBlocks: [{ type, props: await this.pixelImageBlockFixtureService.generateBlockInput() }],
+ activeType: type,
+ };
+ }
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts
new file mode 100644
index 0000000000..8386db2ae6
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts
@@ -0,0 +1,27 @@
+import { DamVideoBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+import { VideoFixtureService } from "../../video-fixture.service";
+import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service";
+
+@Injectable()
+export class DamVideoBlockFixtureService {
+ constructor(
+ private readonly videoFixtureService: VideoFixtureService,
+ private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ const autoplay = faker.datatype.boolean();
+ const damFileId = this.videoFixtureService.getRandomVideo().id;
+
+ return {
+ autoplay,
+ loop: faker.datatype.boolean(),
+ showControls: !autoplay,
+ damFileId,
+ previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts
new file mode 100644
index 0000000000..1ccf21a5e8
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts
@@ -0,0 +1,25 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { FullWidthImageBlock } from "@src/documents/pages/blocks/full-width-image.block";
+
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+import { DamImageBlockFixtureService } from "./dam-image-block-fixture.service";
+
+@Injectable()
+export class FullWidthImageBlockFixtureService {
+ constructor(
+ private readonly damImageBlockFixtureService: DamImageBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ content: {
+ ...(await this.richTextBlockFixtureService.generateBlockInput()),
+ visible: faker.datatype.boolean({ probability: 1.0 }),
+ },
+ image: await this.damImageBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts
new file mode 100644
index 0000000000..fcfc95aa23
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts
@@ -0,0 +1,46 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { MediaBlock } from "@src/common/blocks/media.block";
+
+import { DamImageBlockFixtureService } from "./dam-image-block-fixture.service";
+import { DamVideoBlockFixtureService } from "./dam-video-block-fixture.service";
+import { VimeoVideoBlockFixtureService } from "./vimeo-video-block-fixture.service";
+import { YouTubeVideoBlockFixtureService } from "./youtube-video-block-fixture.service";
+
+@Injectable()
+export class MediaBlockFixtureService {
+ constructor(
+ private readonly damImageBlockFixtureService: DamImageBlockFixtureService,
+ private readonly damVideoBlockFixtureService: DamVideoBlockFixtureService,
+ private readonly youtubeVideoBlockFixtureService: YouTubeVideoBlockFixtureService,
+ private readonly vimeoVideoBlockFixtureService: VimeoVideoBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ const types = ["image", "damVideo", "youTubeVideo", "vimeoVideo"] as const;
+ const type = faker.helpers.arrayElement(types);
+
+ return {
+ attachedBlocks: [
+ {
+ type: "image",
+ props: await this.damImageBlockFixtureService.generateBlockInput(),
+ },
+ {
+ type: "damVideo",
+ props: await this.damVideoBlockFixtureService.generateBlockInput(),
+ },
+ {
+ type: "youTubeVideo",
+ props: await this.youtubeVideoBlockFixtureService.generateBlockInput(),
+ },
+ {
+ type: "vimeoVideo",
+ props: await this.vimeoVideoBlockFixtureService.generateBlockInput(),
+ },
+ ],
+ activeType: type,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts
new file mode 100644
index 0000000000..0466797c41
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts
@@ -0,0 +1,44 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { MediaGalleryBlock, MediaGalleryListBlock } from "@src/common/blocks/media-gallery.block";
+import { MediaGalleryItemBlock } from "@src/common/blocks/media-gallery-item.block";
+import { MediaAspectRatios } from "@src/util/mediaAspectRatios";
+
+import { MediaBlockFixtureService } from "./media-block.fixture.service";
+
+@Injectable()
+export class MediaGalleryBlockFixtureService {
+ constructor(private readonly mediaBlockFixtureService: MediaBlockFixtureService) {}
+
+ async generateMediaGalleryItemBlock(): Promise> {
+ return {
+ media: await this.mediaBlockFixtureService.generateBlockInput(),
+ caption: faker.lorem.words({ min: 3, max: 9 }),
+ };
+ }
+
+ async generateMediaGalleryListBlock(min = 2, max = 6): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.generateMediaGalleryItemBlock(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+
+ async generateBlockInput(): Promise> {
+ return {
+ aspectRatio: faker.helpers.arrayElement(Object.values(MediaAspectRatios)),
+ items: await this.generateMediaGalleryListBlock(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts
new file mode 100644
index 0000000000..00348b0348
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts
@@ -0,0 +1,29 @@
+import { ExtractBlockInputFactoryProps, FocalPoint, ImageCropAreaInput, PixelImageBlock } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+import { ImageFixtureService } from "../../image-fixture.service";
+
+@Injectable()
+export class PixelImageBlockFixtureService {
+ constructor(private readonly imageFixtureService: ImageFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ damFileId: this.imageFixtureService.getRandomPixelImage().id,
+ cropArea: this.calculateDefaultCropInput(),
+ };
+ }
+
+ private calculateDefaultCropInput(): ImageCropAreaInput {
+ const focalPoint = faker.helpers.arrayElement(Object.values(Object.keys(FocalPoint))) as FocalPoint;
+
+ return {
+ focalPoint,
+ x: focalPoint !== FocalPoint.SMART ? 0 : undefined,
+ y: focalPoint !== FocalPoint.SMART ? 0 : undefined,
+ height: focalPoint !== FocalPoint.SMART ? faker.number.int({ min: 20, max: 100 }) : undefined,
+ width: focalPoint !== FocalPoint.SMART ? faker.number.int({ min: 20, max: 100 }) : undefined,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts
new file mode 100644
index 0000000000..d16ade2de8
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts
@@ -0,0 +1,19 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block";
+import { MediaAspectRatios } from "@src/util/mediaAspectRatios";
+
+import { MediaBlockFixtureService } from "./media-block.fixture.service";
+
+@Injectable()
+export class StandaloneMediaBlockFixtureService {
+ constructor(private readonly mediaBlockFixtureService: MediaBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ media: await this.mediaBlockFixtureService.generateBlockInput(),
+ aspectRatio: faker.helpers.arrayElement(Object.values(MediaAspectRatios)),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts
new file mode 100644
index 0000000000..b74bd6dbac
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts
@@ -0,0 +1,15 @@
+import { ExtractBlockInputFactoryProps, SvgImageBlock } from "@comet/cms-api";
+import { Injectable } from "@nestjs/common";
+
+import { ImageFixtureService } from "../../image-fixture.service";
+
+@Injectable()
+export class SvgImageBlockFixtureService {
+ constructor(private readonly imageFixtureService: ImageFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ damFileId: this.imageFixtureService.getRandomSvg().id,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts
new file mode 100644
index 0000000000..b279e48e7c
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts
@@ -0,0 +1,23 @@
+import { ExtractBlockInputFactoryProps, VimeoVideoBlock } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service";
+
+@Injectable()
+export class VimeoVideoBlockFixtureService {
+ constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ const identifier = ["76979871"];
+ const autoplay = faker.datatype.boolean();
+
+ return {
+ autoplay,
+ loop: faker.datatype.boolean(),
+ showControls: !autoplay,
+ vimeoIdentifier: faker.helpers.arrayElement(identifier),
+ previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts
new file mode 100644
index 0000000000..5a0cf9fb62
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts
@@ -0,0 +1,23 @@
+import { ExtractBlockInputFactoryProps, YouTubeVideoBlock } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service";
+
+@Injectable()
+export class YouTubeVideoBlockFixtureService {
+ constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ const identifier = ["dQw4w9WgXcQ"];
+ const autoplay = faker.datatype.boolean();
+
+ return {
+ autoplay,
+ loop: faker.datatype.boolean(),
+ showControls: !autoplay,
+ youtubeIdentifier: faker.helpers.arrayElement(identifier),
+ previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts
new file mode 100644
index 0000000000..7a880ddb87
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts
@@ -0,0 +1,12 @@
+import { AnchorBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+
+@Injectable()
+export class AnchorBlockFixtureService {
+ async generateBlockInput(): Promise> {
+ return {
+ name: faker.word.words(3),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts
new file mode 100644
index 0000000000..0309402a82
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts
@@ -0,0 +1,18 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { CallToActionBlock, Variant as CallToActionVariant } from "@src/common/blocks/call-to-action.block";
+
+import { TextLinkBlockFixtureService } from "./text-link-block-fixture.service";
+
+@Injectable()
+export class CallToActionBlockFixtureService {
+ constructor(private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ textLink: await this.textLinkBlockFixtureService.generateBlockInput(),
+ variant: faker.helpers.arrayElement(Object.values(CallToActionVariant)),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts
new file mode 100644
index 0000000000..03295b8581
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts
@@ -0,0 +1,28 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block";
+
+import { CallToActionBlockFixtureService } from "./call-to-action-block-fixture.service";
+
+@Injectable()
+export class CallToActionListBlockFixtureService {
+ constructor(private readonly callToActionBlockFixtureService: CallToActionBlockFixtureService) {}
+
+ async generateBlockInput(min = 2, max = 6): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.callToActionBlockFixtureService.generateBlockInput(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts
new file mode 100644
index 0000000000..74d0b07c7c
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts
@@ -0,0 +1,24 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { LinkBlock } from "@src/common/blocks/link.block";
+
+const urls = ["https://vivid-planet.com/", "https://github.com/", "https://gitlab.com", "https://stackoverflow.com/"];
+
+@Injectable()
+export class LinkBlockFixtureService {
+ async generateBlockInput(): Promise> {
+ return {
+ attachedBlocks: [
+ {
+ type: "external",
+ props: {
+ targetUrl: faker.helpers.arrayElement(urls),
+ openInNewWindow: faker.datatype.boolean(),
+ },
+ },
+ ],
+ activeType: "external",
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts
new file mode 100644
index 0000000000..c84ed0576c
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts
@@ -0,0 +1,27 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { LinkListBlock } from "@src/common/blocks/link-list.block";
+import { TextLinkBlockFixtureService } from "@src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service";
+
+@Injectable()
+export class LinkListBlockFixtureService {
+ constructor(private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService) {}
+
+ async generateBlockInput(min = 1, max = 4): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.textLinkBlockFixtureService.generateBlockInput(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts
new file mode 100644
index 0000000000..70735f7502
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts
@@ -0,0 +1,21 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import {
+ Alignment as StandaloneCallToActionListBlockAlignment,
+ StandaloneCallToActionListBlock,
+} from "@src/common/blocks/standalone-call-to-action-list.block";
+
+import { CallToActionListBlockFixtureService } from "./call-to-action-list-block.service";
+
+@Injectable()
+export class StandaloneCallToActionListBlockFixtureService {
+ constructor(private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ alignment: faker.helpers.arrayElement(Object.values(StandaloneCallToActionListBlockAlignment)),
+ callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts
new file mode 100644
index 0000000000..c77a2d34da
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts
@@ -0,0 +1,18 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { TextLinkBlock } from "@src/common/blocks/text-link.block";
+
+import { LinkBlockFixtureService } from "./link-block-fixture.service";
+
+@Injectable()
+export class TextLinkBlockFixtureService {
+ constructor(private readonly linkBlockFixtureService: LinkBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ link: await this.linkBlockFixtureService.generateBlockInput(),
+ text: faker.lorem.words({ min: 1, max: 3 }),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts
new file mode 100644
index 0000000000..c8f3a330a2
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts
@@ -0,0 +1,30 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { Alignment as BasicStageBlockAlignment, BasicStageBlock } from "@src/documents/pages/blocks/basic-stage.block";
+
+import { MediaBlockFixtureService } from "../media/media-block.fixture.service";
+import { CallToActionListBlockFixtureService } from "../navigation/call-to-action-list-block.service";
+import { HeadingBlockFixtureService } from "../text-and-content/heading-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+
+@Injectable()
+export class BasicStageBlockFixtureService {
+ constructor(
+ private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService,
+ private readonly headingBlockFixtureService: HeadingBlockFixtureService,
+ private readonly mediaBlockFixtureService: MediaBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ media: await this.mediaBlockFixtureService.generateBlockInput(),
+ heading: await this.headingBlockFixtureService.generateBlockInput(),
+ text: await this.richTextBlockFixtureService.generateBlockInput(),
+ overlay: faker.number.int({ min: 50, max: 90 }),
+ alignment: faker.helpers.arrayElement(Object.values(BasicStageBlockAlignment)),
+ callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts
new file mode 100644
index 0000000000..4b6d31927f
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts
@@ -0,0 +1,29 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { BillboardTeaserBlock } from "@src/documents/pages/blocks/billboard-teaser.block";
+
+import { MediaBlockFixtureService } from "../media/media-block.fixture.service";
+import { CallToActionListBlockFixtureService } from "../navigation/call-to-action-list-block.service";
+import { HeadingBlockFixtureService } from "../text-and-content/heading-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+
+@Injectable()
+export class BillboardTeaserBlockFixtureService {
+ constructor(
+ private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService,
+ private readonly headingBlockFixtureService: HeadingBlockFixtureService,
+ private readonly mediaBlockFixtureService: MediaBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ media: await this.mediaBlockFixtureService.generateBlockInput(),
+ heading: await this.headingBlockFixtureService.generateBlockInput(),
+ text: await this.richTextBlockFixtureService.generateBlockInput(),
+ overlay: faker.number.int({ min: 50, max: 90 }),
+ callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts
new file mode 100644
index 0000000000..d812fac6cc
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts
@@ -0,0 +1,44 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { TeaserBlock } from "@src/documents/pages/blocks/teaser.block";
+import { TeaserItemBlock } from "@src/documents/pages/blocks/teaser-item.block";
+
+import { MediaBlockFixtureService } from "../media/media-block.fixture.service";
+import { TextLinkBlockFixtureService } from "../navigation/text-link-block-fixture.service";
+import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
+
+@Injectable()
+export class TeaserBlockFixtureService {
+ constructor(
+ private readonly mediaBlockFixtureService: MediaBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService,
+ ) {}
+
+ async generateTeaserItemBlock(): Promise> {
+ return {
+ media: await this.mediaBlockFixtureService.generateBlockInput(),
+ title: faker.lorem.words({ min: 3, max: 9 }),
+ description: await this.richTextBlockFixtureService.generateBlockInput(),
+ link: await this.textLinkBlockFixtureService.generateBlockInput(),
+ };
+ }
+
+ async generateBlockInput(min = 2, max = 6): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.generateTeaserItemBlock(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts
new file mode 100644
index 0000000000..b37bc993f1
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts
@@ -0,0 +1,41 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { HeadingBlock, HeadlineTag } from "@src/common/blocks/heading.block";
+
+import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service";
+
+@Injectable()
+export class HeadingBlockFixtureService {
+ constructor(private readonly richTextBlockFixtureService: RichTextBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ const possibleTypes = ["h1", "h2", "h3", "h4", "subHeadlineMedium", "subHeadlineSmall"];
+
+ const eyebrowBlock = {
+ key: faker.string.uuid(),
+ text: faker.lorem.words({ min: 3, max: 9 }),
+ type: faker.helpers.arrayElement(possibleTypes),
+ depth: 0,
+ inlineStyleRanges: [],
+ entityRanges: [],
+ data: {},
+ };
+
+ const headingBlock = {
+ key: faker.string.uuid(),
+ text: faker.lorem.words({ min: 3, max: 9 }),
+ type: faker.helpers.arrayElement(possibleTypes),
+ depth: 0,
+ inlineStyleRanges: [],
+ entityRanges: [],
+ data: {},
+ };
+
+ return {
+ eyebrow: await this.richTextBlockFixtureService.generateBlockInput(1, [eyebrowBlock]),
+ headline: await this.richTextBlockFixtureService.generateBlockInput(1, [headingBlock]),
+ htmlTag: faker.helpers.arrayElement(Object.values(HeadlineTag)),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts
new file mode 100644
index 0000000000..0342f08a64
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts
@@ -0,0 +1,42 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { KeyFactsBlock } from "@src/documents/pages/blocks/key-facts.block";
+import { KeyFactsItemBlock } from "@src/documents/pages/blocks/key-facts-item.block";
+
+import { SvgImageBlockFixtureService } from "../media/svg-image-block-fixture.service";
+import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service";
+
+@Injectable()
+export class KeyFactsBlockFixtureService {
+ constructor(
+ private readonly svgImageBlockFixtureService: SvgImageBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateKeyFactsItemBlock(): Promise> {
+ return {
+ icon: await this.svgImageBlockFixtureService.generateBlockInput(),
+ fact: faker.lorem.sentence(),
+ label: faker.lorem.sentence(),
+ description: await this.richTextBlockFixtureService.generateBlockInput(),
+ };
+ }
+
+ async generateBlockInput(min = 2, max = 6): Promise> {
+ const blockAmount = faker.number.int({ min, max });
+ const blocks = [];
+
+ for (let i = 0; i < blockAmount; i++) {
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.generateKeyFactsItemBlock(),
+ });
+ }
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts
new file mode 100644
index 0000000000..8f1819b3c1
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts
@@ -0,0 +1,37 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { RichTextBlock } from "@src/common/blocks/rich-text.block";
+
+@Injectable()
+export class RichTextBlockFixtureService {
+ async generateBlockInput(
+ lineCount = 3,
+ blocks?: ExtractBlockInputFactoryProps["draftContent"]["blocks"],
+ ): Promise> {
+ const possibleTypes = ["textMedium", "textSmall", "textXSmall"];
+
+ const defaultBlocks: ExtractBlockInputFactoryProps["draftContent"]["blocks"] = [];
+
+ if (!blocks) {
+ for (let i = 0; i < faker.number.int({ min: 1, max: 3 }); i++) {
+ defaultBlocks.push({
+ key: faker.string.uuid(),
+ text: faker.lorem.paragraph(),
+ type: faker.helpers.arrayElement(possibleTypes),
+ depth: 0,
+ inlineStyleRanges: [],
+ entityRanges: [],
+ data: {},
+ });
+ }
+ }
+
+ return {
+ draftContent: {
+ blocks: faker.helpers.arrayElements(blocks ?? defaultBlocks, lineCount),
+ entityMap: {},
+ },
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts
new file mode 100644
index 0000000000..90fd8dbdf0
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts
@@ -0,0 +1,18 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { StandaloneHeadingBlock, TextAlignment } from "@src/common/blocks/standalone-heading.block";
+
+import { HeadingBlockFixtureService } from "./heading-block-fixture.service";
+
+@Injectable()
+export class StandaloneHeadingBlockFixtureService {
+ constructor(private readonly headingBlockFixtureService: HeadingBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ heading: await this.headingBlockFixtureService.generateBlockInput(),
+ textAlignment: faker.helpers.arrayElement(Object.values(TextAlignment)),
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts
new file mode 100644
index 0000000000..58d2c0a255
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts
@@ -0,0 +1,24 @@
+import { ExtractBlockInputFactoryProps, ImagePosition } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { TextImageBlock } from "@src/common/blocks/text-image.block";
+
+import { DamImageBlockFixtureService } from "../media/dam-image-block-fixture.service";
+import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service";
+
+@Injectable()
+export class TextImageBlockFixtureService {
+ constructor(
+ private readonly damImageBlockFixtureService: DamImageBlockFixtureService,
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(): Promise> {
+ return {
+ image: await this.damImageBlockFixtureService.generateBlockInput(),
+ text: await this.richTextBlockFixtureService.generateBlockInput(),
+ imagePosition: faker.helpers.arrayElement(Object.values(ImagePosition)),
+ imageAspectRatio: "16:9",
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/document-generator.service.ts b/demo/api/src/db/fixtures/generators/document-generator.service.ts
new file mode 100644
index 0000000000..ecfb6f9b3b
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/document-generator.service.ts
@@ -0,0 +1,76 @@
+import { PageTreeNodeInterface, PageTreeNodeVisibility, PageTreeService } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { InjectRepository } from "@mikro-orm/nestjs";
+import { EntityManager, EntityRepository } from "@mikro-orm/postgresql";
+import { Injectable } from "@nestjs/common";
+import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block";
+import { SeoBlock } from "@src/documents/pages/blocks/seo.block";
+import { StageBlock } from "@src/documents/pages/blocks/stage.block";
+import { Page } from "@src/documents/pages/entities/page.entity";
+import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope";
+import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category";
+import { UserGroup } from "@src/user-groups/user-group";
+import slugify from "slugify";
+
+import { BlockCategory, PageContentBlockFixtureService } from "./page-content-block-fixture.service";
+import { SeoBlockFixtureService } from "./seo-block-fixture.service";
+import { StageBlockFixtureService } from "./stage-block-fixture.service";
+
+interface GeneratePageInput {
+ name: string;
+ scope: PageTreeNodeScope;
+ parentId?: string;
+ category?: PageTreeNodeCategory;
+ blockCategory?: BlockCategory;
+}
+
+@Injectable()
+export class DocumentGeneratorService {
+ constructor(
+ private readonly pageTreeService: PageTreeService,
+ @InjectRepository(Page) private readonly pagesRepository: EntityRepository,
+ private readonly pageContentBlockFixtureService: PageContentBlockFixtureService,
+ private readonly stageBlockFixtureService: StageBlockFixtureService,
+ private readonly seoBlockFixtureService: SeoBlockFixtureService,
+ private readonly entityManager: EntityManager,
+ ) {}
+
+ async generatePage({
+ name,
+ scope,
+ parentId,
+ category = PageTreeNodeCategory.MainNavigation,
+ blockCategory,
+ }: GeneratePageInput): Promise {
+ const id = faker.string.uuid();
+ const slug = slugify(name.toLowerCase(), { remove: /[*+~.()/'"!:@]/g });
+
+ const node = await this.pageTreeService.createNode(
+ {
+ name,
+ slug,
+ attachedDocument: {
+ id,
+ type: "Page",
+ },
+ parentId,
+ // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589
+ userGroup: UserGroup.All,
+ },
+ category,
+ scope,
+ );
+ await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published);
+
+ await this.entityManager.persistAndFlush(
+ this.pagesRepository.create({
+ id,
+ content: PageContentBlock.blockDataFactory(await this.pageContentBlockFixtureService.generateBlockInput(blockCategory)),
+ seo: SeoBlock.blockDataFactory(await this.seoBlockFixtureService.generateBlockInput()),
+ stage: StageBlock.blockDataFactory(await this.stageBlockFixtureService.generateBlockInput()),
+ }),
+ );
+
+ return node;
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts b/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts
index b4ff4201aa..5ad829ad3b 100644
--- a/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts
+++ b/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts
@@ -7,13 +7,11 @@ export class FileUploadsFixtureService {
constructor(private readonly fileUploadsService: FileUploadsService) {}
async generateFileUploads(): Promise {
- console.log("Generating file uploads...");
-
const images = ["01.jpg", "02.jpg", "03.jpg", "04.jpg", "05.jpg"];
const fileUploads: FileUpload[] = [];
for (const image of images) {
- const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/generators/images/${image}`));
+ const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/assets/images/${image}`));
fileUploads.push(await this.fileUploadsService.upload(file));
}
diff --git a/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts b/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts
index 464dad8048..89f8852382 100644
--- a/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts
+++ b/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts
@@ -13,7 +13,7 @@ export class ImageFileFixtureService {
const images = [];
for (let index = 0; index < IMAGE_FILE_PATHS.length; index++) {
console.log(`Downloading ${IMAGE_FILE_PATHS[index]}.`);
- const image_path = path.resolve(`./src/db/fixtures/generators/images/${IMAGE_FILE_PATHS[index]}.png`);
+ const image_path = path.resolve(`./src/db/fixtures/assets/images/${IMAGE_FILE_PATHS[index]}.png`);
const downloadedImage = await createFileUploadInputFromUrl(image_path);
console.log(`Downloading ${IMAGE_FILE_PATHS[index]} done.`);
diff --git a/demo/api/src/db/fixtures/generators/image-fixture.service.ts b/demo/api/src/db/fixtures/generators/image-fixture.service.ts
new file mode 100644
index 0000000000..6d8d1e9f6e
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/image-fixture.service.ts
@@ -0,0 +1,77 @@
+import { createFileUploadInputFromUrl, FileInterface, FilesService } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { InjectRepository } from "@mikro-orm/nestjs";
+import { EntityManager, EntityRepository } from "@mikro-orm/postgresql";
+import { Injectable } from "@nestjs/common";
+import { DamScope } from "@src/dam/dto/dam-scope";
+import { DamFile } from "@src/dam/entities/dam-file.entity";
+import * as fs from "fs/promises";
+import path from "path";
+
+@Injectable()
+export class ImageFixtureService {
+ private pixelImageFiles: FileInterface[] = [];
+ private svgImageFiles: FileInterface[] = [];
+
+ constructor(
+ private readonly filesService: FilesService,
+ @InjectRepository(DamFile) readonly filesRepository: EntityRepository,
+ private readonly entityManager: EntityManager,
+ ) {}
+
+ public getRandomPixelImage() {
+ const randomIndex = faker.number.int({
+ min: 0,
+ max: this.pixelImageFiles.length - 1,
+ });
+
+ return this.pixelImageFiles[randomIndex];
+ }
+
+ public getRandomSvg() {
+ const randomIndex = faker.number.int({
+ min: 0,
+ max: this.svgImageFiles.length - 1,
+ });
+
+ return this.svgImageFiles[randomIndex];
+ }
+
+ private async generatePixelImages(maximumImageCount: number, scope: DamScope): Promise {
+ const directory = "./src/db/fixtures/assets/images";
+ const allImages = await fs.readdir(path.resolve(directory));
+ const images = faker.helpers.arrayElements(allImages, maximumImageCount);
+
+ for (const image of images) {
+ const file = await createFileUploadInputFromUrl(path.resolve(`${directory}/${image}`));
+ const pixelImage = await this.filesService.upload(file, { scope });
+
+ this.pixelImageFiles.push(pixelImage);
+ }
+
+ await this.entityManager.flush();
+ }
+
+ private async generateSvgImages(maximumImageCount: number, scope: DamScope): Promise {
+ const svgDirectoryPath = "./src/db/fixtures/assets/svg";
+ const files = await fs.readdir(path.resolve(svgDirectoryPath));
+
+ let count = 0;
+ for (const svg of files) {
+ const file = await createFileUploadInputFromUrl(path.resolve(`${svgDirectoryPath}/${svg}`));
+ file.mimetype = "image/svg+xml"; // mime type is undefined for svg files and wrongly typed in download function (a possible undefined type is typed as string)
+ this.svgImageFiles.push(await this.filesService.upload(file, { scope }));
+ count++;
+
+ if (count === maximumImageCount) break;
+ }
+ }
+
+ public async generateImages(maximumImageCount: number, scope: DamScope): Promise {
+ console.log("Generate Pixel Images ...");
+ await this.generatePixelImages(maximumImageCount, scope);
+
+ console.log("Generate SVG Images ...");
+ await this.generateSvgImages(maximumImageCount, scope);
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts
new file mode 100644
index 0000000000..8cd27f2bc4
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts
@@ -0,0 +1,96 @@
+import { ExtractBlockInput, ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block";
+
+import { BlockFixture } from "./blocks/block-fixture";
+import { AccordionBlockFixtureService } from "./blocks/layout/accordion-block-fixture.service";
+import { ColumnsBlockFixtureService } from "./blocks/layout/columns-block-fixture.service";
+import { ContentGroupBlockFixtureService } from "./blocks/layout/content-group-block-fixture.service";
+import { LayoutBlockFixtureService } from "./blocks/layout/layout-block-fixture.service";
+import { SpaceBlockFixtureService } from "./blocks/layout/space-block-fixture.service";
+import { DamImageBlockFixtureService } from "./blocks/media/dam-image-block-fixture.service";
+import { FullWidthImageBlockFixtureService } from "./blocks/media/full-width-image-block-fixture.service";
+import { MediaGalleryBlockFixtureService } from "./blocks/media/media-gallery-block-fixture.service";
+import { StandaloneMediaBlockFixtureService } from "./blocks/media/standalone-media-block-fixture.service";
+import { AnchorBlockFixtureService } from "./blocks/navigation/anchor-block-fixture.service";
+import { LinkListBlockFixtureService } from "./blocks/navigation/link-list-block-fixture.service";
+import { StandaloneCallToActionListBlockFixtureService } from "./blocks/navigation/standalone-call-to-action-list-block-fixture.service";
+import { BillboardTeaserBlockFixtureService } from "./blocks/teaser/billboard-teaser-block-fixture.service";
+import { TeaserBlockFixtureService } from "./blocks/teaser/teaser-block-fixture.service";
+import { KeyFactsBlockFixtureService } from "./blocks/text-and-content/key-facts-block-fixture.service";
+import { RichTextBlockFixtureService } from "./blocks/text-and-content/rich-text-block-fixture.service";
+import { StandaloneHeadingBlockFixtureService } from "./blocks/text-and-content/standalone-heading-block-fixture.service";
+import { TextImageBlockFixtureService } from "./blocks/text-and-content/text-image-block-fixture.service";
+
+export type BlockCategory = "layout" | "media" | "navigation" | "teaser" | "textAndContent";
+
+@Injectable()
+export class PageContentBlockFixtureService {
+ constructor(
+ private readonly accordionBlockFixtureService: AccordionBlockFixtureService,
+ private readonly anchorBlockFixtureService: AnchorBlockFixtureService,
+ private readonly billboardTeaserBlockFixtureService: BillboardTeaserBlockFixtureService,
+ private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService,
+ private readonly columnsBlockFixtureService: ColumnsBlockFixtureService,
+ private readonly contentGroupBlockFixtureService: ContentGroupBlockFixtureService,
+ private readonly fullWidthImageBlockFixtureService: FullWidthImageBlockFixtureService,
+ private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService,
+ private readonly imageBlockFixtureService: DamImageBlockFixtureService,
+ private readonly keyFactsBlockFixtureService: KeyFactsBlockFixtureService,
+ private readonly layoutBlockFixtureService: LayoutBlockFixtureService,
+ private readonly linkListBlockFixtureService: LinkListBlockFixtureService,
+ private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService,
+ private readonly richtextBlockFixtureService: RichTextBlockFixtureService,
+ private readonly spaceBlockFixtureService: SpaceBlockFixtureService,
+ private readonly mediaBlockFixtureService: StandaloneMediaBlockFixtureService,
+ private readonly teaserBlockFixtureService: TeaserBlockFixtureService,
+ private readonly textImageBlockFixtureService: TextImageBlockFixtureService,
+ ) {}
+
+ async generateBlockInput(blockCategory?: BlockCategory): Promise> {
+ const blocks: ExtractBlockInputFactoryProps["blocks"] = [];
+
+ type SupportedBlocks = (typeof blocks)[number]["type"];
+
+ // TODO add fixtures for newsDetail and newsList
+ const fixtures: Record, [BlockCategory, BlockFixture]> = {
+ accordion: ["layout", this.accordionBlockFixtureService],
+ columns: ["layout", this.columnsBlockFixtureService],
+ contentGroup: ["layout", this.contentGroupBlockFixtureService],
+ layout: ["layout", this.layoutBlockFixtureService],
+ space: ["layout", this.spaceBlockFixtureService],
+ fullWidthImage: ["media", this.fullWidthImageBlockFixtureService],
+ image: ["media", this.imageBlockFixtureService],
+ media: ["media", this.mediaBlockFixtureService],
+ mediaGallery: ["media", this.mediaGalleryBlockFixtureService],
+ anchor: ["navigation", this.anchorBlockFixtureService],
+ callToActionList: ["navigation", this.callToActionListBlockFixtureService],
+ billboardTeaser: ["teaser", this.billboardTeaserBlockFixtureService],
+ teaser: ["teaser", this.teaserBlockFixtureService],
+ heading: ["textAndContent", this.headingBlockFixtureService],
+ keyFacts: ["textAndContent", this.keyFactsBlockFixtureService],
+ richtext: ["textAndContent", this.richtextBlockFixtureService],
+ textImage: ["textAndContent", this.textImageBlockFixtureService],
+ };
+
+ const supportedBlocksFixtureGenerators = Object.entries(fixtures)
+ .filter(([, [category]]) => (blockCategory ? category === blockCategory : true))
+ .map<[string, BlockFixture]>(([type, [, generator]]) => [type, generator]);
+
+ for (const [type, generator] of supportedBlocksFixtureGenerators) {
+ const props = await generator.generateBlockInput();
+
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ type: type as any,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ props: props as any,
+ });
+ }
+
+ return PageContentBlock.blockInputFactory({ blocks });
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts b/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts
index 7e053ca2f2..e5822d2fdb 100644
--- a/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts
+++ b/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts
@@ -11,8 +11,6 @@ export class RedirectsFixtureService {
) {}
async generateRedirects(): Promise {
- console.log("Generating redirects...");
-
for (let i = 0; i < 7000; i++) {
this.repository.create({
generationType: RedirectGenerationType.manual,
diff --git a/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts
new file mode 100644
index 0000000000..a4980fabd0
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts
@@ -0,0 +1,31 @@
+import { ExtractBlockInputFactoryProps, SitemapPageChangeFrequency, SitemapPagePriority } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { SeoBlock } from "@src/documents/pages/blocks/seo.block";
+
+import { PixelImageBlockFixtureService } from "./blocks/media/pixel-image-block-fixture.service";
+
+@Injectable()
+export class SeoBlockFixtureService {
+ constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ const alternativeLinks = [];
+ const codes = ["en_US", "en_GB", "es_ES", "fr_FR", "ja_JP", "it_IT"];
+ for (let i = 0; i < faker.number.int({ min: 0, max: 5 }); i++) {
+ alternativeLinks.push({ code: codes[i], url: faker.internet.url() });
+ }
+
+ return {
+ htmlTitle: faker.word.words(2),
+ metaDescription: faker.word.words(20),
+ openGraphTitle: faker.word.words(2),
+ openGraphDescription: faker.word.words(20),
+ openGraphImage: { block: await this.pixelImageBlockFixtureService.generateBlockInput(), visible: faker.datatype.boolean() },
+ noIndex: faker.datatype.boolean(),
+ priority: faker.helpers.arrayElement(Object.values(SitemapPagePriority)),
+ changeFrequency: faker.helpers.arrayElement(Object.values(SitemapPageChangeFrequency)),
+ alternativeLinks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts
new file mode 100644
index 0000000000..deab1c911b
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts
@@ -0,0 +1,25 @@
+import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { Injectable } from "@nestjs/common";
+import { StageBlock } from "@src/documents/pages/blocks/stage.block";
+
+import { BasicStageBlockFixtureService } from "./blocks/stage/basic-stage-block-fixture.service";
+
+@Injectable()
+export class StageBlockFixtureService {
+ constructor(private readonly basicStageBlockFixtureService: BasicStageBlockFixtureService) {}
+
+ async generateBlockInput(): Promise> {
+ const blocks: ExtractBlockInputFactoryProps["blocks"] = [];
+
+ blocks.push({
+ key: faker.string.uuid(),
+ visible: true,
+ props: await this.basicStageBlockFixtureService.generateBlockInput(),
+ });
+
+ return {
+ blocks: blocks,
+ };
+ }
+}
diff --git a/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts b/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts
index 26e368b43e..4bbdbea7d6 100644
--- a/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts
+++ b/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts
@@ -8,7 +8,7 @@ export class SvgImageFileFixtureService {
constructor(private readonly filesService: FilesService) {}
async generateImage(scope: DamScope): Promise {
- const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/generators/images/comet-logo-claim.svg`));
+ const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/assets/svg/comet-logo-claim.svg`));
// Convert to what the browser would send
file.mimetype = "image/svg+xml";
file.originalname = "comet-logo-claim.svg";
diff --git a/demo/api/src/db/fixtures/generators/video-fixture.service.ts b/demo/api/src/db/fixtures/generators/video-fixture.service.ts
new file mode 100644
index 0000000000..cb6379a48e
--- /dev/null
+++ b/demo/api/src/db/fixtures/generators/video-fixture.service.ts
@@ -0,0 +1,41 @@
+import { createFileUploadInputFromUrl, FileInterface, FilesService } from "@comet/cms-api";
+import { faker } from "@faker-js/faker";
+import { InjectRepository } from "@mikro-orm/nestjs";
+import { EntityManager, EntityRepository } from "@mikro-orm/postgresql";
+import { Injectable } from "@nestjs/common";
+import { DamScope } from "@src/dam/dto/dam-scope";
+import { DamFile } from "@src/dam/entities/dam-file.entity";
+import * as fs from "fs/promises";
+import path from "path";
+
+@Injectable()
+export class VideoFixtureService {
+ private videoFiles: FileInterface[] = [];
+
+ constructor(
+ private readonly filesService: FilesService,
+ @InjectRepository(DamFile) readonly filesRepository: EntityRepository,
+ private readonly entityManager: EntityManager,
+ ) {}
+
+ public getRandomVideo() {
+ const randomIndex = faker.number.int({
+ min: 0,
+ max: this.videoFiles.length - 1,
+ });
+
+ return this.videoFiles[randomIndex];
+ }
+
+ public async generateVideos(scope: DamScope): Promise {
+ const videoDirectoryPath = "./src/db/fixtures/assets/videos";
+ const files = await fs.readdir(path.resolve(videoDirectoryPath));
+
+ for (const video of files) {
+ const file = await createFileUploadInputFromUrl(path.resolve(`${videoDirectoryPath}/${video}`));
+ this.videoFiles.push(await this.filesService.upload(file, { scope }));
+ }
+
+ await this.entityManager.flush();
+ }
+}
diff --git a/demo/api/src/documents/pages/blocks/basic-stage.block.ts b/demo/api/src/documents/pages/blocks/basic-stage.block.ts
index fa798a1b9a..98c01e06b5 100644
--- a/demo/api/src/documents/pages/blocks/basic-stage.block.ts
+++ b/demo/api/src/documents/pages/blocks/basic-stage.block.ts
@@ -15,7 +15,7 @@ import { MediaBlock } from "@src/common/blocks/media.block";
import { RichTextBlock } from "@src/common/blocks/rich-text.block";
import { IsEnum, IsInt, Max, Min } from "class-validator";
-enum Alignment {
+export enum Alignment {
left = "left",
center = "center",
}
diff --git a/demo/api/src/documents/pages/blocks/columns.block.ts b/demo/api/src/documents/pages/blocks/columns.block.ts
index 91e0e94e66..4043a4767c 100644
--- a/demo/api/src/documents/pages/blocks/columns.block.ts
+++ b/demo/api/src/documents/pages/blocks/columns.block.ts
@@ -7,7 +7,7 @@ import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-c
import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block";
import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block";
-const ColumnsContentBlock = createBlocksBlock(
+export const ColumnsContentBlock = createBlocksBlock(
{
supportedBlocks: {
accordion: AccordionBlock,
@@ -20,9 +20,7 @@ const ColumnsContentBlock = createBlocksBlock(
mediaGallery: MediaGalleryBlock,
},
},
- {
- name: "ColumnsContent",
- },
+ { name: "ColumnsContent" },
);
export const ColumnsBlock = ColumnsBlockFactory.create(
diff --git a/demo/api/src/documents/pages/blocks/content-group.block.ts b/demo/api/src/documents/pages/blocks/content-group.block.ts
index f7ba95590e..cd334fb1b5 100644
--- a/demo/api/src/documents/pages/blocks/content-group.block.ts
+++ b/demo/api/src/documents/pages/blocks/content-group.block.ts
@@ -23,26 +23,26 @@ import { KeyFactsBlock } from "@src/documents/pages/blocks/key-facts.block";
import { TeaserBlock } from "@src/documents/pages/blocks/teaser.block";
import { IsEnum } from "class-validator";
-const ContentBlock = createBlocksBlock(
+export const ContentBlock = createBlocksBlock(
{
supportedBlocks: {
accordion: AccordionBlock,
anchor: AnchorBlock,
- space: SpaceBlock,
- teaser: TeaserBlock,
- richtext: RichTextBlock,
- heading: StandaloneHeadingBlock,
- columns: ColumnsBlock,
callToActionList: StandaloneCallToActionListBlock,
+ columns: ColumnsBlock,
+ heading: StandaloneHeadingBlock,
keyFacts: KeyFactsBlock,
media: StandaloneMediaBlock,
mediaGallery: MediaGalleryBlock,
+ richtext: RichTextBlock,
+ space: SpaceBlock,
+ teaser: TeaserBlock,
},
},
{ name: "ContentGroupContent" },
);
-enum BackgroundColor {
+export enum BackgroundColor {
default = "default",
lightGray = "lightGray",
darkGray = "darkGray",
diff --git a/demo/api/src/documents/pages/blocks/layout.block.ts b/demo/api/src/documents/pages/blocks/layout.block.ts
index 82fb766e74..6a7c249f07 100644
--- a/demo/api/src/documents/pages/blocks/layout.block.ts
+++ b/demo/api/src/documents/pages/blocks/layout.block.ts
@@ -13,7 +13,7 @@ import { MediaBlock } from "@src/common/blocks/media.block";
import { RichTextBlock } from "@src/common/blocks/rich-text.block";
import { IsEnum } from "class-validator";
-enum LayoutBlockLayout {
+export enum LayoutBlockLayout {
layout1 = "layout1",
layout2 = "layout2",
layout3 = "layout3",
diff --git a/demo/build-and-run-site.sh b/demo/build-and-run-site.sh
new file mode 100755
index 0000000000..c0465694f1
--- /dev/null
+++ b/demo/build-and-run-site.sh
@@ -0,0 +1,24 @@
+# Execute this script via `npm run build-and-run-site`
+#
+# This script builds the site like in the CI and starts it.
+#
+# Reasons why you want to do this:
+# - Check if it builds without warnings
+# - Check if there are suggestions from next build
+# - Check if it behaves in the same way like the dev-server
+# - Check caching behaviour, e.g. Cache-Control header (which is always no-cache in dev-server)
+
+#!/usr/bin/env bash
+
+echo "[1/2] Build site..."
+cd site
+rm -f .env .env.local .env.site-configs
+rm -rf .next
+NODE_ENV=production npm run build
+ln -sf ../../.env ./
+ln -sf ../../.env.local ./
+ln -sf ../.env.site-configs ./
+echo ""
+
+echo "[2/2] Start site..."
+npx dotenv -e .env.secrets -e .env.site-configs -- npm run serve
diff --git a/demo/site/next.config.mjs b/demo/site/next.config.mjs
index f6ff9807c5..d322f3d79d 100644
--- a/demo/site/next.config.mjs
+++ b/demo/site/next.config.mjs
@@ -29,6 +29,17 @@ const nextConfig = {
},
cacheHandler: process.env.REDIS_ENABLED === "true" ? import.meta.resolve("./dist/cache-handler.js").replace("file://", "") : undefined,
cacheMaxMemorySize: process.env.REDIS_ENABLED === "true" ? 0 : undefined, // disable default in-memory caching
+ rewrites: () => {
+ return {
+ afterFiles: [
+ {
+ // Show a 404 instead of trying to render page for paths starting with /_next/ or /assets/ as they don't get rewritten in DomainRewriteMiddleware and cause errors in ...path page
+ source: "/:prefix(_next|assets)/:path*",
+ destination: "/404",
+ },
+ ],
+ };
+ },
};
export default withBundleAnalyzer(nextConfig);
diff --git a/demo/site/server.ts b/demo/site/server.ts
index 19deeb073e..cb68b9e98e 100644
--- a/demo/site/server.ts
+++ b/demo/site/server.ts
@@ -53,6 +53,16 @@ app.prepare().then(() => {
};
}
+ const originalWriteHead = res.writeHead;
+ res.writeHead = function (statusCode: number, ...args: unknown[]) {
+ // since writeHead is a callback function, it's called after handle() -> we get the actual response statusCode
+ if (statusCode >= 400) {
+ // prevent caching of error responses
+ res.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
+ }
+ return originalWriteHead.apply(this, [statusCode, ...args]);
+ };
+
await handle(req, res, parsedUrl);
} catch (err) {
console.error("Error occurred handling", req.url, err);
diff --git a/demo/site/src/app/not-found.tsx b/demo/site/src/app/not-found.tsx
new file mode 100644
index 0000000000..62c3d0b989
--- /dev/null
+++ b/demo/site/src/app/not-found.tsx
@@ -0,0 +1,12 @@
+import Link from "next/link";
+
+export default function NotFound404() {
+ return (
+
+
+ Page not found.
+ Return Home
+
+
+ );
+}
diff --git a/demo/site/src/middleware/redirectToMainHost.ts b/demo/site/src/middleware/redirectToMainHost.ts
index 98b8ddda50..d81e51b8c6 100644
--- a/demo/site/src/middleware/redirectToMainHost.ts
+++ b/demo/site/src/middleware/redirectToMainHost.ts
@@ -1,8 +1,25 @@
+import type { PublicSiteConfig } from "@src/site-configs";
import { getHostByHeaders, getSiteConfigForHost, getSiteConfigs } from "@src/util/siteConfig";
import { type NextRequest, NextResponse } from "next/server";
import { type CustomMiddleware } from "./chain";
+const normalizeDomain = (host: string) => (host.startsWith("www.") ? host.substring(4) : host);
+
+const matchesHostWithAdditionalDomain = (siteConfig: PublicSiteConfig, host: string) => {
+ if (normalizeDomain(siteConfig.domains.main) === normalizeDomain(host)) return true; // non-www redirect
+ if (siteConfig.domains.additional?.map(normalizeDomain).includes(normalizeDomain(host))) return true;
+ return false;
+};
+
+const matchesHostWithPattern = (siteConfig: PublicSiteConfig, host: string) => {
+ if (!siteConfig.domains.pattern) return false;
+ return new RegExp(siteConfig.domains.pattern).test(host);
+};
+
+/**
+ * When http host isn't siteConfig.domains.main (instead .pattern or .additional match), redirect to main host.
+ */
export function withRedirectToMainHostMiddleware(middleware: CustomMiddleware) {
return async (request: NextRequest) => {
const headers = request.headers;
@@ -11,16 +28,14 @@ export function withRedirectToMainHostMiddleware(middleware: CustomMiddleware) {
if (!siteConfig) {
// Redirect to Main Host
- const redirectSiteConfig = getSiteConfigs().find(
- (siteConfig) =>
- siteConfig.domains.additional?.includes(host) ||
- (siteConfig.domains.pattern && host.match(new RegExp(siteConfig.domains.pattern))),
- );
+ const redirectSiteConfig =
+ getSiteConfigs().find((siteConfig) => matchesHostWithAdditionalDomain(siteConfig, host)) ||
+ getSiteConfigs().find((siteConfig) => matchesHostWithPattern(siteConfig, host));
if (redirectSiteConfig) {
- return NextResponse.redirect(redirectSiteConfig.url);
+ return NextResponse.redirect(redirectSiteConfig.url + request.nextUrl.pathname + request.nextUrl.search, { status: 301 });
}
- return NextResponse.next({ status: 404 });
+ return NextResponse.json({ error: `Cannot resolve domain: ${host}` }, { status: 404 });
}
return middleware(request);
};
diff --git a/docs/docs/6-deployment/index.md b/docs/docs/6-deployment/index.md
index 75ef386725..a169cf0116 100644
--- a/docs/docs/6-deployment/index.md
+++ b/docs/docs/6-deployment/index.md
@@ -42,4 +42,4 @@ Serverless container platforms are a good option for those who want to deploy Co
#### Docker Compose
-For those with budget constraints, Docker Compose can be a viable option for deploying Comet applications.
+For those with budget constraints, [Docker Compose](https://docs.docker.com/compose/) can be a viable option for deploying Comet applications. An example deployment can be found [here](https://github.com/vivid-planet/comet-starter/tree/main/.docker-compose). The deployment only requires Docker to be installed on a server and leverages the power of [Traefik](https://doc.traefik.io/traefik/) to manage the ingress traffic (including SSL certificates).
diff --git a/docs/docs/7-migration-guide/migration-from-v6-to-v7.md b/docs/docs/7-migration-guide/migration-from-v6-to-v7.md
index 5a38010156..0470d5504d 100644
--- a/docs/docs/7-migration-guide/migration-from-v6-to-v7.md
+++ b/docs/docs/7-migration-guide/migration-from-v6-to-v7.md
@@ -236,7 +236,7 @@ DamModule.register({
})
```
-#### How to migrate (only required if CDN is used):
+#### How to migrate (only required if CDN is used with `DAM_CDN_ORIGIN_HEADER`):
Remove the following env vars from the API
@@ -288,26 +288,7 @@ If you want to enable the origin check:
+ }
```
-3. Adjust `site/server.js`
-
-```diff
-// site/server.js
-
-- const cdnEnabled = process.env.CDN_ENABLED === "true";
-- const disableCdnOriginHeaderCheck = process.env.DISABLE_CDN_ORIGIN_HEADER_CHECK === "true";
-- const cdnOriginHeader = process.env.CDN_ORIGIN_HEADER;
-+ const cdnOriginCheckSecret = process.env.CDN_ORIGIN_CHECK_SECRET;
-
-// ...
-
-- if (cdnEnabled && !disableCdnOriginHeaderCheck) {
-- const incomingCdnOriginHeader = req.headers["x-cdn-origin-check"];
-- if (cdnOriginHeader !== incomingCdnOriginHeader) {
-+ if (cdnOriginCheckSecret) {
-+ if (req.headers["x-cdn-origin-check"] !== cdnOriginCheckSecret) {
-```
-
-4. DNS changes might be required. `api.example.com` should point to CDN, CDN should point to internal API domain
+3. DNS changes might be required. `api.example.com` should point to CDN, CDN should point to internal API domain
### API Generator: Remove support for `visible` boolean, use `status` enum instead
@@ -1080,6 +1061,25 @@ const nextConfig = {
module.exports = withBundleAnalyzer(nextConfig);
```
+### Adjust CDN config in `site/server.js`
+
+```diff
+// site/server.js
+
+- const cdnEnabled = process.env.CDN_ENABLED === "true";
+- const disableCdnOriginHeaderCheck = process.env.DISABLE_CDN_ORIGIN_HEADER_CHECK === "true";
+- const cdnOriginHeader = process.env.CDN_ORIGIN_HEADER;
++ const cdnOriginCheckSecret = process.env.CDN_ORIGIN_CHECK_SECRET;
+
+// ...
+
+- if (cdnEnabled && !disableCdnOriginHeaderCheck) {
+- const incomingCdnOriginHeader = req.headers["x-cdn-origin-check"];
+- if (cdnOriginHeader !== incomingCdnOriginHeader) {
++ if (cdnOriginCheckSecret) {
++ if (req.headers["x-cdn-origin-check"] !== cdnOriginCheckSecret) {
+```
+
### Add a custom `InternalLinkBlock`
The `InternalLinkBlock` provided by `@comet/cms-site` is deprecated.
diff --git a/docs/lint-staged.config.mjs b/docs/lint-staged.config.mjs
index 03addb55b0..3ad1e888c3 100644
--- a/docs/lint-staged.config.mjs
+++ b/docs/lint-staged.config.mjs
@@ -5,4 +5,5 @@
export default {
"*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint",
"*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier",
+ "docs/**": "cspell",
};
diff --git a/packages/admin/admin-babel-preset/CHANGELOG.md b/packages/admin/admin-babel-preset/CHANGELOG.md
index 941ae17cc2..710eb4e624 100644
--- a/packages/admin/admin-babel-preset/CHANGELOG.md
+++ b/packages/admin/admin-babel-preset/CHANGELOG.md
@@ -1,5 +1,7 @@
# @comet/admin-babel-preset
+## 7.15.0
+
## 7.14.0
## 7.13.0
diff --git a/packages/admin/admin-babel-preset/package.json b/packages/admin/admin-babel-preset/package.json
index d31e835a40..7a6d353db5 100644
--- a/packages/admin/admin-babel-preset/package.json
+++ b/packages/admin/admin-babel-preset/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin-babel-preset",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"type": "git",
"url": "https://github.com/vivid-planet/comet",
diff --git a/packages/admin/admin-color-picker/CHANGELOG.md b/packages/admin/admin-color-picker/CHANGELOG.md
index 77caa63cce..c334602587 100644
--- a/packages/admin/admin-color-picker/CHANGELOG.md
+++ b/packages/admin/admin-color-picker/CHANGELOG.md
@@ -1,5 +1,17 @@
# @comet/admin-color-picker
+## 7.15.0
+
+### Patch Changes
+
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [faa54eb8e]
+- Updated dependencies [7d8c36e6c]
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [6827982fe]
+ - @comet/admin@7.15.0
+ - @comet/admin-icons@7.15.0
+
## 7.14.0
### Patch Changes
diff --git a/packages/admin/admin-color-picker/package.json b/packages/admin/admin-color-picker/package.json
index 541e8c25b4..4a7c387384 100644
--- a/packages/admin/admin-color-picker/package.json
+++ b/packages/admin/admin-color-picker/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin-color-picker",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/admin-color-picker",
"type": "git",
@@ -26,8 +26,8 @@
"start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput"
},
"dependencies": {
- "@comet/admin": "workspace:^7.14.0",
- "@comet/admin-icons": "workspace:^7.14.0",
+ "@comet/admin": "workspace:^7.15.0",
+ "@comet/admin-icons": "workspace:^7.15.0",
"react-colorful": "^5.6.1",
"tinycolor2": "^1.6.0",
"use-debounce": "^10.0.4"
@@ -35,8 +35,8 @@
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@mui/material": "^6.4.5",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
diff --git a/packages/admin/admin-date-time/CHANGELOG.md b/packages/admin/admin-date-time/CHANGELOG.md
index 51906184f1..59792d3a4e 100644
--- a/packages/admin/admin-date-time/CHANGELOG.md
+++ b/packages/admin/admin-date-time/CHANGELOG.md
@@ -1,5 +1,17 @@
# @comet/admin-date-time
+## 7.15.0
+
+### Patch Changes
+
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [faa54eb8e]
+- Updated dependencies [7d8c36e6c]
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [6827982fe]
+ - @comet/admin@7.15.0
+ - @comet/admin-icons@7.15.0
+
## 7.14.0
### Patch Changes
diff --git a/packages/admin/admin-date-time/package.json b/packages/admin/admin-date-time/package.json
index ffe49d9175..15429762f1 100644
--- a/packages/admin/admin-date-time/package.json
+++ b/packages/admin/admin-date-time/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin-date-time",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/admin-date-time",
"type": "git",
@@ -26,8 +26,8 @@
"start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput"
},
"dependencies": {
- "@comet/admin": "workspace:^7.14.0",
- "@comet/admin-icons": "workspace:^7.14.0",
+ "@comet/admin": "workspace:^7.15.0",
+ "@comet/admin-icons": "workspace:^7.15.0",
"@mui/utils": "^6.4.3",
"date-fns": "^4.1.0",
"react-date-range": "^2.0.1"
@@ -35,8 +35,8 @@
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@mui/material": "^6.4.5",
"@types/react": "^18.3.18",
"@types/react-date-range": "^1.4.9",
diff --git a/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts b/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts
index f1edaca92c..66d447f28c 100644
--- a/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts
+++ b/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts
@@ -70,7 +70,7 @@ export function generateFormLayout({
code = `
}
${
config.supportText
diff --git a/packages/admin/admin-icons/CHANGELOG.md b/packages/admin/admin-icons/CHANGELOG.md
index ec67f42bdf..07bd059b2f 100644
--- a/packages/admin/admin-icons/CHANGELOG.md
+++ b/packages/admin/admin-icons/CHANGELOG.md
@@ -1,5 +1,7 @@
# @comet/admin-icons
+## 7.15.0
+
## 7.14.0
## 7.13.0
diff --git a/packages/admin/admin-icons/package.json b/packages/admin/admin-icons/package.json
index 147389879b..9d2b2e4725 100644
--- a/packages/admin/admin-icons/package.json
+++ b/packages/admin/admin-icons/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin-icons",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/admin-icons",
"type": "git",
@@ -29,8 +29,8 @@
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@mui/material": "^6.4.5",
"@types/cli-progress": "^3.11.6",
"@types/node": "^22.13.5",
diff --git a/packages/admin/admin-rte/CHANGELOG.md b/packages/admin/admin-rte/CHANGELOG.md
index a13616081d..1f5fea192f 100644
--- a/packages/admin/admin-rte/CHANGELOG.md
+++ b/packages/admin/admin-rte/CHANGELOG.md
@@ -1,5 +1,17 @@
# @comet/admin-rte
+## 7.15.0
+
+### Patch Changes
+
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [faa54eb8e]
+- Updated dependencies [7d8c36e6c]
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [6827982fe]
+ - @comet/admin@7.15.0
+ - @comet/admin-icons@7.15.0
+
## 7.14.0
### Minor Changes
diff --git a/packages/admin/admin-rte/package.json b/packages/admin/admin-rte/package.json
index 545ef35bb9..073c6a632f 100644
--- a/packages/admin/admin-rte/package.json
+++ b/packages/admin/admin-rte/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin-rte",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/admin-rte",
"type": "git",
@@ -28,8 +28,8 @@
"test:watch": "jest --watch"
},
"dependencies": {
- "@comet/admin": "workspace:^7.14.0",
- "@comet/admin-icons": "workspace:^7.14.0",
+ "@comet/admin": "workspace:^7.15.0",
+ "@comet/admin-icons": "workspace:^7.15.0",
"detect-browser": "^5.3.0",
"draft-js-export-html": "^1.4.1",
"draft-js-import-html": "^1.4.1",
@@ -39,8 +39,8 @@
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@mui/material": "^6.4.5",
"@types/draft-js": "^0.11.18",
"@types/jest": "^29.5.14",
diff --git a/packages/admin/admin/CHANGELOG.md b/packages/admin/admin/CHANGELOG.md
index e6c34f9dce..43893bd94b 100644
--- a/packages/admin/admin/CHANGELOG.md
+++ b/packages/admin/admin/CHANGELOG.md
@@ -1,5 +1,56 @@
# @comet/admin
+## 7.15.0
+
+### Minor Changes
+
+- a189d4ed9: Support dynamic values for the `label` prop of `SwitchField` depending on its `checked` state
+
+ ```tsx
+ (checked ? "On" : "Off")} />
+ ```
+
+- 7d8c36e6c: Add the `DataGridPanel` component to replace MUIs default `Panel` used by `DataGrid` to match the Comet DXP design
+
+ It is recommended to add this component to your theme's `defaultProps` of `MuiDataGrid`.
+
+ Example theme configuration for `admin/src/theme.ts`:
+
+ ```ts
+ import { DataGridPanel } from "@comet/admin";
+ import { createCometTheme } from "@comet/admin-theme";
+ import type {} from "@mui/x-data-grid/themeAugmentation";
+
+ export const theme = createCometTheme({
+ components: {
+ MuiDataGrid: {
+ defaultProps: {
+ components: {
+ Panel: DataGridPanel,
+ },
+ },
+ },
+ },
+ });
+ ```
+
+- a189d4ed9: Allow passing a `ReactNode` to `fieldLabel` of `CheckboxField` and `SwitchField`
+
+ This enables using `FormattedMessage` for the label.
+
+ ```tsx
+ } />
+ } />
+ ```
+
+### Patch Changes
+
+- faa54eb8e: Fix display of warnings for forms that use both form-level and field-level validation
+- 6827982fe: Preserve the default `Button` color when using the `sx` prop with the `textLight` or `textDark` variant
+- Updated dependencies [7d8c36e6c]
+ - @comet/admin-theme@7.15.0
+ - @comet/admin-icons@7.15.0
+
## 7.14.0
### Minor Changes
diff --git a/packages/admin/admin/package.json b/packages/admin/admin/package.json
index 7446604699..0962bbf4e7 100644
--- a/packages/admin/admin/package.json
+++ b/packages/admin/admin/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/admin",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/admin",
"type": "git",
@@ -28,7 +28,7 @@
"test:watch": "jest --watch"
},
"dependencies": {
- "@comet/admin-icons": "workspace:^7.14.0",
+ "@comet/admin-icons": "workspace:^7.15.0",
"@mui/utils": "^6.4.3",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
@@ -46,9 +46,9 @@
"@apollo/client": "^3.13.1",
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
- "@comet/eslint-plugin": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
+ "@comet/eslint-plugin": "workspace:^7.15.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/material": "^6.4.5",
diff --git a/packages/admin/admin/src/common/buttons/Button.tsx b/packages/admin/admin/src/common/buttons/Button.tsx
index d3fadee9b6..cd46c287f4 100644
--- a/packages/admin/admin/src/common/buttons/Button.tsx
+++ b/packages/admin/admin/src/common/buttons/Button.tsx
@@ -71,6 +71,7 @@ const getMobileIconNode = ({ mobileIcon, startIcon, endIcon }: Pick(inProps: ButtonProps, ref: ForwardedRef) => {
const {
slotProps,
+ sx,
variant = "primary",
responsive,
mobileIcon = "auto",
@@ -97,6 +98,10 @@ export const Button = forwardRef((inProps: But
const commonButtonProps = {
...variantToMuiProps[variant],
+ sx: {
+ ...variantToMuiProps[variant].sx,
+ ...sx,
+ },
...restProps,
ownerState,
...slotProps?.root,
diff --git a/packages/admin/admin/src/dataGrid/DataGridPanel.tsx b/packages/admin/admin/src/dataGrid/DataGridPanel.tsx
new file mode 100644
index 0000000000..8215b88e31
--- /dev/null
+++ b/packages/admin/admin/src/dataGrid/DataGridPanel.tsx
@@ -0,0 +1,408 @@
+import { Add, Check, Close, Reset } from "@comet/admin-icons";
+import {
+ type ComponentsOverrides,
+ css,
+ // eslint-disable-next-line no-restricted-imports
+ Dialog,
+ DialogContent,
+ Divider,
+ type Theme,
+ Typography,
+ useMediaQuery,
+ useTheme,
+ useThemeProps,
+} from "@mui/material";
+import {
+ gridColumnVisibilityModelSelector,
+ gridFilterableColumnDefinitionsSelector,
+ type GridFilterItem,
+ gridFilterModelSelector,
+ GridPanel,
+ type GridPanelProps,
+ gridPreferencePanelStateSelector,
+ GridPreferencePanelsValue,
+ useGridApiContext,
+ useGridRootProps,
+ useGridSelector,
+} from "@mui/x-data-grid";
+import { type DataGridProcessedProps } from "@mui/x-data-grid/internals";
+import { type ReactNode, useCallback, useMemo } from "react";
+import { FormattedMessage } from "react-intl";
+
+import { Button } from "../common/buttons/Button";
+import { createComponentSlot } from "../helpers/createComponentSlot";
+import { type ThemedComponentBaseProps } from "../helpers/ThemedComponentBaseProps";
+
+const panelTypeTitle: Record = {
+ [GridPreferencePanelsValue.filters]: ,
+ [GridPreferencePanelsValue.columns]: ,
+};
+
+export type DataGridPanelClassKey =
+ | "desktopGridPanel"
+ | "desktopPanelFooterDivider"
+ | "desktopPanelFooter"
+ | "desktopAddFilterButton"
+ | "mobileDialog"
+ | "mobileDialogHeader"
+ | "mobileDialogTitle"
+ | "mobileDialogCloseButton"
+ | "mobileDialogContent"
+ | "mobileDialogFooter"
+ | "mobileAddFilterButtonWrapper"
+ | "mobileAddFilterButton"
+ | "mobileDialogFooterActions"
+ | "resetFiltersButton"
+ | "resetColumnsButton"
+ | "applyButton";
+
+export type DataGridPanelProps = Pick &
+ Omit<
+ ThemedComponentBaseProps<{
+ desktopGridPanel: typeof GridPanel;
+ desktopPanelFooterDivider: typeof Divider;
+ desktopPanelFooter: "div";
+ desktopAddFilterButton: typeof Button;
+ mobileDialog: typeof Dialog;
+ mobileDialogHeader: "div";
+ mobileDialogTitle: typeof Typography;
+ mobileDialogCloseButton: typeof Button;
+ mobileDialogContent: typeof DialogContent;
+ mobileDialogFooter: "div";
+ mobileAddFilterButtonWrapper: "div";
+ mobileAddFilterButton: typeof Button;
+ mobileDialogFooterActions: "div";
+ resetFiltersButton: typeof Button;
+ resetColumnsButton: typeof Button;
+ applyButton: typeof Button;
+ }>,
+ "sx"
+ > & {
+ iconMapping?: {
+ closeDialog?: ReactNode;
+ addFilter?: ReactNode;
+ resetFilters?: ReactNode;
+ resetColumns?: ReactNode;
+ apply?: ReactNode;
+ };
+ };
+
+const addFilterText = ;
+
+let lastAddedFilterItemId = 0;
+
+export const DataGridPanel = (inProps: DataGridPanelProps) => {
+ const { children, open, slotProps, iconMapping = {} } = useThemeProps({ props: inProps, name: "CometAdminDataGridPanel" });
+ const apiRef = useGridApiContext();
+ const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
+ const filterableColumns = useGridSelector(apiRef, gridFilterableColumnDefinitionsSelector);
+ const { openedPanelValue } = useGridSelector(apiRef, gridPreferencePanelStateSelector);
+ const initialColumnVisibilityModel = useMemo(() => gridColumnVisibilityModelSelector(apiRef), [apiRef]);
+ const rootProps = useGridRootProps();
+
+ const {
+ closeDialog: closeDialogIcon = ,
+ addFilter: addFilterIcon = ,
+ resetFilters: resetFiltersIcon = ,
+ resetColumns: resetColumnsIcon = ,
+ apply: applyIcon = ,
+ } = iconMapping;
+
+ const geNewFilterItem = useCallback(() => {
+ const firstColumnWithOperator = filterableColumns.find((colDef) => colDef.filterOperators?.length);
+
+ if (!firstColumnWithOperator?.filterOperators?.length) {
+ return null;
+ }
+
+ return {
+ field: firstColumnWithOperator.field,
+ operator: firstColumnWithOperator.filterOperators[0].value,
+ id: lastAddedFilterItemId++,
+ };
+ }, [filterableColumns]);
+
+ const filterItems = useMemo(() => {
+ if (filterModel.items.length) {
+ return filterModel.items;
+ }
+
+ const newFilterItem = geNewFilterItem();
+
+ if (newFilterItem) {
+ return [newFilterItem];
+ }
+
+ return [];
+ }, [filterModel.items, geNewFilterItem]);
+
+ const addNewFilter = useCallback(() => {
+ const newFilterItem = geNewFilterItem();
+
+ if (newFilterItem) {
+ apiRef.current.upsertFilterItems([...filterItems, newFilterItem]);
+ }
+ }, [apiRef, filterItems, geNewFilterItem]);
+
+ const resetFilters = useCallback(() => {
+ apiRef.current.setFilterModel({ items: [] });
+ }, [apiRef]);
+
+ const resetColumns = useCallback(() => {
+ apiRef.current.setColumnVisibilityModel(initialColumnVisibilityModel);
+ }, [apiRef, initialColumnVisibilityModel]);
+
+ const closeDialog = useCallback(() => {
+ if (openedPanelValue === GridPreferencePanelsValue.filters) {
+ apiRef.current.hideFilterPanel();
+ } else {
+ apiRef.current.hidePreferences();
+ }
+ }, [apiRef, openedPanelValue]);
+
+ const theme = useTheme();
+ const renderFullScreen = useMediaQuery(theme.breakpoints.down("md"));
+
+ const resetFiltersButton = (
+
+
+
+ );
+
+ const resetColumnsButton = (
+
+
+
+ );
+
+ const applyButton = (
+
+
+
+ );
+
+ if (renderFullScreen) {
+ return (
+
+
+ {!!openedPanelValue && panelTypeTitle[openedPanelValue]}
+
+ {closeDialogIcon}
+
+
+ {children}
+
+ {openedPanelValue === GridPreferencePanelsValue.filters && !rootProps.disableMultipleColumnsFiltering && (
+
+
+ {addFilterText}
+
+
+ )}
+
+ {openedPanelValue === GridPreferencePanelsValue.filters && (
+ <>
+ {resetFiltersButton}
+ {applyButton}
+ >
+ )}
+ {openedPanelValue === GridPreferencePanelsValue.columns && (
+ <>
+ {resetColumnsButton}
+ {applyButton}
+ >
+ )}
+
+
+
+ );
+ }
+
+ return (
+
+ {children}
+
+
+ {openedPanelValue === GridPreferencePanelsValue.filters && (
+ <>
+
+ {!rootProps.disableMultipleColumnsFiltering && (
+
+ {addFilterText}
+
+ )}
+
+ {resetFiltersButton}
+ >
+ )}
+ {openedPanelValue === GridPreferencePanelsValue.columns && (
+ <>
+ {resetColumnsButton}
+ {applyButton}
+ >
+ )}
+
+
+ );
+};
+
+const DesktopGridPanel = createComponentSlot(GridPanel)({
+ componentName: "DataGridPanel",
+ slotName: "desktopGridPanel",
+})(css`
+ .MuiDataGrid-panelHeader,
+ .MuiDataGrid-panelFooter {
+ // Hide MUIs header and footer so we can add our own with a better structure for styling
+ display: none;
+ }
+`);
+
+const DesktopPanelFooterDivider = createComponentSlot(Divider)({
+ componentName: "DataGridPanel",
+ slotName: "desktopPanelFooterDivider",
+})();
+
+const DesktopPanelFooter = createComponentSlot("div")({
+ componentName: "DataGridPanel",
+ slotName: "desktopPanelFooter",
+})(
+ ({ theme }) => css`
+ display: flex;
+ justify-content: space-between;
+ padding: ${theme.spacing(4)};
+ `,
+);
+
+const DesktopAddFilterButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "desktopAddFilterButton",
+})();
+
+const MobileDialog = createComponentSlot(Dialog)({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialog",
+})(css`
+ .MuiDataGrid-panelContent {
+ max-height: none;
+ }
+
+ .MuiDataGrid-panelHeader,
+ .MuiDataGrid-panelFooter {
+ // Hide MUIs header and footer so we can add our own with a better structure for styling
+ display: none;
+ }
+`);
+
+const MobileDialogHeader = createComponentSlot("div")({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogHeader",
+})(
+ ({ theme }) => css`
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: ${theme.spacing(2)};
+ padding-left: ${theme.spacing(4)};
+ box-shadow: ${theme.shadows[4]};
+ `,
+);
+
+const MobileDialogTitle = createComponentSlot(Typography)({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogTitle",
+})();
+
+const MobileDialogCloseButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogCloseButton",
+})(css`
+ min-width: 0; // TODO: Should this be done in the theme?
+`);
+
+const MobileDialogContent = createComponentSlot(DialogContent)({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogContent",
+})(
+ ({ theme }) => css`
+ padding: 0;
+ background-color: white;
+
+ ${theme.breakpoints.up("sm")} {
+ padding: 0;
+ }
+ `,
+);
+
+const MobileDialogFooter = createComponentSlot("div")({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogFooter",
+})();
+
+const MobileAddFilterButtonWrapper = createComponentSlot("div")({
+ componentName: "DataGridPanel",
+ slotName: "mobileAddFilterButtonWrapper",
+})(
+ ({ theme }) => css`
+ position: relative;
+ display: flex;
+ justify-content: center;
+ padding: ${theme.spacing(2)} ${theme.spacing(4)};
+ box-shadow: ${theme.shadows[4]};
+ border-bottom: 1px solid ${theme.palette.divider};
+ `,
+);
+
+const MobileAddFilterButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "mobileAddFilterButton",
+})();
+
+const MobileDialogFooterActions = createComponentSlot("div")({
+ componentName: "DataGridPanel",
+ slotName: "mobileDialogFooterActions",
+})(
+ ({ theme }) => css`
+ display: flex;
+ justify-content: space-between;
+ padding: ${theme.spacing(4)};
+ box-shadow: ${theme.shadows[4]};
+ `,
+);
+
+const ResetFiltersButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "resetFiltersButton",
+})();
+
+const ResetColumnsButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "resetColumnsButton",
+})();
+
+const ApplyButton = createComponentSlot(Button)({
+ componentName: "DataGridPanel",
+ slotName: "applyButton",
+})();
+
+declare module "@mui/material/styles" {
+ interface ComponentsPropsList {
+ CometAdminDataGridPanel: DataGridPanelProps;
+ }
+
+ interface ComponentNameToClassKey {
+ CometAdminDataGridPanel: DataGridPanelClassKey;
+ }
+
+ interface Components {
+ CometAdminDataGridPanel?: {
+ defaultProps?: Partial;
+ styleOverrides?: ComponentsOverrides["CometAdminDataGridPanel"];
+ };
+ }
+}
diff --git a/packages/admin/admin/src/form/fields/CheckboxField.tsx b/packages/admin/admin/src/form/fields/CheckboxField.tsx
index 46baad8e03..97a35b36b7 100644
--- a/packages/admin/admin/src/form/fields/CheckboxField.tsx
+++ b/packages/admin/admin/src/form/fields/CheckboxField.tsx
@@ -1,10 +1,11 @@
import { FormControlLabel, type FormControlLabelProps } from "@mui/material";
+import { type ReactNode } from "react";
import { FinalFormCheckbox, type FinalFormCheckboxProps } from "../Checkbox";
import { Field, type FieldProps } from "../Field";
export interface CheckboxFieldProps extends FieldProps {
- fieldLabel?: string;
+ fieldLabel?: ReactNode;
componentsProps?: {
formControlLabel?: FormControlLabelProps;
finalFormCheckbox?: FinalFormCheckboxProps;
diff --git a/packages/admin/admin/src/form/fields/SwitchField.tsx b/packages/admin/admin/src/form/fields/SwitchField.tsx
index 88210cd69a..c7d0197544 100644
--- a/packages/admin/admin/src/form/fields/SwitchField.tsx
+++ b/packages/admin/admin/src/form/fields/SwitchField.tsx
@@ -1,10 +1,13 @@
import { FormControlLabel, type FormControlLabelProps } from "@mui/material";
+import { type ReactNode } from "react";
import { Field, type FieldProps } from "../Field";
import { FinalFormSwitch, type FinalFormSwitchProps } from "../Switch";
-export interface SwitchFieldProps extends FieldProps {
- fieldLabel?: string;
+export interface SwitchFieldProps extends Omit, "label"> {
+ name: string;
+ fieldLabel?: ReactNode;
+ label?: ReactNode | ((checked?: boolean) => ReactNode);
componentsProps?: {
formControlLabel?: FormControlLabelProps;
finalFormSwitch?: FinalFormSwitchProps;
@@ -16,7 +19,11 @@ export const SwitchField = ({ fieldLabel, label, componentsProps = {}, ...restPr
return (
{(props) => (
- } {...formControlLabelProps} />
+ }
+ {...formControlLabelProps}
+ />
)}
);
diff --git a/packages/admin/admin/src/index.ts b/packages/admin/admin/src/index.ts
index 517120af07..bb371466a6 100644
--- a/packages/admin/admin/src/index.ts
+++ b/packages/admin/admin/src/index.ts
@@ -54,6 +54,7 @@ export { CrudContextMenu, CrudContextMenuClassKey, CrudContextMenuProps } from "
export { CrudMoreActionsMenuClassKey } from "./dataGrid/CrudMoreActionsMenu";
export { CrudMoreActionsMenu, CrudMoreActionsMenuContext, CrudMoreActionsMenuItem, CrudMoreActionsMenuProps } from "./dataGrid/CrudMoreActionsMenu";
export { CrudVisibility, CrudVisibilityProps } from "./dataGrid/CrudVisibility";
+export { DataGridPanel, DataGridPanelClassKey, DataGridPanelProps } from "./dataGrid/DataGridPanel";
export { ExportApi, useDataGridExcelExport } from "./dataGrid/excelExport/useDataGridExcelExport";
export { GridCellContent, GridCellContentClassKey, GridCellContentProps } from "./dataGrid/GridCellContent";
export { GridActionsColDef, GridBaseColDef, GridColDef, GridSingleSelectColDef } from "./dataGrid/GridColDef";
diff --git a/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx b/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx
index 30b19061d6..553b87d5c7 100644
--- a/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx
+++ b/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx
@@ -1,15 +1,14 @@
import { ArrowDown, ArrowUp, Check, Clear, Close, Delete, MoreVertical, Search } from "@comet/admin-icons";
import {
buttonBaseClasses,
- buttonClasses,
- formControlClasses,
+ getSwitchUtilityClass,
iconButtonClasses,
- inputAdornmentClasses,
inputBaseClasses,
- inputClasses,
inputLabelClasses,
+ nativeSelectClasses,
svgIconClasses,
type SvgIconProps,
+ switchClasses,
TextField,
type TextFieldProps,
} from "@mui/material";
@@ -31,16 +30,20 @@ const getDensityHeightValue = (density: string | unknown, spacing: Spacing) => {
}
};
-export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { palette, shadows, spacing }) => ({
+const filtersLeftSectionWidth = 120;
+const filterDeleteIconSize = 32;
+const filterLeftSectionGap = 5;
+const filterOperatorInputWidth = filtersLeftSectionWidth - filterDeleteIconSize - filterLeftSectionGap;
+
+export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { palette, shadows, spacing, breakpoints }) => ({
...component,
defaultProps: {
...component?.defaultProps,
disableRowSelectionOnClick: true,
slots: {
- /* @TODO: add FilterPanelAddIcon to display Comet Add Icon once MUI Datagrid is updated to v6 or higher */
QuickFilterIcon: Search,
QuickFilterClearIcon: Clear,
- FilterPanelDeleteIcon: Delete,
+ FilterPanelDeleteIcon: (props: SvgIconProps) => ,
BooleanCellTrueIcon: Check,
BooleanCellFalseIcon: Close,
ColumnSortedAscendingIcon: ArrowUp,
@@ -49,8 +52,16 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, {
ColumnMenuIcon: (props: SvgIconProps) => ,
...component?.defaultProps?.slots,
},
+ slotProps: {
+ ...component?.defaultProps?.slotProps,
+ baseButton: {
+ color: "info",
+ ...component?.defaultProps?.slotProps?.baseButton,
+ },
+ },
localeText: {
noRowsLabel: GRID_DEFAULT_LOCALE_TEXT.noResultsOverlayLabel,
+ columnsPanelTextFieldLabel: "",
...component?.defaultProps?.localeText,
},
},
@@ -69,6 +80,34 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, {
},
},
},
+ panelHeader: {
+ padding: `4px 4px ${spacing(1)} 4px`,
+ borderBottom: `1px solid ${palette.divider}`,
+ },
+ columnsManagement: {
+ padding: 0,
+ },
+ columnsManagementRow: {
+ marginBottom: spacing(2),
+
+ "&:last-child": {
+ marginBottom: 0,
+ },
+
+ [`& .${switchClasses.root}`]: {
+ marginRight: 0,
+ },
+ [`& .${switchClasses.root} .${switchClasses.thumb}`]: {
+ width: 10,
+ height: 10,
+ },
+ [`& .${switchClasses.root} .${switchClasses.switchBase}`]: {
+ padding: 3,
+ },
+ [`& .${switchClasses.root} .${switchClasses.switchBase}.${getSwitchUtilityClass("checked")}`]: {
+ transform: "translateX(20px)",
+ },
+ },
columnHeader: ({ ownerState }) => ({
/* !important is required to override inline styles */
height: `${getDensityHeightValue(ownerState?.density, spacing)} !important`,
@@ -124,126 +163,124 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, {
height: "20px",
marginRight: "10px",
},
- panel: {
- ["@media (max-width: 900px)"]: {
- width: "100%",
- transform: "translate3d(0,0,0)",
- },
- },
panelContent: {
- padding: spacing(1, 0),
- [`& .${gridClasses.filterForm}:first-child .${gridClasses.filterFormLogicOperatorInput}`]: {
- display: "flex",
- },
- ["@media (max-width: 900px)"]: {
- maxHeight: "none",
- padding: 0,
- },
+ padding: spacing(4),
+ },
+ paper: {
+ border: `1px solid ${palette.grey[100]}`,
+ boxShadow: shadows[4],
+ borderRadius: 4,
+ maxHeight: "none",
+ flexDirection: "column",
},
filterForm: {
- margin: spacing(5, 4, 0, 4),
- padding: spacing(2, 1),
- gap: "5px",
- borderBottom: `1px solid ${palette.grey[50]}`,
- ["@media (max-width: 900px)"]: {
- flexDirection: "row",
- flexWrap: "wrap",
- margin: spacing(4, 4, 0, 4),
- gap: 0,
- padding: 0,
- paddingBottom: spacing(5),
- "&:last-child": {
- marginBottom: 0,
- paddingBottom: 0,
- },
- },
- "&:last-child": {
- border: "none",
+ flexDirection: "row",
+ flexWrap: "wrap",
+ padding: 0,
+
+ [`${breakpoints.up("md")}`]: {
+ flexWrap: "nowrap",
+ gap: spacing(1),
},
- [`.${formControlClasses.root}`]: {
- marginRight: 0,
+
+ ["&:not(:last-child)"]: {
+ paddingBottom: spacing(4),
+ marginBottom: spacing(4),
+ borderBottom: `1px solid ${palette.divider}`,
+
+ [`${breakpoints.up("md")}`]: {
+ paddingBottom: spacing(2),
+ marginBottom: spacing(2),
+ borderBottomColor: palette.grey[50],
+ },
},
- [`.${iconButtonClasses.root}`]: {
- height: 32,
- width: 32,
+
+ [`&:first-child .${gridClasses.filterFormOperatorInput}`]: {
+ // The first "Operator"-select is fully hidden by default when there is only one filter.
+ // Setting `display: block` makes sure it takes up it's space as if it were visible to prevent the alignment from breaking.
+ // Even though `display: block` is set now, it's still not visible, due to it's default styling of `visibility: hidden`.
+ display: "block",
},
+
[`.${inputLabelClasses.root}`]: {
- transform: "translateY(-22px)",
+ position: "static",
+ transform: "none",
fontSize: 14,
- ["@media (max-width: 900px)"]: {
- position: "relative",
- transform: "unset",
- },
- },
- [`.${inputClasses.root}`]: {
- marginTop: 0,
+ fontWeight: 600,
},
- [`& .${inputAdornmentClasses.root}`]: {
- padding: spacing(0, 1, 0, 0),
- },
- },
- filterFormLogicOperatorInput: {
- ["@media (max-width: 900px)"]: {
- padding: spacing(2, 1),
- width: "27.2%",
+
+ [`.${nativeSelectClasses.select}`]: {
+ whiteSpace: "nowrap",
+ textOverflow: "ellipsis",
},
},
filterFormDeleteIcon: {
+ width: filterDeleteIconSize,
+ height: filterDeleteIconSize,
+ marginRight: filterLeftSectionGap,
+ marginTop: "auto",
+ marginBottom: 3,
justifyContent: "center",
- [`& .${svgIconClasses.root}`]: {
- width: 16,
- height: 16,
+ [`${breakpoints.up("md")}`]: {
+ marginRight: 0,
},
- ["@media (max-width: 900px)"]: {
- padding: spacing(2, 1),
- alignItems: "flex-start",
- justifyContent: "flex-end",
- width: "11.1%",
+ [`& > .${iconButtonClasses.root}`]: {
+ height: "100%",
},
},
- panelFooter: {
- borderTop: `1px solid ${palette.grey[100]}`,
- padding: "7px 0",
- [`.${buttonClasses.root}`]: {
- color: palette.primary.main,
- },
- ["@media (max-width: 900px)"]: {
- justifyContent: "center",
- boxShadow: shadows[4],
+ filterFormLogicOperatorInput: {
+ width: filterOperatorInputWidth,
+ marginRight: 0,
+
+ [`${breakpoints.up("md")}`]: {
+ width: 80,
},
},
filterFormColumnInput: {
- marginRight: spacing(4),
+ width: `calc(100% - ${filtersLeftSectionWidth}px)`,
+ paddingLeft: spacing(2),
+ boxSizing: "border-box",
- ["@media (max-width: 900px)"]: {
- padding: spacing(2, 1),
- width: "61.6%",
+ [`${breakpoints.up("md")}`]: {
+ width: 199,
+ paddingLeft: 0,
},
},
filterFormOperatorInput: {
- margin: spacing(0, 4, 0, 0),
+ marginTop: spacing(3),
+ flexBasis: filterOperatorInputWidth,
+ flexGrow: 1,
- ["@media (max-width: 900px)"]: {
- padding: spacing(2, 1),
- width: "38.3%",
+ [`${breakpoints.up("md")}`]: {
+ marginTop: 0,
+ width: 110,
},
},
filterFormValueInput: {
- ["@media (max-width: 900px)"]: {
- padding: spacing(2, 1),
- width: "61.6%",
+ width: `calc(100% - ${filtersLeftSectionWidth}px)`,
+ paddingLeft: spacing(2),
+ boxSizing: "border-box",
+ marginTop: spacing(3),
+
+ [`${breakpoints.up("md")}`]: {
+ width: 199,
+ paddingLeft: 0,
+ marginTop: 0,
},
- },
- paper: {
- boxShadow: shadows[4],
- border: `1px solid ${palette.divider}`,
- borderRadius: "4px",
- ["@media (max-width: 900px)"]: {
- height: "100%",
- maxHeight: "none",
+
+ "&:empty": {
+ display: "none", // Make space for `filterFormOperatorInput` to expand and take up the full width
},
+
+ [`& .${inputBaseClasses.root}`]: {
+ marginTop: 0,
+ },
+ },
+ panelFooter: {
+ padding: spacing(2),
+ borderTop: `1px solid ${palette.divider}`,
},
// @ts-expect-error This key exists but is missing in the types.
toolbarQuickFilter: {
diff --git a/packages/admin/cms-admin/CHANGELOG.md b/packages/admin/cms-admin/CHANGELOG.md
index 21333d3210..065dc906a0 100644
--- a/packages/admin/cms-admin/CHANGELOG.md
+++ b/packages/admin/cms-admin/CHANGELOG.md
@@ -1,5 +1,24 @@
# @comet/cms-admin
+## 7.15.0
+
+### Patch Changes
+
+- 46ab330da: Adapt styling of the dashboard header to match the Comet DXP design
+- Updated dependencies [e056e8f3d]
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [faa54eb8e]
+- Updated dependencies [7d8c36e6c]
+- Updated dependencies [a189d4ed9]
+- Updated dependencies [6827982fe]
+- Updated dependencies [7d8c36e6c]
+ - @comet/blocks-admin@7.15.0
+ - @comet/admin@7.15.0
+ - @comet/admin-theme@7.15.0
+ - @comet/admin-date-time@7.15.0
+ - @comet/admin-icons@7.15.0
+ - @comet/admin-rte@7.15.0
+
## 7.14.0
### Minor Changes
diff --git a/packages/admin/cms-admin/package.json b/packages/admin/cms-admin/package.json
index f1e6a71adc..60929d6d72 100644
--- a/packages/admin/cms-admin/package.json
+++ b/packages/admin/cms-admin/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/cms-admin",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/admin/cms-admin",
"type": "git",
@@ -32,10 +32,10 @@
"test:watch": "jest --watch"
},
"dependencies": {
- "@comet/admin": "workspace:^7.14.0",
- "@comet/admin-date-time": "workspace:^7.14.0",
- "@comet/admin-icons": "workspace:^7.14.0",
- "@comet/admin-rte": "workspace:^7.14.0",
+ "@comet/admin": "workspace:^7.15.0",
+ "@comet/admin-date-time": "workspace:^7.15.0",
+ "@comet/admin-icons": "workspace:^7.15.0",
+ "@comet/admin-rte": "workspace:^7.15.0",
"axios": "^0.29.0",
"change-case": "^4.1.2",
"class-validator": "^0.14.1",
@@ -67,9 +67,9 @@
"@apollo/client": "^3.13.1",
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
- "@comet/admin-babel-preset": "workspace:^7.14.0",
- "@comet/cli": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/admin-babel-preset": "workspace:^7.15.0",
+ "@comet/cli": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@graphql-codegen/cli": "^5.0.5",
@@ -117,7 +117,6 @@
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"ts-jest": "^29.2.6",
- "ts-node": "^10.9.2",
"typescript": "^5.7.3"
},
"peerDependencies": {
diff --git a/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx b/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx
index 60c4cbf4fc..bed83b6265 100644
--- a/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx
+++ b/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx
@@ -392,10 +392,7 @@ export function createColumnsBlock(
-
+
@@ -410,7 +407,7 @@ export function createColumnsBlock(
size="large"
startIcon={}
>
-
+
)}
diff --git a/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx b/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx
index ff307e761d..03d4deb348 100644
--- a/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx
+++ b/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx
@@ -1,5 +1,4 @@
-import { Field, FinalFormSwitch, OnChangeField } from "@comet/admin";
-import { FormControlLabel } from "@mui/material";
+import { OnChangeField, SwitchField } from "@comet/admin";
import { useForm } from "react-final-form";
import { FormattedMessage } from "react-intl";
@@ -7,30 +6,9 @@ export const VideoOptionsFields = () => {
const form = useForm();
return (
<>
-
- {(props) => (
- }
- control={}
- />
- )}
-
-
- {(props) => (
- }
- control={}
- />
- )}
-
-
- {(props) => (
- }
- control={}
- />
- )}
-
+ } />
+ } />
+ } />
{/* case: autoplay = false and showControls = false is not allowed */}
{(value, previousValue) => {
diff --git a/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx b/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx
index d2ee1602f8..a2a1223133 100644
--- a/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx
+++ b/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx
@@ -435,11 +435,11 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi =
uploadedFiles.push({ id: response.data.id, parentId: targetFolderId, type: "file", file });
} catch (err) {
errorOccurred = true;
- const typedErr = err as AxiosError<{ error: string; message: string; statusCode: number }>;
+ const typedErr = err as AxiosError<{ error: string; message: string; statusCode: number } | string>;
- if (typedErr.response?.data.error === "CometImageResolutionException") {
+ if (hasObjectErrorData(typedErr) && typedErr.response?.data.error === "CometImageResolutionException") {
addValidationError(file, );
- } else if (typedErr.response?.data.error === "CometValidationException") {
+ } else if (hasObjectErrorData(typedErr) && typedErr.response?.data.error === "CometValidationException") {
const message = typedErr.response.data.message;
const extension = `.${file.name.split(".").pop()}`;
@@ -454,7 +454,7 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi =
} else {
addValidationError(file, );
}
- } else if (typedErr.response?.data.message.includes("SVG contains forbidden content")) {
+ } else if (hasStringErrorData(typedErr) && typedErr.response?.data.includes("SVG contains forbidden content")) {
addValidationError(file, );
} else if (typedErr.response === undefined && typedErr.request) {
addValidationError(file, );
@@ -507,3 +507,13 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi =
newlyUploadedItems,
};
};
+
+const hasObjectErrorData = (
+ err: AxiosError<{ error: string; message: string; statusCode: number } | string>,
+): err is AxiosError<{ error: string; message: string; statusCode: number }> => {
+ return typeof err.response?.data === "object" && err.response?.data.error !== undefined;
+};
+
+const hasStringErrorData = (err: AxiosError<{ error: string; message: string; statusCode: number } | string>): err is AxiosError => {
+ return typeof err.response?.data === "string";
+};
diff --git a/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx b/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx
index fff6a4bc96..f58f7e76e5 100644
--- a/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx
+++ b/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx
@@ -3,12 +3,11 @@ import {
FilterBar,
FilterBarPopoverFilter,
FinalFormSearchTextField,
- FinalFormSwitch,
type IFilterApi,
type ISortInformation,
+ SwitchField,
TableFilterFinalForm,
} from "@comet/admin";
-import { FormControlLabel } from "@mui/material";
import { FormattedMessage, useIntl } from "react-intl";
import { type DamFilter } from "../../DamTable";
@@ -37,14 +36,10 @@ export const DamTableFilter = ({ filterApi, hideArchiveFilter }: DamTableFilterP
label={intl.formatMessage({ id: "comet.pages.dam.archived", defaultMessage: "Archived" })}
sx={{ marginRight: 2, marginLeft: 2 }}
>
-
- {(props) => (
- }
- label={}
- />
- )}
-
+ }
+ />
)}
name="sort">
diff --git a/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx b/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx
index 35e9cf9b74..e89f79608e 100644
--- a/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx
+++ b/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx
@@ -30,7 +30,7 @@ type RootProps = {
const Root = styled("div")`
position: relative;
- height: 300px;
+ height: 230px;
color: ${({ textColor }) => (textColor === "light" ? "white" : textColor === "dark" ? "black" : "inherit")};
background-color: ${({ theme }) => theme.palette.grey[700]};
@@ -45,13 +45,25 @@ const Root = styled("div")`
background-image: url(${backgroundImageUrl["2x"]});
}
`}
+
+ ${({ theme }) => theme.breakpoints.up("sm")} {
+ height: 300px;
+ }
`;
const Greeting = styled(Typography)`
position: absolute;
- left: ${({ theme }) => theme.spacing(8)};
- bottom: ${({ theme }) => theme.spacing(8)};
- font-size: 55px;
- line-height: 64px;
- font-weight: 200;
+ left: ${({ theme }) => theme.spacing(4)};
+ bottom: 12px;
+ font-size: 30px;
+ line-height: 38px;
+ font-weight: 160;
+
+ ${({ theme }) => theme.breakpoints.up("sm")} {
+ left: ${({ theme }) => theme.spacing(8)};
+ bottom: ${({ theme }) => theme.spacing(8)};
+ font-size: 55px;
+ line-height: 64px;
+ font-weight: 200;
+ }
`;
diff --git a/packages/admin/cms-admin/src/dashboard/DateTime.tsx b/packages/admin/cms-admin/src/dashboard/DateTime.tsx
index ba09030d63..6ffa15e1f3 100644
--- a/packages/admin/cms-admin/src/dashboard/DateTime.tsx
+++ b/packages/admin/cms-admin/src/dashboard/DateTime.tsx
@@ -29,18 +29,34 @@ export const DateTime = () => {
const Root = styled("div")`
position: absolute;
- top: ${({ theme }) => theme.spacing(4)};
- right: ${({ theme }) => theme.spacing(8)};
- font-weight: 200;
+ top: 16px;
+ right: ${({ theme }) => theme.spacing(4)};
text-align: right;
+
+ ${({ theme }) => theme.breakpoints.up("sm")} {
+ top: ${({ theme }) => theme.spacing(4)};
+ right: ${({ theme }) => theme.spacing(8)};
+ }
`;
const DateContainer = styled("div")`
- font-size: 33px;
- line-height: 39px;
+ font-size: 24px;
+ line-height: 28px;
+ font-weight: 150;
+
+ ${({ theme }) => theme.breakpoints.up("sm")} {
+ font-size: 33px;
+ line-height: 39px;
+ }
`;
const TimeContainer = styled("div")`
- font-size: 55px;
- line-height: 64px;
+ font-size: 36px;
+ line-height: 42px;
+ font-weight: 170;
+
+ ${({ theme }) => theme.breakpoints.up("sm")} {
+ font-size: 55px;
+ line-height: 64px;
+ }
`;
diff --git a/packages/api/cms-api/CHANGELOG.md b/packages/api/cms-api/CHANGELOG.md
index 7d6bc94388..a70b675a47 100644
--- a/packages/api/cms-api/CHANGELOG.md
+++ b/packages/api/cms-api/CHANGELOG.md
@@ -1,5 +1,18 @@
# @comet/cms-api
+## 7.15.0
+
+### Patch Changes
+
+- 83b8111d6: Allow `use` tag in SVG again
+
+ `use` can be used to define paths once in a SVG and then integrating them multiple times via anchor links: ``. This should not be prohibited.
+
+ It's still not possible to use `use` to reference external files, since we still prohibit `href` and `xlink:href` attributes starting with `http://`, `https://` and `javascript:`.
+
+- e6f9641db: Add fallback values for users created via ID token
+ - @comet/blocks-api@7.15.0
+
## 7.14.0
### Minor Changes
diff --git a/packages/api/cms-api/package.json b/packages/api/cms-api/package.json
index eea33d916e..01c6746f94 100644
--- a/packages/api/cms-api/package.json
+++ b/packages/api/cms-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/cms-api",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/api/cms-api",
"type": "git",
@@ -69,7 +69,7 @@
},
"devDependencies": {
"@aws-sdk/types": "^3.734.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@golevelup/ts-jest": "^0.6.2",
"@kubernetes/client-node": "^1.0.0",
"@mikro-orm/core": "^6.4.7",
diff --git a/packages/api/cms-api/src/dam/files/files.utils.ts b/packages/api/cms-api/src/dam/files/files.utils.ts
index dcf60f8a0e..31cad371b5 100644
--- a/packages/api/cms-api/src/dam/files/files.utils.ts
+++ b/packages/api/cms-api/src/dam/files/files.utils.ts
@@ -52,7 +52,6 @@ type SvgNode =
const disallowedSvgTags = [
"script", // can lead to XSS
"foreignObject", // can embed non-SVG content
- "use", // can load external resources
"image", // can load external resources
"animate", // can modify attributes; resource exhaustion
"animateMotion", // can modify attributes; resource exhaustion
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 79406c08ea..e2cb0b1204 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,7 @@
# @comet/cli
+## 7.15.0
+
## 7.14.0
## 7.13.0
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 2e63863120..e36df3a1b8 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/cli",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/cli",
"type": "git",
@@ -31,7 +31,7 @@
"ts-node": "^10.9.2"
},
"devDependencies": {
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@types/node": "^22.13.5",
"eslint": "^9.20.1",
"npm-run-all2": "^5.0.2",
diff --git a/packages/eslint-config/CHANGELOG.md b/packages/eslint-config/CHANGELOG.md
index 06d5fbce30..6178665ef3 100644
--- a/packages/eslint-config/CHANGELOG.md
+++ b/packages/eslint-config/CHANGELOG.md
@@ -1,5 +1,11 @@
# @comet/eslint-config
+## 7.15.0
+
+### Patch Changes
+
+- @comet/eslint-plugin@7.15.0
+
## 7.14.0
### Patch Changes
diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json
index d70c84ce00..ea91b87a96 100644
--- a/packages/eslint-config/package.json
+++ b/packages/eslint-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/eslint-config",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/eslint-config",
"type": "git",
@@ -30,8 +30,8 @@
"dependencies": {
"@calm/eslint-plugin-react-intl": "^1.4.1",
"@eslint/js": "^9.21.0",
- "@next/eslint-plugin-next": "^15.1.7",
- "eslint-config-next": "^15.1.7",
+ "@next/eslint-plugin-next": "^15.2.0",
+ "eslint-config-next": "^15.2.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-formatjs": "^5.2.14",
"eslint-plugin-jsonc": "^2.19.1",
diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md
index 46d0ffe5d0..12a1690025 100644
--- a/packages/eslint-plugin/CHANGELOG.md
+++ b/packages/eslint-plugin/CHANGELOG.md
@@ -1,5 +1,7 @@
# @comet/eslint-plugin
+## 7.15.0
+
## 7.14.0
## 7.13.0
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index c1c7728e4c..9b466ba992 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/eslint-plugin",
- "version": "7.14.0",
+ "version": "7.15.0",
"main": "lib/index.js",
"scripts": {
"build": "$npm_execpath run clean && tsc",
diff --git a/packages/site/cms-site/CHANGELOG.md b/packages/site/cms-site/CHANGELOG.md
index 904bc595a9..ed77726873 100644
--- a/packages/site/cms-site/CHANGELOG.md
+++ b/packages/site/cms-site/CHANGELOG.md
@@ -1,5 +1,11 @@
# @comet/cms-site
+## 7.15.0
+
+### Patch Changes
+
+- 75fb1d0d4: Fix block preview not rendering before user interaction
+
## 7.14.0
### Minor Changes
diff --git a/packages/site/cms-site/package.json b/packages/site/cms-site/package.json
index 55be42c73f..67fdf7f34f 100644
--- a/packages/site/cms-site/package.json
+++ b/packages/site/cms-site/package.json
@@ -1,6 +1,6 @@
{
"name": "@comet/cms-site",
- "version": "7.14.0",
+ "version": "7.15.0",
"repository": {
"directory": "packages/site/cms-site",
"type": "git",
@@ -34,8 +34,8 @@
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
- "@comet/cli": "workspace:^7.14.0",
- "@comet/eslint-config": "workspace:^7.14.0",
+ "@comet/cli": "workspace:^7.15.0",
+ "@comet/eslint-config": "workspace:^7.15.0",
"@types/draft-js": "^0.11.18",
"@types/jest": "^29.5.14",
"@types/lodash.isequal": "^4.5.8",
diff --git a/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx b/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx
index 1fb6661502..2ec230ccd6 100644
--- a/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx
+++ b/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef } from "react";
+import { useEffect, useState } from "react";
import { createFetchInMemoryCache } from "../graphQLFetch/fetchInMemoryCache";
import { convertPreviewDataToHeaders, createFetchWithDefaults, createGraphQLFetch, type GraphQLFetch } from "../graphQLFetch/graphQLFetch";
@@ -16,16 +16,19 @@ export function useBlockPreviewFetch(apiUrl: string): { fetch: Fetch; graphQLFet
export function useBlockPreviewFetch(apiUrl?: string | undefined): { fetch: Fetch; graphQLFetch?: GraphQLFetch };
export function useBlockPreviewFetch(apiUrl?: string | undefined) {
const { showOnlyVisible, graphQLApiUrl } = useIFrameBridge();
+ const [graphQLFetch, setGraphQLFetch] = useState(() =>
+ apiUrl ? createBlockPreviewFetch(apiUrl, !showOnlyVisible) : undefined,
+ );
- const graphQLFetchRef = useRef(apiUrl ? createBlockPreviewFetch(apiUrl, !showOnlyVisible) : undefined);
useEffect(() => {
if (graphQLApiUrl) {
- graphQLFetchRef.current = createBlockPreviewFetch(graphQLApiUrl, !showOnlyVisible);
+ // We need to use an updater function here because createBlockPreviewFetch's return value would otherwise be incorrectly treated as an updater function.
+ setGraphQLFetch(() => createBlockPreviewFetch(graphQLApiUrl, !showOnlyVisible));
}
}, [showOnlyVisible, graphQLApiUrl]);
return {
- graphQLFetch: graphQLFetchRef.current,
+ graphQLFetch,
fetch: cachingFetch,
};
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1fa1c4ca17..125a979387 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -327,6 +327,9 @@ importers:
class-validator:
specifier: ^0.14.1
version: 0.14.1
+ cli-progress:
+ specifier: ^3.12.0
+ version: 3.12.0
compression:
specifier: ^1.8.0
version: 1.8.0
@@ -379,6 +382,9 @@ importers:
'@nestjs/schematics':
specifier: ^11.0.1
version: 11.0.1(chokidar@4.0.3)(typescript@5.7.3)
+ '@types/cli-progress':
+ specifier: ^3.11.6
+ version: 3.11.6
'@types/compression':
specifier: ^1.7.5
version: 1.7.5
@@ -806,7 +812,7 @@ importers:
packages/admin/admin:
dependencies:
'@comet/admin-icons':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-icons
'@mui/utils':
specifier: ^6.4.3
@@ -855,13 +861,13 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@comet/eslint-plugin':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-plugin
'@emotion/react':
specifier: ^11.14.0
@@ -1011,10 +1017,10 @@ importers:
packages/admin/admin-color-picker:
dependencies:
'@comet/admin':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin
'@comet/admin-icons':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-icons
react-colorful:
specifier: ^5.6.1
@@ -1033,10 +1039,10 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@mui/material':
specifier: ^6.4.5
@@ -1084,10 +1090,10 @@ importers:
packages/admin/admin-date-time:
dependencies:
'@comet/admin':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin
'@comet/admin-icons':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-icons
'@mui/utils':
specifier: ^6.4.3
@@ -1106,10 +1112,10 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@mui/material':
specifier: ^6.4.5
@@ -1261,10 +1267,10 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@mui/material':
specifier: ^6.4.5
@@ -1318,10 +1324,10 @@ importers:
packages/admin/admin-rte:
dependencies:
'@comet/admin':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin
'@comet/admin-icons':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-icons
detect-browser:
specifier: ^5.3.0
@@ -1346,10 +1352,10 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@mui/material':
specifier: ^6.4.5
@@ -1412,16 +1418,16 @@ importers:
packages/admin/cms-admin:
dependencies:
'@comet/admin':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin
'@comet/admin-date-time':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-date-time
'@comet/admin-icons':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-icons
'@comet/admin-rte':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-rte
axios:
specifier: ^0.29.0
@@ -1512,13 +1518,13 @@ importers:
specifier: ^7.26.9
version: 7.26.9
'@comet/admin-babel-preset':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../admin-babel-preset
'@comet/cli':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../cli
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@emotion/react':
specifier: ^11.14.0
@@ -1661,9 +1667,6 @@ importers:
ts-jest:
specifier: ^29.2.6
version: 29.2.6(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3)))(typescript@5.7.3)
- ts-node:
- specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3)
typescript:
specifier: ^5.7.3
version: 5.7.3
@@ -1856,7 +1859,7 @@ importers:
specifier: ^3.734.0
version: 3.734.0
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@golevelup/ts-jest':
specifier: ^0.6.2
@@ -1995,7 +1998,7 @@ importers:
version: 10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3)
devDependencies:
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../eslint-config
'@types/node':
specifier: ^22.13.5
@@ -2022,11 +2025,11 @@ importers:
specifier: ^9.21.0
version: 9.21.0
'@next/eslint-plugin-next':
- specifier: ^15.1.7
- version: 15.1.7
+ specifier: ^15.2.0
+ version: 15.2.0
eslint-config-next:
- specifier: ^15.1.7
- version: 15.1.7(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
+ specifier: ^15.2.0
+ version: 15.2.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
eslint-config-prettier:
specifier: ^10.0.1
version: 10.0.1(eslint@9.20.1(jiti@2.4.2))
@@ -2090,7 +2093,7 @@ importers:
version: 10.0.1(eslint@9.20.1(jiti@2.4.2))
eslint-plugin-import:
specifier: ^2.31.0
- version: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))
+ version: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
eslint-plugin-jsonc:
specifier: ^2.19.1
version: 2.19.1(eslint@9.20.1(jiti@2.4.2))
@@ -2150,10 +2153,10 @@ importers:
version: 3.1.1(react@18.3.1)
devDependencies:
'@comet/cli':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../cli
'@comet/eslint-config':
- specifier: workspace:^7.14.0
+ specifier: workspace:^7.15.0
version: link:../../eslint-config
'@types/draft-js':
specifier: ^0.11.18
@@ -5732,8 +5735,8 @@ packages:
'@next/env@14.2.24':
resolution: {integrity: sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==}
- '@next/eslint-plugin-next@15.1.7':
- resolution: {integrity: sha512-kRP7RjSxfTO13NE317ek3mSGzoZlI33nc/i5hs1KaWpK+egs85xg0DJ4p32QEiHnR0mVjuUfhRIun7awqfL7pQ==}
+ '@next/eslint-plugin-next@15.2.0':
+ resolution: {integrity: sha512-jHFUG2OwmAuOASqq253RAEG/5BYcPHn27p1NoWZDCf4OdvdK0yRYWX92YKkL+Mk2s+GyJrmd/GATlL5b2IySpw==}
'@next/swc-darwin-arm64@14.2.24':
resolution: {integrity: sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==}
@@ -7901,14 +7904,6 @@ packages:
'@types/yargs@17.0.33':
resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
- '@typescript-eslint/eslint-plugin@8.24.0':
- resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.8.0'
-
'@typescript-eslint/eslint-plugin@8.24.1':
resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7917,13 +7912,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
- '@typescript-eslint/parser@8.24.0':
- resolution: {integrity: sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.8.0'
-
'@typescript-eslint/parser@8.24.1':
resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7939,21 +7927,10 @@ packages:
resolution: {integrity: sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/scope-manager@8.24.0':
- resolution: {integrity: sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/scope-manager@8.24.1':
resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/type-utils@8.24.0':
- resolution: {integrity: sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.8.0'
-
'@typescript-eslint/type-utils@8.24.1':
resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7969,10 +7946,6 @@ packages:
resolution: {integrity: sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/types@8.24.0':
- resolution: {integrity: sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/types@8.24.1':
resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7989,12 +7962,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <5.8.0'
- '@typescript-eslint/typescript-estree@8.24.0':
- resolution: {integrity: sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <5.8.0'
-
'@typescript-eslint/typescript-estree@8.24.1':
resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -8015,13 +7982,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
- '@typescript-eslint/utils@8.24.0':
- resolution: {integrity: sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.8.0'
-
'@typescript-eslint/utils@8.24.1':
resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -8037,10 +7997,6 @@ packages:
resolution: {integrity: sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/visitor-keys@8.24.0':
- resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/visitor-keys@8.24.1':
resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -10234,8 +10190,8 @@ packages:
peerDependencies:
eslint: '>=6.0.0'
- eslint-config-next@15.1.7:
- resolution: {integrity: sha512-zXoMnYUIy3XHaAoOhrcYkT9UQWvXqWju2K7NNsmb5wd/7XESDwof61eUdW4QhERr3eJ9Ko/vnXqIrj8kk/drYw==}
+ eslint-config-next@15.2.0:
+ resolution: {integrity: sha512-LkG0KKpinAoNPk2HXSx0fImFb/hQ6RnhSxTkpJFTkQ0SmnzsbRsjjN95WC/mDY34nKOenpptYEVvfkCR/h+VjA==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
typescript: '>=3.3.1'
@@ -11044,11 +11000,6 @@ packages:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
- glob@11.0.0:
- resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
- engines: {node: 20 || >=22}
- hasBin: true
-
glob@11.0.1:
resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
engines: {node: 20 || >=22}
@@ -22075,7 +22026,7 @@ snapshots:
'@next/env@14.2.24': {}
- '@next/eslint-plugin-next@15.1.7':
+ '@next/eslint-plugin-next@15.2.0':
dependencies:
fast-glob: 3.3.1
@@ -24767,23 +24718,6 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
- '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
- dependencies:
- '@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- '@typescript-eslint/scope-manager': 8.24.0
- '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- '@typescript-eslint/visitor-keys': 8.24.0
- eslint: 9.20.1(jiti@2.4.2)
- graphemer: 1.4.0
- ignore: 5.3.2
- natural-compare: 1.4.0
- ts-api-utils: 2.0.1(typescript@5.7.3)
- typescript: 5.7.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -24801,18 +24735,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
- dependencies:
- '@typescript-eslint/scope-manager': 8.24.0
- '@typescript-eslint/types': 8.24.0
- '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3)
- '@typescript-eslint/visitor-keys': 8.24.0
- debug: 4.4.0
- eslint: 9.20.1(jiti@2.4.2)
- typescript: 5.7.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.24.1
@@ -24835,27 +24757,11 @@ snapshots:
'@typescript-eslint/types': 8.23.0
'@typescript-eslint/visitor-keys': 8.23.0
- '@typescript-eslint/scope-manager@8.24.0':
- dependencies:
- '@typescript-eslint/types': 8.24.0
- '@typescript-eslint/visitor-keys': 8.24.0
-
'@typescript-eslint/scope-manager@8.24.1':
dependencies:
'@typescript-eslint/types': 8.24.1
'@typescript-eslint/visitor-keys': 8.24.1
- '@typescript-eslint/type-utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
- dependencies:
- '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3)
- '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- debug: 4.4.0
- eslint: 9.20.1(jiti@2.4.2)
- ts-api-utils: 2.0.1(typescript@5.7.3)
- typescript: 5.7.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/type-utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3)
@@ -24871,8 +24777,6 @@ snapshots:
'@typescript-eslint/types@8.23.0': {}
- '@typescript-eslint/types@8.24.0': {}
-
'@typescript-eslint/types@8.24.1': {}
'@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)':
@@ -24903,20 +24807,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@8.24.0(typescript@5.7.3)':
- dependencies:
- '@typescript-eslint/types': 8.24.0
- '@typescript-eslint/visitor-keys': 8.24.0
- debug: 4.4.0
- fast-glob: 3.3.3
- is-glob: 4.0.3
- minimatch: 9.0.5
- semver: 7.7.1
- ts-api-utils: 2.0.1(typescript@5.7.3)
- typescript: 5.7.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3)':
dependencies:
'@typescript-eslint/types': 8.24.1
@@ -24953,17 +24843,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@9.20.1(jiti@2.4.2))
- '@typescript-eslint/scope-manager': 8.24.0
- '@typescript-eslint/types': 8.24.0
- '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3)
- eslint: 9.20.1(jiti@2.4.2)
- typescript: 5.7.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.20.1(jiti@2.4.2))
@@ -24985,11 +24864,6 @@ snapshots:
'@typescript-eslint/types': 8.23.0
eslint-visitor-keys: 4.2.0
- '@typescript-eslint/visitor-keys@8.24.0':
- dependencies:
- '@typescript-eslint/types': 8.24.0
- eslint-visitor-keys: 4.2.0
-
'@typescript-eslint/visitor-keys@8.24.1':
dependencies:
'@typescript-eslint/types': 8.24.1
@@ -27552,16 +27426,16 @@ snapshots:
eslint: 9.20.1(jiti@2.4.2)
semver: 7.7.1
- eslint-config-next@15.1.7(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3):
+ eslint-config-next@15.2.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3):
dependencies:
- '@next/eslint-plugin-next': 15.1.7
+ '@next/eslint-plugin-next': 15.2.0
'@rushstack/eslint-patch': 1.10.5
- '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
+ '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
+ '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
eslint: 9.20.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.20.1(jiti@2.4.2))
eslint-plugin-react: 7.37.4(eslint@9.20.1(jiti@2.4.2))
eslint-plugin-react-hooks: 5.1.0(eslint@9.20.1(jiti@2.4.2))
@@ -27602,7 +27476,7 @@ snapshots:
is-glob: 4.0.3
stable-hash: 0.0.4
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -27612,24 +27486,14 @@ snapshots:
esquery: 1.6.0
jsonc-eslint-parser: 2.4.0
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)):
- dependencies:
- debug: 3.2.7
- optionalDependencies:
- '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- eslint: 9.20.1(jiti@2.4.2)
- eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2))
- transitivePeerDependencies:
- - supports-color
-
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
eslint: 9.20.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -27650,7 +27514,7 @@ snapshots:
- ts-jest
- typescript
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -27661,36 +27525,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.20.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
- hasown: 2.0.2
- is-core-module: 2.16.1
- is-glob: 4.0.3
- minimatch: 3.1.2
- object.fromentries: 2.0.8
- object.groupby: 1.0.3
- object.values: 1.2.1
- semver: 6.3.1
- string.prototype.trimend: 1.0.9
- tsconfig-paths: 3.15.0
- optionalDependencies:
- '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
- transitivePeerDependencies:
- - eslint-import-resolver-typescript
- - eslint-import-resolver-webpack
- - supports-color
-
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)):
- dependencies:
- '@rtsao/scc': 1.1.0
- array-includes: 3.1.8
- array.prototype.findlastindex: 1.2.5
- array.prototype.flat: 1.3.3
- array.prototype.flatmap: 1.3.3
- debug: 3.2.7
- doctrine: 2.1.0
- eslint: 9.20.1(jiti@2.4.2)
- eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -28663,15 +28498,6 @@ snapshots:
package-json-from-dist: 1.0.0
path-scurry: 1.11.1
- glob@11.0.0:
- dependencies:
- foreground-child: 3.2.1
- jackspeak: 4.0.2
- minimatch: 10.0.1
- minipass: 7.1.2
- package-json-from-dist: 1.0.0
- path-scurry: 2.0.0
-
glob@11.0.1:
dependencies:
foreground-child: 3.2.1
@@ -33445,7 +33271,7 @@ snapshots:
rimraf@6.0.1:
dependencies:
- glob: 11.0.0
+ glob: 11.0.1
package-json-from-dist: 1.0.0
robust-predicates@3.0.2: {}
diff --git a/storybook/.storybook/preview.tsx b/storybook/.storybook/preview.tsx
index 80ebe22548..7e485c9a17 100644
--- a/storybook/.storybook/preview.tsx
+++ b/storybook/.storybook/preview.tsx
@@ -1,8 +1,9 @@
import "@fontsource-variable/roboto-flex/full.css";
-import { createCometTheme, MainContent, MuiThemeProvider } from "@comet/admin";
+import { createCometTheme, DataGridPanel, MainContent, MuiThemeProvider } from "@comet/admin";
import { DateFnsLocaleProvider } from "@comet/admin-date-time";
import { createTheme as createMuiTheme, GlobalStyles } from "@mui/material";
+import type {} from "@mui/x-data-grid/themeAugmentation";
import type { Preview } from "@storybook/react";
import { type Locale as DateFnsLocale } from "date-fns";
import { de as deLocale, enUS as enLocale } from "date-fns/locale";
@@ -72,7 +73,21 @@ const preview: Preview = {
decorators: [
(Story, context) => {
const { theme: selectedTheme, locale: selectedLocale } = context.args;
- const theme = selectedTheme === themeOptions.defaultMui ? createMuiTheme() : createCometTheme();
+ const theme =
+ selectedTheme === themeOptions.defaultMui
+ ? createMuiTheme()
+ : createCometTheme({
+ components: {
+ MuiDataGrid: {
+ defaultProps: {
+ slots: {
+ // @ts-expect-error @jamesricky fix this please
+ panel: DataGridPanel,
+ },
+ },
+ },
+ },
+ });
return (
diff --git a/storybook/src/admin/form/AllFieldComponents.stories.tsx b/storybook/src/admin/form/AllFieldComponents.stories.tsx
index d5203d5da2..27dd6a9551 100644
--- a/storybook/src/admin/form/AllFieldComponents.stories.tsx
+++ b/storybook/src/admin/form/AllFieldComponents.stories.tsx
@@ -104,7 +104,7 @@ export const AllFieldComponents = {
variant={fieldVariant}
fullWidth
/>
-
+ (checked ? "On" : "Off")} fieldLabel="Switch" variant={fieldVariant} />