From eed26a9ad09b274da30284c3f3b38b0d84f31570 Mon Sep 17 00:00:00 2001 From: Zvonimir Fras Date: Wed, 24 Jan 2024 11:47:54 -0500 Subject: [PATCH] Improve custom components modal - add support for multiple collections - add support for featured collections (from GitHub) - add support for loading collections from a custom URL Signed-off-by: Zvonimir Fras --- .../components/custom-components-modal.tsx | 22 ++- app/src/components/fragment.tsx | 19 ++- app/src/context/github-context.tsx | 45 +++++- app/src/routes/edit/elements-pane.tsx | 45 ++++-- .../share-options/exports/export-modal.tsx | 20 ++- app/src/utils/misc-tools.ts | 29 ++++ .../custom-components-collection-editor.tsx | 146 ++++++++++++++++-- 7 files changed, 279 insertions(+), 47 deletions(-) create mode 100644 app/src/utils/misc-tools.ts diff --git a/app/src/components/custom-components-modal.tsx b/app/src/components/custom-components-modal.tsx index 2687b7285..314c5f8c4 100644 --- a/app/src/components/custom-components-modal.tsx +++ b/app/src/components/custom-components-modal.tsx @@ -8,14 +8,27 @@ import { } from '@carbon/react'; import { TrashCan, DataEnrichmentAdd } from '@carbon/icons-react'; import { CustomComponentsCollectionEditor, getNewCustomComponentsCollection } from '@carbon-builder/sdk-react'; -import { GlobalStateContext, ModalContext } from '../context'; +import { GithubContext, GlobalStateContext, ModalContext } from '../context'; import { css } from 'emotion'; +import { capitalize } from 'lodash'; export const CustomComponentsModal = () => { const { customComponentsModal, hideCustomComponentsModal } = useContext(ModalContext); const { customComponentsCollections, setCustomComponentsCollections } = useContext(GlobalStateContext); + const { getFeaturedCustomComponentsCollections } = useContext(GithubContext); const [isDeleteOpen, setIsDeleteOpen] = useState({} as any); + const [featuredCollectionsItems, setFeaturedCollectionsItems] = useState([] as any[]); + + useEffect(() => { + getFeaturedCustomComponentsCollections().then((value: any) => { + setFeaturedCollectionsItems(value.map((v: any) => ({ + id: v.name, + text: capitalize(v.name) + }))); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { if (!customComponentsCollections || !Array.isArray(customComponentsCollections) || customComponentsCollections.length === 0) { @@ -37,7 +50,7 @@ export const CustomComponentsModal = () => { { customComponentsCollections.map((collection: any, index: number) => - + { diff --git a/app/src/routes/edit/share-options/exports/export-modal.tsx b/app/src/routes/edit/share-options/exports/export-modal.tsx index 4eb72ef73..67ece0ade 100644 --- a/app/src/routes/edit/share-options/exports/export-modal.tsx +++ b/app/src/routes/edit/share-options/exports/export-modal.tsx @@ -25,6 +25,7 @@ import { createAngularApp } from './frameworks/angular/latest/fragment'; import { ModalContext } from '../../../../context/modal-context'; import { saveBlob } from '../../../../utils/file-tools'; +import { useRemoteCustomComponentsCollections } from '../../../../utils/misc-tools'; import { GlobalStateContext } from '../../../../context'; import { ExportImageComponent } from './export-image-component'; import { filenameToLanguage, getFragmentJsonExportString } from '@carbon-builder/sdk-react'; @@ -188,6 +189,8 @@ const findTreeItemByPath = (node: any, path: string, currentPath = ''): any => { export const ExportModal = () => { const { fragments, settings, setSettings, styleClasses, customComponentsCollections } = useContext(GlobalStateContext); const { fragmentExportModal, hideFragmentExportModal } = useContext(ModalContext); + const [remoteCustomComponentsCollections] = useRemoteCustomComponentsCollections(); + const [allCustomComponentsCollections, setAllCustomComponentsCollections] = useState([] as any[]); const [selectedAngularFileItem, setSelectedAngularFileItem] = useState({ id: 'src/app/app.component.ts' } as any); @@ -202,6 +205,13 @@ export const ExportModal = () => { const monaco = useMonaco(); + useEffect(() => { + setAllCustomComponentsCollections([ + ...(remoteCustomComponentsCollections as any[] || []).flat(), + ...customComponentsCollections + ]); + }, [remoteCustomComponentsCollections, customComponentsCollections]); + const carbonVersions = [ { id: 'v10' }, { id: 'v11' } @@ -229,12 +239,12 @@ export const ExportModal = () => { if (fragmentExportModal?.fragment) { switch (version) { case 'v10': - setReactCode(createReactAppV10(fragmentExportModal.fragment, fragments, styleClasses, customComponentsCollections)); - setAngularCode(createAngularAppV10(fragmentExportModal.fragment, fragments, styleClasses, customComponentsCollections)); + setReactCode(createReactAppV10(fragmentExportModal.fragment, fragments, styleClasses, allCustomComponentsCollections)); + setAngularCode(createAngularAppV10(fragmentExportModal.fragment, fragments, styleClasses, allCustomComponentsCollections)); break; default: - setReactCode(createReactApp(fragmentExportModal.fragment, fragments, styleClasses, customComponentsCollections)); - setAngularCode(createAngularApp(fragmentExportModal.fragment, fragments, styleClasses, customComponentsCollections)); + setReactCode(createReactApp(fragmentExportModal.fragment, fragments, styleClasses, allCustomComponentsCollections)); + setAngularCode(createAngularApp(fragmentExportModal.fragment, fragments, styleClasses, allCustomComponentsCollections)); break; } return; @@ -242,7 +252,7 @@ export const ExportModal = () => { setReactCode([]); setAngularCode([]); - }, [customComponentsCollections, fragmentExportModal.fragment, fragmentExportModal.isVisible, fragments, styleClasses, version]); + }, [allCustomComponentsCollections, fragmentExportModal.fragment, fragmentExportModal.isVisible, fragments, styleClasses, version]); useEffect(() => { monaco?.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ diff --git a/app/src/utils/misc-tools.ts b/app/src/utils/misc-tools.ts new file mode 100644 index 000000000..fe4789f71 --- /dev/null +++ b/app/src/utils/misc-tools.ts @@ -0,0 +1,29 @@ +import { useContext, useEffect, useState } from 'react'; +import { GithubContext, GlobalStateContext } from '../context'; +import axios from 'axios'; + +export const useRemoteCustomComponentsCollections = () => { + const { customComponentsCollections } = useContext(GlobalStateContext); + const { getFeaturedCustomComponentsCollection } = useContext(GithubContext); + + const [collections, setCollections] = useState([] as any[]); + const promises: any[] = []; + customComponentsCollections.forEach((collection: any) => { + if (collection.type === 'featured') { + promises.push(getFeaturedCustomComponentsCollection(collection.collection)); + } + + if (collection.type === 'url') { + promises.push(axios.get(collection.url, { responseType: 'text' }).then((value: any) => value.data)); + } + }); + + useEffect(() => { + Promise.all(promises).then((data: any[]) => { + setCollections(data); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return [collections, setCollections]; +}; diff --git a/sdk/react/src/lib/components/custom-components-collection-editor.tsx b/sdk/react/src/lib/components/custom-components-collection-editor.tsx index f2f5969ff..2e3c2bdcc 100644 --- a/sdk/react/src/lib/components/custom-components-collection-editor.tsx +++ b/sdk/react/src/lib/components/custom-components-collection-editor.tsx @@ -1,10 +1,39 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { + Button, + ContentSwitcher, + Dropdown, + Switch, + TextInput +} from '@carbon/react'; +import { Checkmark } from '@carbon/icons-react'; import Handlebars from 'handlebars'; import Editor from '@monaco-editor/react'; +import { css } from 'emotion'; -export const CustomComponentsCollectionEditor = ({ collection, setCollection }: any) => { +export const CustomComponentsCollectionEditor = ({ collection, setCollection, featuredCollectionsItems }: any) => { const [jsonParseError, setJsonParseError] = useState(''); const [model, _setModel] = useState(JSON.stringify(collection, null, '\t')); + const [collectionTypeIndex, setCollectionTypeIndex] = useState(collection.type === 'featured' ? 0 : collection.type === 'url' ? 1 : 2); + + const [selectedFeaturedCollection, setSelectedFeaturedCollection] = useState(null as { id: string; text: string } | null); + const [collectionName, setCollectionName] = useState(collection.name); + const [collectionUrl, setCollectionUrl] = useState(collection.url || ''); + + featuredCollectionsItems = featuredCollectionsItems || []; + + useEffect(() => { + if (collection.type !== 'featured' || !featuredCollectionsItems) { + return; + } + + const featuredCollectionItem = featuredCollectionsItems.find((item: any) => item.id === collection.collection); + + if (featuredCollectionItem) { + setSelectedFeaturedCollection(featuredCollectionItem); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [featuredCollectionsItems]); const setModel = (modelString: string) => { _setModel(modelString); @@ -33,20 +62,111 @@ export const CustomComponentsCollectionEditor = ({ collection, setCollection }: }; return <> + setCollectionTypeIndex(index)}> + + + + + { + collectionTypeIndex === 0 + && <> + setCollectionName(event.currentTarget.value)} /> + item ? item.text : ''} + onChange={({ selectedItem }: any) => setSelectedFeaturedCollection(selectedItem)} /> + + + } + { + collectionTypeIndex === 1 + && <> + setCollectionName(event.currentTarget.value)} /> + setCollectionUrl(event.currentTarget.value)} /> + + + } { - jsonParseError + collectionTypeIndex === 2 && <> - Not saved until the error is corrected: - -
{jsonParseError}
-
+

Valid JSON is automatically saved on edit.

+ { + jsonParseError + && <> + Not saved until the error is corrected: + +
{jsonParseError}
+
+ + } + } - ; };