diff --git a/packages/threat-composer-app/src/components/FullAppLayout/index.tsx b/packages/threat-composer-app/src/components/FullAppLayout/index.tsx index e1e6bd94..ced158b7 100644 --- a/packages/threat-composer-app/src/components/FullAppLayout/index.tsx +++ b/packages/threat-composer-app/src/components/FullAppLayout/index.tsx @@ -16,7 +16,6 @@ import AppLayoutComponent, { AppLayoutProps as AppLayoutComponentProps, } from '@cloudscape-design/components/app-layout'; -import Box from '@cloudscape-design/components/box'; import BreadcrumbGroup, { BreadcrumbGroupProps } from '@cloudscape-design/components/breadcrumb-group'; import { CancelableEventHandler } from '@cloudscape-design/components/internal/events'; import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; @@ -163,19 +162,15 @@ const AppLayout: FC> = ({ 'navigation' in props ? ( props.navigation ) : ( -
- -
+ ) } - content={ - !contentType || contentType === 'default' ? {children} : children - } + content={children} {...props} contentType={contentType} notifications={notifications} diff --git a/packages/threat-composer-app/src/index.tsx b/packages/threat-composer-app/src/index.tsx index ef498be9..0d557685 100644 --- a/packages/threat-composer-app/src/index.tsx +++ b/packages/threat-composer-app/src/index.tsx @@ -21,15 +21,16 @@ import reportWebVitals from './reportWebVitals'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import isMemoryRouterUsed from './utils/isMemoryRouterUsed'; +//For the ide-extension, the theme can be set via meta tag. const initialThemeString = (document.querySelector('meta[name="dark-mode"]') as HTMLMetaElement)?.content; -const initialTheme = initialThemeString ? +let initialTheme = initialThemeString ? (initialThemeString === 'true' ? Mode.Dark : Mode.Light) : undefined; ReactDOM.render( - + , diff --git a/packages/threat-composer/src/components/application/ApplicationInfo/index.tsx b/packages/threat-composer/src/components/application/ApplicationInfo/index.tsx index 9e56a5d6..0a9f1174 100644 --- a/packages/threat-composer/src/components/application/ApplicationInfo/index.tsx +++ b/packages/threat-composer/src/components/application/ApplicationInfo/index.tsx @@ -21,6 +21,7 @@ import SpaceBetween from '@cloudscape-design/components/space-between'; import { FC, useState, useCallback, useMemo, useEffect } from 'react'; import { useApplicationInfoContext } from '../../../contexts/ApplicationContext/context'; import { ApplicationInfoSchema, EditableComponentBaseProps } from '../../../customTypes'; +import ContentLayout from '../../generic/ContentLayout'; import Input from '../../generic/Input'; import MarkdownEditor from '../../generic/MarkdownEditor'; import MarkdownViewer from '../../generic/MarkdownViewer'; @@ -30,7 +31,7 @@ const ApplicationInfo: FC = ({ MarkdownEditorComponentType = MarkdownEditor, }) => { const { applicationInfo, setApplicationInfo } = useApplicationInfoContext(); - const [editMode, setEditMode] = useState(!applicationInfo.name && !applicationInfo.description ); + const [editMode, setEditMode] = useState(!applicationInfo.name && !applicationInfo.description); const [content, setContent] = useState(''); const [name, setName] = useState(''); @@ -60,33 +61,35 @@ const ApplicationInfo: FC = ({ ) : (); }, [editMode, handleSaveApplicationInfo, handleEdit, setEditMode]); - return ({applicationInfo.name || 'Application Introduction'}} - >{editMode ? ( - - - setName(event.detail.value) - } - validateData={ApplicationInfoSchema.shape.name.safeParse} - placeholder='Enter application name' + return ( + {applicationInfo.name || 'Application Introduction'}} + >{editMode ? ( + + + setName(event.detail.value) + } + validateData={ApplicationInfoSchema.shape.name.safeParse} + placeholder='Enter application name' + /> + + - - - ) : - ( - {applicationInfo.description || ''} - )} - ); + ) : + ( + {applicationInfo.description || ''} + )} + + ); }; export default ApplicationInfo; \ No newline at end of file diff --git a/packages/threat-composer/src/components/architecture/ArchitectureInfo/index.tsx b/packages/threat-composer/src/components/architecture/ArchitectureInfo/index.tsx index a1986ed9..d0deb89d 100644 --- a/packages/threat-composer/src/components/architecture/ArchitectureInfo/index.tsx +++ b/packages/threat-composer/src/components/architecture/ArchitectureInfo/index.tsx @@ -20,14 +20,15 @@ import BaseDiagramInfo from '../../generic/BaseDiagramInfo'; const ArchitectureInfo: FC = (props) => { const { architectureInfo, setArchitectureInfo } = useArchitectureInfoContext(); - return setArchitectureInfo(diagram)} - validateData={ArchitectureInfoSchema.shape.description.safeParse} - />; + return ( + setArchitectureInfo(diagram)} + validateData={ArchitectureInfoSchema.shape.description.safeParse} + />); }; export default ArchitectureInfo; \ No newline at end of file diff --git a/packages/threat-composer/src/components/assumptions/AssumptionList/index.tsx b/packages/threat-composer/src/components/assumptions/AssumptionList/index.tsx index 5aba3aa8..8ee12384 100644 --- a/packages/threat-composer/src/components/assumptions/AssumptionList/index.tsx +++ b/packages/threat-composer/src/components/assumptions/AssumptionList/index.tsx @@ -16,7 +16,6 @@ import Button from '@cloudscape-design/components/button'; import Container from '@cloudscape-design/components/container'; import Grid from '@cloudscape-design/components/grid'; -import Header from '@cloudscape-design/components/header'; import SpaceBetween from '@cloudscape-design/components/space-between'; import TextFilter from '@cloudscape-design/components/text-filter'; import { FC, useCallback, useMemo, useState } from 'react'; @@ -24,6 +23,7 @@ import { useAssumptionLinksContext } from '../../../contexts'; import { useAssumptionsContext } from '../../../contexts/AssumptionsContext/context'; import { Assumption, AssumptionLink } from '../../../customTypes'; import { addTagToEntity, removeTagFromEntity } from '../../../utils/entityTag'; +import ContentLayout from '../../generic/ContentLayout'; import LinkedEntityFilter, { ALL, WITHOUT_NO_LINKED_ENTITY, WITH_LINKED_ENTITY } from '../../generic/LinkedEntityFilter'; import TagSelector from '../../generic/TagSelector'; import AssumptionCard from '../AssumptionCard'; @@ -158,11 +158,9 @@ const AssumptionList: FC = () => { }, [saveAssumption, addAssumptionLinks]); - return (
+ return ( - Assumption List - }> + { onSave={handleSaveNew} /> -
); + ); }; export default AssumptionList; \ No newline at end of file diff --git a/packages/threat-composer/src/components/generic/BaseDiagramInfo/index.tsx b/packages/threat-composer/src/components/generic/BaseDiagramInfo/index.tsx index c0dd6634..cc346601 100644 --- a/packages/threat-composer/src/components/generic/BaseDiagramInfo/index.tsx +++ b/packages/threat-composer/src/components/generic/BaseDiagramInfo/index.tsx @@ -21,6 +21,7 @@ import SpaceBetween from '@cloudscape-design/components/space-between'; import { FC, useCallback, useState, useMemo, useEffect } from 'react'; import { BaseImageInfo, EditableComponentBaseProps } from '../../../customTypes'; import imageStyles from '../../../styles/image'; +import ContentLayout from '../../generic/ContentLayout'; import ImageEdit from '../ImageEdit'; import MarkdownEditor, { MarkdownEditorProps } from '../MarkdownEditor'; import MarkdownViewer from '../MarkdownViewer'; @@ -71,27 +72,32 @@ const BaseDiagramInfo: FC = ({ ) : (); }, [editMode, handleSaveDiagramInfo, handleEdit, setEditMode]); - return ({headerTitle}}> - {editMode ? ( - -
{headerTitle} Diagram
- -
) : - ( -
Introduction
- - {entity.description || ''} - -
{diagramTitle}
- {entity.image && {diagramTitle}} -
)} -
+ return ( + + {editMode ? ( + +
{headerTitle} Diagram
+ +
) : + ( +
Introduction
+ + {entity.description || ''} + +
{diagramTitle}
+ {entity.image && {diagramTitle}} +
)} +
+
); }; diff --git a/packages/threat-composer/src/components/generic/ContentLayout/index.tsx b/packages/threat-composer/src/components/generic/ContentLayout/index.tsx new file mode 100644 index 00000000..837b31db --- /dev/null +++ b/packages/threat-composer/src/components/generic/ContentLayout/index.tsx @@ -0,0 +1,49 @@ +/** ******************************************************************************************************************* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ******************************************************************************************************************** */ + +import ContentLayoutComponent from '@cloudscape-design/components/content-layout'; +import Header, { HeaderProps } from '@cloudscape-design/components/header'; +import { FC, PropsWithChildren } from 'react'; +import { useApplicationInfoContext } from '../../../contexts/ApplicationContext'; + +export interface ContentLayoutProps extends Omit { + /** + * The title of the header. + */ + title?: string; +} + +const ContentLayout: FC> = ({ + title, + children, + ...props +}) => { + const { applicationInfo } = useApplicationInfoContext(); + + return ( + {title} + } + > + {children} + ); +}; + +export default ContentLayout; \ No newline at end of file diff --git a/packages/threat-composer/src/components/generic/ThemeProvider/index.tsx b/packages/threat-composer/src/components/generic/ThemeProvider/index.tsx index 6673588c..2a913019 100644 --- a/packages/threat-composer/src/components/generic/ThemeProvider/index.tsx +++ b/packages/threat-composer/src/components/generic/ThemeProvider/index.tsx @@ -15,10 +15,13 @@ ******************************************************************************************************************** */ import { applyDensity, applyMode, Density, Mode } from '@cloudscape-design/global-styles'; import { FC, createContext, useState, useEffect, useContext, PropsWithChildren } from 'react'; +import useLocalStorageState from 'use-local-storage-state'; +import { LOCAL_STORAGE_KEY_THEME_MODE, LOCAL_STORAGE_KEY_THEME_DENSITY } from '../../../configs'; import '@cloudscape-design/global-styles/index.css'; export interface ThemeProviderProps { + appMode?: string; theme?: Mode; densitiy?: Density; } @@ -30,22 +33,29 @@ export interface ThemeContextApi { setDensity: React.Dispatch>; } -const initialState: ThemeContextApi = { - theme: Mode.Light, - density: Density.Comfortable, - setTheme: () => { }, - setDensity: () => { }, -}; +const useTheme = (props: ThemeProviderProps, { theme, setTheme, density, setDensity }: ThemeContextApi) => { + useEffect(() => { + typeof props.theme !== 'undefined' && setTheme(props.theme); + }, [props.theme]); -const ThemeContext = createContext(initialState); + useEffect(() => { + typeof props.densitiy !== 'undefined' && setDensity(props.densitiy); + }, [props.densitiy]); -const ThemeProvider: FC> = ({ + useEffect(() => { + applyMode(theme); + }, [theme]); + + useEffect(() => { + applyDensity(density); + }, [density]); +}; + +const ThemeLocalStateProvider: FC> = ({ children, ...props }) => { - const [theme, setTheme] = useState(() => { - return props.theme || Mode.Light; - }); + const [theme, setTheme] = useState(props.theme || Mode.Light); const [density, setDensity] = useState(() => { if (props.densitiy === Density.Compact) { @@ -55,21 +65,45 @@ const ThemeProvider: FC> = ({ return Density.Comfortable; }); - useEffect(() => { - typeof props.theme !== 'undefined' && setTheme(props.theme); - }, [props.theme]); + useTheme(props, { + theme, + setTheme, + density, + setDensity, + }); - useEffect(() => { - typeof props.densitiy !== 'undefined' && setDensity(props.densitiy); - }, [props.densitiy]); + return ( + + {children} + + ); +}; - useEffect(() => { - applyMode(theme); - }, [theme]); +const ThemeLocalStorageProvider: FC> = ({ + children, + ...props +}) => { + const [theme, setTheme] = useLocalStorageState(LOCAL_STORAGE_KEY_THEME_MODE, { + defaultValue: props.theme || Mode.Light, + }); - useEffect(() => { - applyDensity(density); - }, [density]); + const [density, setDensity] = useLocalStorageState(LOCAL_STORAGE_KEY_THEME_DENSITY, { + defaultValue: props.densitiy === Density.Compact ? Density.Compact : Density.Comfortable, + }); + + useTheme(props, { + theme, + setTheme, + density, + setDensity, + }); return ( > = ({ ); }; +const initialState: ThemeContextApi = { + theme: Mode.Light, + density: Density.Comfortable, + setTheme: () => { }, + setDensity: () => { }, +}; + +const ThemeContext = createContext(initialState); + + +const ThemeProvider: FC> = ({ + appMode, + ...props +}) => { + return appMode === 'ide-extension' ? : ; +}; + export { Mode, Density, diff --git a/packages/threat-composer/src/components/generic/ThemeToggle/index.tsx b/packages/threat-composer/src/components/generic/ThemeToggle/index.tsx new file mode 100644 index 00000000..b97455e5 --- /dev/null +++ b/packages/threat-composer/src/components/generic/ThemeToggle/index.tsx @@ -0,0 +1,53 @@ +/** ******************************************************************************************************************* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ******************************************************************************************************************** */ +/** @jsxImportSource @emotion/react */ +import Icon from '@cloudscape-design/components/icon'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import Toggle from '@cloudscape-design/components/toggle'; +import { Mode } from '@cloudscape-design/global-styles'; +import { css } from '@emotion/react'; +import { useThemeContext } from '../ThemeProvider'; + +const styles = { + container: css({ + height: '100%', + display: 'flex', + alignItems: 'center', + }), + svg: css({ + color: 'grey !important', + fill: 'grey !important', + }), +}; + +const ThemeToggle = () => { + const { theme, setTheme } = useThemeContext(); + return (
+ + + setTheme(detail.checked ? Mode.Dark : Mode.Light) + } + checked={theme === Mode.Dark} + /> + +
); +}; + +export default ThemeToggle; \ No newline at end of file diff --git a/packages/threat-composer/src/components/generic/WindowExporter/index.tsx b/packages/threat-composer/src/components/generic/WindowExporter/index.tsx index 4677ea09..5a6a8c0c 100644 --- a/packages/threat-composer/src/components/generic/WindowExporter/index.tsx +++ b/packages/threat-composer/src/components/generic/WindowExporter/index.tsx @@ -17,6 +17,7 @@ import { useCallback, FC, PropsWithChildren, useEffect } from 'react'; import { useWorkspacesContext } from '../../../contexts'; import useExportImport, { PLACEHOLDER_EXCHANGE_DATA } from '../../../hooks/useExportImport'; import useRemoveData from '../../../hooks/useRemoveData'; +import convertToMarkdown from '../../../utils/convertToMarkdown'; /** * Export threat-composer functionalities via window object. @@ -40,6 +41,11 @@ const WindowExporter: FC> = ({ children }) => { [importData], ); + const getCurrentWorkspaceDataMarkdown = useCallback(async () => { + return convertToMarkdown(getWorkspaceData()); + }, [getWorkspaceData]); + + useEffect(() => { window.threatcomposer.getWorkspaceList = () => workspaceList; }, [workspaceList]); @@ -52,6 +58,10 @@ const WindowExporter: FC> = ({ children }) => { window.threatcomposer.getCurrentWorkspaceData = getWorkspaceData; }, [getWorkspaceData]); + useEffect(() => { + window.threatcomposer.getCurrentWorkspaceDataMarkdown = getCurrentWorkspaceDataMarkdown; + }, [getCurrentWorkspaceDataMarkdown]); + useEffect(() => { window.threatcomposer.setCurrentWorkspaceData = setWorkspaceData; }, [setWorkspaceData]); diff --git a/packages/threat-composer/src/components/mitigations/MitigationList/index.tsx b/packages/threat-composer/src/components/mitigations/MitigationList/index.tsx index 37b6b7e1..0fc3b374 100644 --- a/packages/threat-composer/src/components/mitigations/MitigationList/index.tsx +++ b/packages/threat-composer/src/components/mitigations/MitigationList/index.tsx @@ -16,13 +16,13 @@ import Button from '@cloudscape-design/components/button'; import Container from '@cloudscape-design/components/container'; import Grid from '@cloudscape-design/components/grid'; -import Header from '@cloudscape-design/components/header'; import SpaceBetween from '@cloudscape-design/components/space-between'; import TextFilter from '@cloudscape-design/components/text-filter'; import { FC, useCallback, useMemo, useState } from 'react'; import { useAssumptionLinksContext, useMitigationLinksContext } from '../../../contexts'; import { useMitigationsContext } from '../../../contexts/MitigationsContext/context'; import { AssumptionLink, Mitigation, MitigationLink } from '../../../customTypes'; +import ContentLayout from '../../generic/ContentLayout'; import LinkedEntityFilter, { ALL, WITHOUT_NO_LINKED_ENTITY, WITH_LINKED_ENTITY } from '../../generic/LinkedEntityFilter'; import TagSelector from '../../generic/TagSelector'; import MitigationCard from '../MitigationCard'; @@ -175,13 +175,9 @@ const MitigationList: FC = () => { }, [saveMitigation, addMitigationLinks, addAssumptionLinks]); - return (
+ return ( - Mitigation List - }> + { onSave={handleSaveNew} /> -
); + ); }; export default MitigationList; \ No newline at end of file diff --git a/packages/threat-composer/src/components/report/ThreatModel/components/ThreatModelView/index.tsx b/packages/threat-composer/src/components/report/ThreatModel/components/ThreatModelView/index.tsx index 2a6a4609..54206f11 100644 --- a/packages/threat-composer/src/components/report/ThreatModel/components/ThreatModelView/index.tsx +++ b/packages/threat-composer/src/components/report/ThreatModel/components/ThreatModelView/index.tsx @@ -17,7 +17,8 @@ import Box from '@cloudscape-design/components/box'; import Button from '@cloudscape-design/components/button'; import ButtonDropdown, { ButtonDropdownProps } from '@cloudscape-design/components/button-dropdown'; -import Header from '@cloudscape-design/components/header'; +import ContentLayoutComponent from '@cloudscape-design/components/content-layout'; +import Header, { HeaderProps } from '@cloudscape-design/components/header'; import { CancelableEventHandler } from '@cloudscape-design/components/internal/events'; import Popover from '@cloudscape-design/components/popover'; import SpaceBetween from '@cloudscape-design/components/space-between'; @@ -25,9 +26,10 @@ import Spinner from '@cloudscape-design/components/spinner'; import StatusIndicator from '@cloudscape-design/components/status-indicator'; import * as awsui from '@cloudscape-design/design-tokens'; import { css } from '@emotion/react'; -import { FC, useEffect, useCallback, useState, ReactNode } from 'react'; +import { FC, useEffect, useCallback, useState, ReactNode, PropsWithChildren, useMemo } from 'react'; import { DataExchangeFormat, HasContentDetails, ViewNavigationEvent } from '../../../../../customTypes'; import printStyles from '../../../../../styles/print'; +import convertToMarkdown from '../../../../../utils/convertToMarkdown'; import convertToYaml from '../../../../../utils/convertToYaml'; import { downloadContentAsMarkdown, @@ -35,16 +37,8 @@ import { downloadContentAsYaml, downloadObjectAsJson, } from '../../../../../utils/downloadContent'; -import sanitizeHtml from '../../../../../utils/sanitizeHtml'; import MarkdownViewer from '../../../../generic/MarkdownViewer'; -import { getApplicationInfoContent } from '../../utils/getApplicationInfo'; -import { getApplicationName } from '../../utils/getApplicationName'; -import { getArchitectureContent } from '../../utils/getArchitecture'; -import { getAssetsContent } from '../../utils/getAssets'; -import { getAssumptionsContent } from '../../utils/getAssumptions'; -import { getDataflowContent } from '../../utils/getDataFlow'; -import { getMitigationsContent } from '../../utils/getMitigations'; -import { getThreatsContent } from '../../utils/getThreats'; + const styles = { text: css({ @@ -62,6 +56,27 @@ const styles = { }), }; +const ContentLayout: FC> = ({ + isPreview, + children, + ...props +}) => { + if (isPreview) { + return <>{children}; + } + + return ( + } + > + {children} + ); +}; + export interface ThreatModelViewProps extends ViewNavigationEvent { isPreview?: boolean; showPrintDownloadButtons?: boolean; @@ -90,18 +105,7 @@ const ThreatModelView: FC = ({ useEffect(() => { const updateContent = async () => { setLoading(true); - const sanitizedData = sanitizeHtml(data); - const processedContent = (composerMode === 'Full' ? [ - (!hasContentDetails || hasContentDetails.applicationName) && await getApplicationName(sanitizedData), - (!hasContentDetails || hasContentDetails.applicationInfo) && await getApplicationInfoContent(sanitizedData), - (!hasContentDetails || hasContentDetails.architecture) && await getArchitectureContent(sanitizedData), - (!hasContentDetails || hasContentDetails.dataflow) && await getDataflowContent(sanitizedData), - (!hasContentDetails || hasContentDetails.assumptions) && await getAssumptionsContent(sanitizedData), - (!hasContentDetails || hasContentDetails.threats) && await getThreatsContent(sanitizedData), - (!hasContentDetails || hasContentDetails.mitigations) && await getMitigationsContent(sanitizedData), - (!hasContentDetails || hasContentDetails.threats) && await getAssetsContent(sanitizedData), - ] : [await getThreatsContent(sanitizedData, true)]).filter(x => !!x).join('\n'); - + const processedContent = await convertToMarkdown(data, composerMode); setContent(processedContent); setLoading(false); }; @@ -133,7 +137,6 @@ const ThreatModelView: FC = ({ downloadFileName && downloadContentAsYaml(yamlContent, downloadFileName); }, [data, downloadFileName]); - const handleDownloadClick: CancelableEventHandler = useCallback(async ({ detail }) => { switch (detail.id) { case 'docx': @@ -177,42 +180,46 @@ const ThreatModelView: FC = ({ return buttons.flatMap((b, index) => index === len - 1 ? {b} : [b, or]); }, [hasContentDetails, props]); - return (
+ const actions = useMemo(() => + + Content copied + + } + > + + + {downloadFileName && showPrintDownloadButtons && + Download + } + {showPrintDownloadButtons && } + , [handleCopyMarkdown, downloadFileName, showPrintDownloadButtons, convertToDocx, handleDownloadClick, onPrintButtonClick]); + + return ( -
- - Content copied - - } - > - - - {downloadFileName && showPrintDownloadButtons && - Download - } - {showPrintDownloadButtons && } - - } - > -
+ {isPreview &&
+
+
+
} {content ? ({content}) : ({loading ? : 'No data available'}) @@ -226,7 +233,7 @@ const ThreatModelView: FC = ({
} - ); + ); }; export default ThreatModelView; \ No newline at end of file diff --git a/packages/threat-composer/src/components/report/ThreatModel/index.tsx b/packages/threat-composer/src/components/report/ThreatModel/index.tsx index c33de76b..c1c09a55 100644 --- a/packages/threat-composer/src/components/report/ThreatModel/index.tsx +++ b/packages/threat-composer/src/components/report/ThreatModel/index.tsx @@ -19,8 +19,8 @@ import { APP_MODE_IDE_EXTENSION } from '../../../configs/appMode'; import { useGlobalSetupContext, useWorkspacesContext } from '../../../contexts'; import { DataExchangeFormat, ViewNavigationEvent } from '../../../customTypes'; import useImportExport from '../../../hooks/useExportImport'; -import useHasContent from '../../../hooks/useHasContent'; import getExportFileName from '../../../utils/getExportFileName'; +import hasContent from '../../../utils/hasContent'; export interface ThreatModelProps extends ViewNavigationEvent { onPrintButtonClick?: (data: DataExchangeFormat) => void; @@ -34,28 +34,33 @@ const ThreatModel: FC = ({ }) => { const { getWorkspaceData } = useImportExport(); const { composerMode, appMode } = useGlobalSetupContext(); - const [_, hasContentDetails] = useHasContent(); const { currentWorkspace } = useWorkspacesContext(); const downloadFileName = useMemo(() => { return getExportFileName(composerMode, false, currentWorkspace); }, [composerMode, currentWorkspace]); - return onPrintButtonClick?.(getWorkspaceData())} - showPrintDownloadButtons={appMode !== APP_MODE_IDE_EXTENSION} - composerMode={composerMode} - data={getWorkspaceData()} - downloadFileName={downloadFileName} - hasContentDetails={hasContentDetails} - onApplicationInfoView={props.onApplicationInfoView} - onArchitectureView={props.onArchitectureView} - onDataflowView={props.onDataflowView} - onAssumptionListView={props.onAssumptionListView} - onThreatListView={props.onThreatListView} - onMitigationListView={props.onMitigationListView} - />; + const hasContentDetails = useMemo(() => { + const [, details] = hasContent(getWorkspaceData()); + return details; + }, [getWorkspaceData]); + + return ( + onPrintButtonClick?.(getWorkspaceData())} + showPrintDownloadButtons={appMode !== APP_MODE_IDE_EXTENSION} + composerMode={composerMode} + data={getWorkspaceData()} + downloadFileName={downloadFileName} + hasContentDetails={hasContentDetails} + onApplicationInfoView={props.onApplicationInfoView} + onArchitectureView={props.onArchitectureView} + onDataflowView={props.onDataflowView} + onAssumptionListView={props.onAssumptionListView} + onThreatListView={props.onThreatListView} + onMitigationListView={props.onMitigationListView} + />); }; export default ThreatModel; \ No newline at end of file diff --git a/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx b/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx index f6b26df6..53bd8377 100644 --- a/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx +++ b/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx @@ -14,18 +14,19 @@ limitations under the License. ******************************************************************************************************************** */ /** @jsxImportSource @emotion/react */ +import ContentLayoutComponent from '@cloudscape-design/components/content-layout'; import Grid from '@cloudscape-design/components/grid'; import SpaceBetween from '@cloudscape-design/components/space-between'; import TextContent from '@cloudscape-design/components/text-content'; import * as awsui from '@cloudscape-design/design-tokens'; import { css } from '@emotion/react'; -import React, { FC, useCallback, useMemo, useState, useRef, useEffect, ReactNode } from 'react'; +import React, { FC, useCallback, useMemo, useState, useRef, useEffect, ReactNode, PropsWithChildren } from 'react'; import { v4 as uuidV4 } from 'uuid'; import { EditorProps } from './types'; import { DEFAULT_NEW_ENTITY_ID, DEFAULT_WORKSPACE_LABEL } from '../../../configs/constants'; import { useAssumptionLinksContext } from '../../../contexts/AssumptionLinksContext/context'; import { useAssumptionsContext } from '../../../contexts/AssumptionsContext/context'; -import { useGlobalSetupContext } from '../../../contexts/GlobalSetupContext/context'; +import { GlobalSetupContextApi, useGlobalSetupContext } from '../../../contexts/GlobalSetupContext/context'; import { useMitigationLinksContext } from '../../../contexts/MitigationLinksContext/context'; import { useMitigationsContext } from '../../../contexts/MitigationsContext/context'; import { useThreatsContext } from '../../../contexts/ThreatsContext/context'; @@ -85,7 +86,41 @@ const editorMapping: { [key in ThreatFieldTypes]: React.ComponentType = ({ +const ContentLayout: FC void; + onStartOver: () => void; + onComplete: () => void; +}>> = ({ + children, + editingStatement, + composerMode, + onCancel, + onStartOver, + onComplete, + saveButtonText, +}) => { + if (composerMode !== 'Full') { + return (<>{children}); + } + + return ( + }> + {children} + ); +}; + +export const ThreatStatementEditorInner: FC = ({ editingStatement, onThreatListView, }) => { @@ -311,9 +346,16 @@ const ThreatStatementEditorInner: FC + - {composerMode !== 'EditorOnly' &&
} - ); + ); }; const ThreatStatementEditor: FC = (props) => { const { editingStatement } = useThreatsContext(); - return editingStatement ? : null; + return editingStatement ? : null; }; export default ThreatStatementEditor; \ No newline at end of file diff --git a/packages/threat-composer/src/components/threats/ThreatStatementList/index.tsx b/packages/threat-composer/src/components/threats/ThreatStatementList/index.tsx index 58ae2e5e..534f8a5c 100644 --- a/packages/threat-composer/src/components/threats/ThreatStatementList/index.tsx +++ b/packages/threat-composer/src/components/threats/ThreatStatementList/index.tsx @@ -22,15 +22,16 @@ import Multiselect from '@cloudscape-design/components/multiselect'; import SpaceBetween from '@cloudscape-design/components/space-between'; import TextFilter from '@cloudscape-design/components/text-filter'; import { css } from '@emotion/react'; -import { FC, useCallback, useMemo, useState } from 'react'; +import { FC, PropsWithChildren, useCallback, useMemo, useState } from 'react'; import { LEVEL_SELECTOR_OPTIONS, DEFAULT_NEW_ENTITY_ID, LEVEL_NOT_SET } from '../../../configs'; import { useAssumptionLinksContext, useMitigationLinksContext } from '../../../contexts'; -import { useGlobalSetupContext } from '../../../contexts/GlobalSetupContext/context'; +import { GlobalSetupContextApi, useGlobalSetupContext } from '../../../contexts/GlobalSetupContext/context'; import { useThreatsContext } from '../../../contexts/ThreatsContext/context'; import { TemplateThreatStatement, ThreatStatementListFilter, ViewNavigationEvent } from '../../../customTypes'; import useEditMetadata from '../../../hooks/useEditMetadata'; import { addTagToEntity, removeTagFromEntity } from '../../../utils/entityTag'; import AssetSelector from '../../generic/AssetSelector'; +import ContentLayoutComponent, { ContentLayoutProps } from '../../generic/ContentLayout'; import LinkedEntityFilter, { ALL, WITHOUT_NO_LINKED_ENTITY, WITH_LINKED_ENTITY } from '../../generic/LinkedEntityFilter'; import { OPTIONS as STRIDEOptions } from '../../generic/STRIDESelector'; import TagSelector from '../../generic/TagSelector'; @@ -62,6 +63,24 @@ const styles = { }), }; +const ContentLayout: FC> = ({ + composerMode, + children, + ...props +}) => { + if (composerMode !== 'Full') { + return <>{children}; + } + + return ( + {children} + ); +}; + export interface ThreatStatementListProps { initialFilter?: ThreatStatementListFilter; onThreatEditorView?: ViewNavigationEvent['onThreatEditorView']; @@ -348,15 +367,20 @@ const ThreatStatementList: FC = ({ { colspan: { default: 1 } }]; }, [composerMode]); - return (
+ return ( } - >Threat Statement List
- }> + info={