diff --git a/packages/compass-components/src/components/empty-content.tsx b/packages/compass-components/src/components/empty-content.tsx index beef022bb10..dc0ecc81f7a 100644 --- a/packages/compass-components/src/components/empty-content.tsx +++ b/packages/compass-components/src/components/empty-content.tsx @@ -44,32 +44,45 @@ const callToActionLinkContainerStyles = css({ }); type EmptyContentProps = { - icon: React.FunctionComponent; + icon?: React.FunctionComponent; title: string; + titleClassName?: string; subTitle: React.ReactNode; + subTitleClassName?: string; callToAction?: React.ReactNode; callToActionLink?: React.ReactNode; }; const EmptyContent: React.FunctionComponent< EmptyContentProps & React.HTMLProps -> = ({ icon: Icon, title, subTitle, callToAction, callToActionLink }) => { +> = ({ + icon: Icon, + title, + subTitle, + callToAction, + callToActionLink, + titleClassName, + subTitleClassName, +}) => { const darkMode = useDarkMode(); return (
-
- -
+ {Icon && ( +
+ +
+ )} {title} - {subTitle} + {subTitle} {!!callToAction && (
{typeof callToAction === 'string' ? ( diff --git a/packages/compass-data-modeling/src/components/icons/collaborate.tsx b/packages/compass-data-modeling/src/components/icons/collaborate.tsx new file mode 100644 index 00000000000..3273d0402e3 --- /dev/null +++ b/packages/compass-data-modeling/src/components/icons/collaborate.tsx @@ -0,0 +1,49 @@ +import React, { useMemo } from 'react'; +import { palette, useDarkMode } from '@mongodb-js/compass-components'; + +const Collaborate: React.FunctionComponent = () => { + const darkMode = useDarkMode(); + const strokeColor = useMemo( + () => (darkMode ? palette.white : palette.black), + [darkMode] + ); + + return ( + + + + + + + + + + + ); +}; + +export default Collaborate; diff --git a/packages/compass-data-modeling/src/components/icons/flexibility.tsx b/packages/compass-data-modeling/src/components/icons/flexibility.tsx new file mode 100644 index 00000000000..2774c8249c4 --- /dev/null +++ b/packages/compass-data-modeling/src/components/icons/flexibility.tsx @@ -0,0 +1,35 @@ +import React, { useMemo } from 'react'; +import { palette, useDarkMode } from '@mongodb-js/compass-components'; + +const Flexibility: React.FunctionComponent = () => { + const darkMode = useDarkMode(); + const strokeColor = useMemo( + () => (darkMode ? palette.white : palette.black), + [darkMode] + ); + // Using green that doesn't change with dark mode + const fillColor = palette.green.base; + + return ( + + + + + ); +}; + +export default Flexibility; diff --git a/packages/compass-data-modeling/src/components/icons/insight.tsx b/packages/compass-data-modeling/src/components/icons/insight.tsx new file mode 100644 index 00000000000..6da69cbcf22 --- /dev/null +++ b/packages/compass-data-modeling/src/components/icons/insight.tsx @@ -0,0 +1,39 @@ +import React, { useMemo } from 'react'; +import { palette, useDarkMode } from '@mongodb-js/compass-components'; + +const Insight: React.FunctionComponent = () => { + const darkMode = useDarkMode(); + const strokeColor = useMemo( + () => (darkMode ? palette.white : palette.black), + [darkMode] + ); + // Green color that doesn't change with dark mode + const fillColor = palette.green.base; + + return ( + + + + + + ); +}; + +export default Insight; diff --git a/packages/compass-data-modeling/src/components/icons/schema-visualization.tsx b/packages/compass-data-modeling/src/components/icons/schema-visualization.tsx new file mode 100644 index 00000000000..58450582d25 --- /dev/null +++ b/packages/compass-data-modeling/src/components/icons/schema-visualization.tsx @@ -0,0 +1,88 @@ +import React, { useMemo } from 'react'; +import { palette, useDarkMode } from '@mongodb-js/compass-components'; + +const SchemaVisualization: React.FunctionComponent = () => { + const darkMode = useDarkMode(); + const strokeColor = useMemo( + () => (darkMode ? palette.white : palette.black), + [darkMode] + ); + // Using green that doesn't change with dark mode + const fillColor = palette.green.base; + + return ( + + + + + + + + + + + + + + ); +}; + +export default SchemaVisualization; diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx new file mode 100644 index 00000000000..85fbf76bee5 --- /dev/null +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { expect } from 'chai'; +import { + screen, + userEvent, + waitFor, +} from '@mongodb-js/testing-library-compass'; +import SavedDiagramsList from './saved-diagrams-list'; +import { renderWithStore } from '../../test/setup-store'; +import type { DataModelingStore } from '../../test/setup-store'; +import { DataModelStorageServiceProvider } from '../provider'; +import type { MongoDBDataModelDescription } from '../services/data-model-storage'; + +describe('SavedDiagramsList', function () { + const renderSavedDiagramsList = ({ + loadAll = () => Promise.resolve([]), + }: { + loadAll?: () => Promise; + } = {}) => { + const mockDataModelStorage = { + status: 'READY', + error: null, + items: [], + save: () => { + return Promise.resolve(false); + }, + delete: () => { + return Promise.resolve(false); + }, + loadAll, + load: () => { + return Promise.resolve(null); + }, + }; + return renderWithStore( + + + , + { + services: { + dataModelStorage: mockDataModelStorage, + }, + } + ); + }; + + context('when there are no saved diagrams', function () { + let store: DataModelingStore; + + beforeEach(async function () { + const result = renderSavedDiagramsList(); + store = result.store; + + // wait till the empty list is loaded + await waitFor(() => { + expect(screen.getByTestId('empty-content')).to.be.visible; + }); + }); + + it('shows the empty state', function () { + expect( + screen.getByText('Design, Visualize, and Evolve your Data Model') + ).to.be.visible; + }); + + it('allows to start adding diagrams', function () { + const createDiagramButton = screen.getByRole('button', { + name: 'Generate diagram', + }); + expect(store.getState().generateDiagramWizard.inProgress).to.be.false; + expect(createDiagramButton).to.be.visible; + userEvent.click(createDiagramButton); + expect(store.getState().generateDiagramWizard.inProgress).to.be.true; + }); + }); + + context('when there are diagrams', function () { + let store: DataModelingStore; + + beforeEach(async function () { + const result = renderSavedDiagramsList({ + loadAll: () => + Promise.resolve([ + { + id: 'diagram-1', + name: 'Diagram 1', + } as MongoDBDataModelDescription, + ]), + }); + store = result.store; + + // wait till the list is loaded + await waitFor(() => { + expect(screen.getByTestId('saved-diagram-list')).to.be.visible; + }); + }); + + it('shows the list of diagrams', function () { + expect(screen.getByText('Diagram 1')).to.exist; + }); + + it('allows to add another diagram', function () { + const createDiagramButton = screen.getByRole('button', { + name: 'Generate new diagram', + }); + expect(store.getState().generateDiagramWizard.inProgress).to.be.false; + expect(createDiagramButton).to.be.visible; + userEvent.click(createDiagramButton); + expect(store.getState().generateDiagramWizard.inProgress).to.be.true; + }); + }); +}); diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx index 45d471f1aac..0d4878a949a 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx @@ -4,16 +4,86 @@ import { createNewDiagram } from '../store/generate-diagram-wizard'; import { Button, Card, + css, EmptyContent, - Icon, ItemActionMenu, + Link, WorkspaceContainer, + spacing, } from '@mongodb-js/compass-components'; import { useDataModelSavedItems } from '../provider'; import { deleteDiagram, openDiagram, renameDiagram } from '../store/diagram'; import type { MongoDBDataModelDescription } from '../services/data-model-storage'; +import CollaborateIcon from './icons/collaborate'; +import SchemaVisualizationIcon from './icons/schema-visualization'; +import FlexibilityIcon from './icons/flexibility'; +import InsightIcon from './icons/insight'; -const SavedDiagramsList: React.FunctionComponent<{ +const subTitleStyles = css({ + maxWidth: '750px', +}); + +const featuresListStyles = css({ + display: 'flex', + justifyContent: 'center', + gap: spacing[600], + marginTop: spacing[400], +}); + +const featureItemStyles = css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: spacing[400], +}); + +type Feature = 'visualization' | 'collaboration' | 'interactive' | 'insights'; +const featureDescription: Record< + Feature, + { icon: React.FunctionComponent; title: string; subtitle: string } +> = { + visualization: { + icon: SchemaVisualizationIcon, + title: 'Quick Visualization & Refactoring', + subtitle: 'Instantly visualize and refactor data models', + }, + collaboration: { + icon: CollaborateIcon, + title: 'Collaboration & Sharing with your team', + subtitle: 'Collaborate and share schemas across teams', + }, + interactive: { + icon: FlexibilityIcon, + title: 'Interactive Diagram Analysis', + subtitle: 'Explore and annotate interactive diagrams', + }, + insights: { + icon: InsightIcon, + title: 'Performance Insights & Optimization', + subtitle: 'Uncover performance insights & best practices', + }, +}; + +const FeaturesList: React.FunctionComponent<{ features: Feature[] }> = ({ + features, +}) => { + return ( +
+ {features.map((feature, key) => { + const { icon: Icon, title, subtitle } = featureDescription[feature]; + return ( +
+ +

{title}

+

{subtitle}

+
+ ); + })} +
+ ); +}; + +export const SavedDiagramsList: React.FunctionComponent<{ onCreateDiagramClick: () => void; onOpenDiagramClick: (diagram: MongoDBDataModelDescription) => void; onDiagramDeleteClick: (id: string) => void; @@ -36,7 +106,7 @@ const SavedDiagramsList: React.FunctionComponent<{ if (showList) { content = ( -
+
{items.map((diagram) => { return ( } title="Design, Visualize, and Evolve your Data Model" subTitle={ <> @@ -80,15 +149,27 @@ const SavedDiagramsList: React.FunctionComponent<{ applications evolve, so must your schema—intelligently and strategically. Minimize complexity, prevent performance bottlenecks, and keep your development agile. + + + Data Modeling Documentation + } + subTitleClassName={subTitleStyles} callToAction={ } > @@ -106,7 +187,7 @@ const SavedDiagramsList: React.FunctionComponent<{ size="xsmall" data-testid="create-diagram-button" > - Create diagram + Generate new diagram ) : null; diff --git a/packages/compass-data-modeling/src/provider/index.tsx b/packages/compass-data-modeling/src/provider/index.tsx index 716756627a9..ec5b683ac66 100644 --- a/packages/compass-data-modeling/src/provider/index.tsx +++ b/packages/compass-data-modeling/src/provider/index.tsx @@ -5,7 +5,7 @@ import type { } from '../services/data-model-storage'; import { createServiceLocator } from 'hadron-app-registry'; -type DataModelStorageServiceState = { +export type DataModelStorageServiceState = { status: 'INITIAL' | 'LOADING' | 'REFRESHING' | 'READY' | 'ERROR'; error: Error | null; items: MongoDBDataModelDescription[];