This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
The following commit(s) were added to refs/heads/main by this push: new 5e889e62 Fix #1180 5e889e62 is described below commit 5e889e6290662aa1a8acd92dcb7e288b093689b0 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Fri Mar 15 18:14:44 2024 -0400 Fix #1180 --- .../main/webui/src/designer/KaravanDesigner.tsx | 62 +++++----- .../main/webui/src/designer/selector/DslCard.tsx | 71 +++++++++++ .../webui/src/designer/selector/DslFastCard.tsx | 58 +++++++++ .../webui/src/designer/selector/DslPreferences.tsx | 13 +++ .../webui/src/designer/selector/DslSelector.css | 40 ++++++- .../webui/src/designer/selector/DslSelector.tsx | 130 ++++++++------------- karavan-designer/public/example/demo.camel.yaml | 2 - karavan-designer/src/designer/selector/DslCard.tsx | 71 +++++++++++ .../src/designer/selector/DslFastCard.tsx | 58 +++++++++ .../src/designer/selector/DslPreferences.tsx | 13 +++ .../src/designer/selector/DslSelector.css | 40 ++++++- .../src/designer/selector/DslSelector.tsx | 130 ++++++++------------- karavan-space/src/designer/selector/DslCard.tsx | 71 +++++++++++ .../src/designer/selector/DslFastCard.tsx | 58 +++++++++ .../src/designer/selector/DslPreferences.tsx | 13 +++ .../src/designer/selector/DslSelector.css | 40 ++++++- .../src/designer/selector/DslSelector.tsx | 130 ++++++++------------- 17 files changed, 708 insertions(+), 292 deletions(-) diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index 8e49e054..36b65118 100644 --- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx @@ -73,30 +73,35 @@ export function KaravanDesigner(props: Props) { useEffect(() => { const sub = EventBus.onIntegrationUpdate()?.subscribe((update: IntegrationUpdate) => save(update.integration, update.propertyOnly)); - InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode); - InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode); - InfrastructureAPI.setOnSave(props.onSave); - InfrastructureAPI.setOnSavePropertyPlaceholder(props.onSavePropertyPlaceholder); - InfrastructureAPI.setOnInternalConsumerClick(props.onInternalConsumerClick); + try { + InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode); + InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode); + InfrastructureAPI.setOnSave(props.onSave); + InfrastructureAPI.setOnSavePropertyPlaceholder(props.onSavePropertyPlaceholder); + InfrastructureAPI.setOnInternalConsumerClick(props.onInternalConsumerClick); - setSelectedStep(undefined); - const i = makeIntegration(props.yaml, props.filename); - setIntegration(i, false); - let designerTab = i.kind === 'Kamelet' ? 'kamelet' : props.tab; - if (designerTab === undefined) { - const counts = CamelUi.getFlowCounts(i); - designerTab = (counts.get('routes') || 0) > 0 ? 'routes' : designerTab; - designerTab = (counts.get('rest') || 0) > 0 ? 'rest' : designerTab; - designerTab = (counts.get('beans') || 0) > 0 ? 'beans' : designerTab; + setSelectedStep(undefined); + const i = makeIntegration(props.yaml, props.filename); + setIntegration(i, false); + let designerTab = i.kind === 'Kamelet' ? 'kamelet' : props.tab; + if (designerTab === undefined) { + const counts = CamelUi.getFlowCounts(i); + designerTab = (counts.get('routes') || 0) > 0 ? 'routes' : designerTab; + designerTab = (counts.get('rest') || 0) > 0 ? 'rest' : designerTab; + designerTab = (counts.get('beans') || 0) > 0 ? 'beans' : designerTab; + } + setTab(designerTab || 'routes') + reset(); + setDark(props.dark); + setPropertyPlaceholders(props.propertyPlaceholders) + setVariables(VariableUtil.findVariables(props.files)) + setBeans(props.beans) + resetFiles(props.files) + setHideLogDSL(props.hideLogDSL === true); + } catch (e: any) { + console.log(e) + EventBus.sendAlert(' ' + e?.name, '' + e?.message, 'danger'); } - setTab(designerTab || 'routes') - reset(); - setDark(props.dark); - setPropertyPlaceholders(props.propertyPlaceholders) - setVariables(VariableUtil.findVariables(props.files)) - setBeans(props.beans) - resetFiles(props.files) - setHideLogDSL(props.hideLogDSL === true); return () => { sub?.unsubscribe(); setSelectedStep(undefined); @@ -169,19 +174,6 @@ export function KaravanDesigner(props: Props) { <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab> {props.showCodeTab && <Tab eventKey='code' title={getTab("YAML", "YAML Code", "code", true)}></Tab>} </Tabs> - {/*{tab === 'routes' && <Tooltip content={"Hide Log elements"}>*/} - {/* <Switch*/} - {/* isReversed*/} - {/* isChecked={hideLogDSL}*/} - {/* onChange={(_, checked) => {*/} - {/* setHideLogDSL(checked)*/} - {/* }}*/} - {/* aria-label={"Hide Log"}*/} - {/* id="hideLogDSL"*/} - {/* name="hideLogDSL"*/} - {/* className={"hide-log"}*/} - {/* />*/} - {/*</Tooltip>}*/} </div> {tab === 'kamelet' && <KameletDesigner/>} {tab === 'routes' && <RouteDesigner/>} diff --git a/karavan-app/src/main/webui/src/designer/selector/DslCard.tsx b/karavan-app/src/main/webui/src/designer/selector/DslCard.tsx new file mode 100644 index 00000000..1e0b9a3f --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/selector/DslCard.tsx @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Badge, + Card, CardBody, CardFooter, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import {KameletApi} from "karavan-core/lib/api/KameletApi"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + const {dsl, index} = props; + const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; + const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card" + onClick={event => selectDsl(event, dsl)}> + <CardHeader className="header-labels"> + <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> + {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && + <Badge isRead className="version labels">{dsl.version}</Badge> + } + {isCustom && <Badge className="custom">custom</Badge>} + </CardHeader> + <CardHeader> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-card-title'>{dsl.title}</Text> + </CardHeader> + <CardBody> + {/*<Text>{dsl.description}</Text>*/} + <Text className="pf-v5-u-color-200">{dsl.description}</Text> + </CardBody> + <CardFooter className="footer-labels"> + <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> + {labels.map((label, index) => <Badge key={label + "-" + index} isRead + className="labels">{label}</Badge>)} + </div> + + </CardFooter> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/designer/selector/DslFastCard.tsx b/karavan-app/src/main/webui/src/designer/selector/DslFastCard.tsx new file mode 100644 index 00000000..26bb8dfb --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/selector/DslFastCard.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Button, + Card, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import TimesIcon from "@patternfly/react-icons/dist/esm/icons/outlined-times-circle-icon"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void + onDeleteFast: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslFastCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + function deleteFast(evt: React.MouseEvent) { + props.onDeleteFast(evt, dsl); + } + + const {dsl, index} = props; + + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card dsl-fast-card" + onClick={event => selectDsl(event, dsl)}> + <Button className='fast-delete' variant='link' icon={<TimesIcon/>} onClick={deleteFast}/> + <CardHeader className='header'> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-fast-card-title'>{dsl.title}</Text> + </CardHeader> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/designer/selector/DslPreferences.tsx b/karavan-app/src/main/webui/src/designer/selector/DslPreferences.tsx index 87844b5a..9464b813 100644 --- a/karavan-app/src/main/webui/src/designer/selector/DslPreferences.tsx +++ b/karavan-app/src/main/webui/src/designer/selector/DslPreferences.tsx @@ -74,3 +74,16 @@ export function addPreferredElement(type: 'eip' | 'components' | 'kamelets', dsl console.log(e); } } + +export function deletePreferredElement(type: 'eip' | 'components' | 'kamelets', dsl: DslMetaModel) { + try { + const dslKey = type === 'eip' ? dsl.dsl : (type === 'components' ? dsl.uri : dsl.name); + const local = localStorage.getItem(PREFERRED_ELEMENTS_STORAGE_NAME); + const pes = local !== null ? new PreferredElements(JSON.parse(local)) : new PreferredElements(); + let list: PreferredElement[] = (pes as any)[type]; + (pes as any)[type] = [...list.filter(l => l.dslKey !== dslKey)]; + localStorage.setItem(PREFERRED_ELEMENTS_STORAGE_NAME, JSON.stringify(pes)); + } catch (e) { + console.log(e); + } +} diff --git a/karavan-app/src/main/webui/src/designer/selector/DslSelector.css b/karavan-app/src/main/webui/src/designer/selector/DslSelector.css index 279c1f41..4c54ffa3 100644 --- a/karavan-app/src/main/webui/src/designer/selector/DslSelector.css +++ b/karavan-app/src/main/webui/src/designer/selector/DslSelector.css @@ -27,6 +27,8 @@ .dsl-modal .dsl-card .pf-v5-c-card__header-main { display: flex; flex-direction: row; + justify-content: start; + overflow-x: hidden; } .dsl-modal .dsl-card .header-labels .pf-v5-c-card__header-main { @@ -120,15 +122,23 @@ .dsl-modal .labels { opacity: 0.5; font-weight: 200; - text-wrap: nowrap; + text-wrap: none; } -.dsl-modal .dsl-card:hover .labels { - opacity: 1; +.dsl-modal .dsl-card:hover .pf-v5-c-badge { + margin-right: 1px; +} + +.dsl-modal .dsl-card:hover { + background-color: var(--pf-v5-global--palette--black-200); +} + +.dsl-modal .dsl-card:hover .pf-v5-c-badge.pf-m-read { + --pf-v5-c-badge--BackgroundColor: var(--pf-v5-global--palette--black-150); } .dsl-modal .dsl-card:hover p { - color: var(--pf-v5-global--Color--100); + /*color: var(--pf-v5-global--Color--200);*/ } .dsl-modal .dsl-fast-card .header p { @@ -140,3 +150,25 @@ .dsl-modal .dsl-fast-card .header { padding: 10px; } + +.dsl-modal .dsl-fast-card .fast-delete { + padding: 0; + margin: 0; + width: 16px; + height: 16px; + vertical-align: text-bottom; + position: absolute; + right: 0; + top: -1px; + visibility: hidden; +} + +.dsl-modal .dsl-fast-card .fast-delete .pf-v5-svg { + fill: var(--pf-v5-global--danger-color--100); + width: 16px; + height: 16px; +} + +.dsl-modal .dsl-fast-card:hover .fast-delete { + visibility: visible; +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/designer/selector/DslSelector.tsx b/karavan-app/src/main/webui/src/designer/selector/DslSelector.tsx index 9e366515..50424e8c 100644 --- a/karavan-app/src/main/webui/src/designer/selector/DslSelector.tsx +++ b/karavan-app/src/main/webui/src/designer/selector/DslSelector.tsx @@ -16,12 +16,7 @@ */ import React, {useEffect, useState} from 'react'; import { - Badge, Button, - Card, - CardBody, - CardFooter, - CardHeader, Flex, FlexItem, Gallery, @@ -31,7 +26,6 @@ import { Tab, Tabs, TabTitleText, - Text, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, @@ -47,13 +41,15 @@ import {useRouteDesignerHook} from "../route/useRouteDesignerHook"; import {ComponentApi} from 'karavan-core/lib/api/ComponentApi'; import {KameletApi} from 'karavan-core/lib/api/KameletApi'; import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; -import {addPreferredElement, getPreferredElements} from "./DslPreferences"; +import {addPreferredElement, deletePreferredElement, getPreferredElements} from "./DslPreferences"; +import {DslFastCard} from "./DslFastCard"; +import {DslCard} from "./DslCard"; interface Props { tabIndex?: string | number } -export function DslSelector (props: Props) { +export function DslSelector(props: Props) { const [showSelector, showSteps, parentId, parentDsl, selectorTabIndex, setShowSelector, setSelectorTabIndex, selectedPosition, selectedLabels, addSelectedLabel, deleteSelectedLabel, clearSelectedLabels] = @@ -72,11 +68,14 @@ export function DslSelector (props: Props) { const [preferredKamelets, setPreferredKamelets] = useState<string[]>([]); useEffect(() => { + setPreferences(); + }, [selectedLabels]); + + function setPreferences() { setPreferredEip(getPreferredElements('eip')); setPreferredComponents(getPreferredElements('components')); setPreferredKamelets(getPreferredElements('kamelets')); - }, [selectedLabels]); - + } function selectTab(evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) { setSelectorTabIndex(eventKey); @@ -87,9 +86,13 @@ export function DslSelector (props: Props) { setFilter(''); setShowSelector(false); onDslSelect(dsl, parentId, selectedPosition); - const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); addPreferredElement(type, dsl) } + function deleteFast(evt: React.MouseEvent, dsl: DslMetaModel) { + evt.stopPropagation(); + deletePreferredElement(type, dsl); + setPreferences(); + } function searchInput() { return ( @@ -105,11 +108,11 @@ export function DslSelector (props: Props) { <FlexItem> <TextInputGroup> <TextInputGroupMain className="text-field" type="text" autoComplete={"off"} - value={filter} - onChange={(_, value) => setFilter(value)}/> + value={filter} + onChange={(_, value) => setFilter(value)}/> <TextInputGroupUtilities> <Button variant="plain" onClick={_ => setFilter('')}> - <TimesIcon /> + <TimesIcon/> </Button> </TextInputGroupUtilities> </TextInputGroup> @@ -118,50 +121,6 @@ export function DslSelector (props: Props) { ) } - function getCard(dsl: DslMetaModel, index: number) { - const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; - const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className="header-labels"> - <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> - {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && - <Badge isRead className="version labels">{dsl.version}</Badge> - } - {isCustom && <Badge className="custom">custom</Badge>} - </CardHeader> - <CardHeader> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-card-title'>{dsl.title}</Text> - </CardHeader> - <CardBody> - {/*<Text>{dsl.description}</Text>*/} - <Text className="pf-v5-u-color-200">{dsl.description}</Text> - </CardBody> - <CardFooter className="footer-labels"> - <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> - {labels.map((label, index) => <Badge key={label + "-" + index} isRead - className="labels">{label}</Badge>)} - </div> - - </CardFooter> - </Card> - ) - } - - function getFastCard(dsl: DslMetaModel, index: number) { - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card dsl-fast-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className='header'> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-fast-card-title'>{dsl.title}</Text> - </CardHeader> - </Card> - ) - } - function close() { setFilter(''); setShowSelector(false); @@ -175,7 +134,7 @@ export function DslSelector (props: Props) { } } - function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'):DslMetaModel[] { + function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'): DslMetaModel[] { return elements.filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (type !== 'eip' || selectedLabels.length === 0) { @@ -187,6 +146,7 @@ export function DslSelector (props: Props) { } const isEip = selectorTabIndex === 'eip'; + const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); const isRouteConfig = parentDsl === 'RouteConfigurationDefinition'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; @@ -219,6 +179,17 @@ export function DslSelector (props: Props) { ? filteredComponentElements : (navigation === 'kamelets' ? filteredKameletElements : filteredEipElements); + const fastElements = elements.filter((d: DslMetaModel) => { + if (isEip) { + return preferredElements.includes(d.dsl); + } else if (navigation === 'components') { + return d.uri && preferredElements.includes(d.uri) + } else { + return preferredElements.includes(d.name) + } + }).filter((_, i) => i < 7) + + return ( <Modal aria-label={title} @@ -237,7 +208,8 @@ export function DslSelector (props: Props) { onSelect={selectTab}> {parentDsl !== undefined && <Tab eventKey={"eip"} key={"tab-eip"} - title={<TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> + title={ + <TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> </Tab> } {!isRouteConfig && @@ -259,30 +231,26 @@ export function DslSelector (props: Props) { actions={{}}> <PageSection padding={{default: "noPadding"}} variant={dark ? "darker" : "light"}> {isEip && <ToggleGroup aria-label="Labels" isCompact> - {eipLabels.map(eipLabel => <ToggleGroupItem - key={eipLabel} - text={eipLabel} - buttonId={eipLabel} - isSelected={selectedLabels.includes(eipLabel)} - onChange={selected => selectLabel(eipLabel)} - />)} - <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} icon={<TimesIcon/>}/> + {eipLabels.map(eipLabel => + <ToggleGroupItem key={eipLabel} + text={eipLabel} + buttonId={eipLabel} + isSelected={selectedLabels.includes(eipLabel)} + onChange={selected => selectLabel(eipLabel)} + />)} + <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} + icon={<TimesIcon/>}/> </ToggleGroup>} - <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '150px'}}> - {showSelector && elements - .filter((d: DslMetaModel) => { - if (isEip) { - return preferredElements.includes(d.dsl); - } else if (navigation === 'components') { - return d.uri && preferredElements.includes(d.uri) - } else { - return preferredElements.includes(d.name) - } - }) - .filter((_, i) => i < 7).map((dsl: DslMetaModel, index: number) => getFastCard(dsl, index))} + <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" + minWidths={{default: '150px'}}> + {showSelector && fastElements.map((dsl: DslMetaModel, index: number) => + <DslFastCard dsl={dsl} index={index} onDslSelect={selectDsl} onDeleteFast={deleteFast}/> + )} </Gallery> <Gallery key={"gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '200px'}}> - {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => getCard(dsl, index))} + {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => + <DslCard dsl={dsl} index={index} onDslSelect={selectDsl}/> + )} </Gallery> </PageSection> </Modal> diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml index facfc3d1..b751e86b 100644 --- a/karavan-designer/public/example/demo.camel.yaml +++ b/karavan-designer/public/example/demo.camel.yaml @@ -59,5 +59,3 @@ Beer battle: ${jq(variable:beer1,.name)} (${variable.alc1}%) is weaker than ${jq(variable:beer2,.name)} (${variable.alc2}%) - otherwise: - id: otherwise-03ac \ No newline at end of file diff --git a/karavan-designer/src/designer/selector/DslCard.tsx b/karavan-designer/src/designer/selector/DslCard.tsx new file mode 100644 index 00000000..1e0b9a3f --- /dev/null +++ b/karavan-designer/src/designer/selector/DslCard.tsx @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Badge, + Card, CardBody, CardFooter, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import {KameletApi} from "karavan-core/lib/api/KameletApi"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + const {dsl, index} = props; + const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; + const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card" + onClick={event => selectDsl(event, dsl)}> + <CardHeader className="header-labels"> + <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> + {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && + <Badge isRead className="version labels">{dsl.version}</Badge> + } + {isCustom && <Badge className="custom">custom</Badge>} + </CardHeader> + <CardHeader> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-card-title'>{dsl.title}</Text> + </CardHeader> + <CardBody> + {/*<Text>{dsl.description}</Text>*/} + <Text className="pf-v5-u-color-200">{dsl.description}</Text> + </CardBody> + <CardFooter className="footer-labels"> + <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> + {labels.map((label, index) => <Badge key={label + "-" + index} isRead + className="labels">{label}</Badge>)} + </div> + + </CardFooter> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-designer/src/designer/selector/DslFastCard.tsx b/karavan-designer/src/designer/selector/DslFastCard.tsx new file mode 100644 index 00000000..26bb8dfb --- /dev/null +++ b/karavan-designer/src/designer/selector/DslFastCard.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Button, + Card, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import TimesIcon from "@patternfly/react-icons/dist/esm/icons/outlined-times-circle-icon"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void + onDeleteFast: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslFastCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + function deleteFast(evt: React.MouseEvent) { + props.onDeleteFast(evt, dsl); + } + + const {dsl, index} = props; + + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card dsl-fast-card" + onClick={event => selectDsl(event, dsl)}> + <Button className='fast-delete' variant='link' icon={<TimesIcon/>} onClick={deleteFast}/> + <CardHeader className='header'> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-fast-card-title'>{dsl.title}</Text> + </CardHeader> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-designer/src/designer/selector/DslPreferences.tsx b/karavan-designer/src/designer/selector/DslPreferences.tsx index 87844b5a..9464b813 100644 --- a/karavan-designer/src/designer/selector/DslPreferences.tsx +++ b/karavan-designer/src/designer/selector/DslPreferences.tsx @@ -74,3 +74,16 @@ export function addPreferredElement(type: 'eip' | 'components' | 'kamelets', dsl console.log(e); } } + +export function deletePreferredElement(type: 'eip' | 'components' | 'kamelets', dsl: DslMetaModel) { + try { + const dslKey = type === 'eip' ? dsl.dsl : (type === 'components' ? dsl.uri : dsl.name); + const local = localStorage.getItem(PREFERRED_ELEMENTS_STORAGE_NAME); + const pes = local !== null ? new PreferredElements(JSON.parse(local)) : new PreferredElements(); + let list: PreferredElement[] = (pes as any)[type]; + (pes as any)[type] = [...list.filter(l => l.dslKey !== dslKey)]; + localStorage.setItem(PREFERRED_ELEMENTS_STORAGE_NAME, JSON.stringify(pes)); + } catch (e) { + console.log(e); + } +} diff --git a/karavan-designer/src/designer/selector/DslSelector.css b/karavan-designer/src/designer/selector/DslSelector.css index 279c1f41..4c54ffa3 100644 --- a/karavan-designer/src/designer/selector/DslSelector.css +++ b/karavan-designer/src/designer/selector/DslSelector.css @@ -27,6 +27,8 @@ .dsl-modal .dsl-card .pf-v5-c-card__header-main { display: flex; flex-direction: row; + justify-content: start; + overflow-x: hidden; } .dsl-modal .dsl-card .header-labels .pf-v5-c-card__header-main { @@ -120,15 +122,23 @@ .dsl-modal .labels { opacity: 0.5; font-weight: 200; - text-wrap: nowrap; + text-wrap: none; } -.dsl-modal .dsl-card:hover .labels { - opacity: 1; +.dsl-modal .dsl-card:hover .pf-v5-c-badge { + margin-right: 1px; +} + +.dsl-modal .dsl-card:hover { + background-color: var(--pf-v5-global--palette--black-200); +} + +.dsl-modal .dsl-card:hover .pf-v5-c-badge.pf-m-read { + --pf-v5-c-badge--BackgroundColor: var(--pf-v5-global--palette--black-150); } .dsl-modal .dsl-card:hover p { - color: var(--pf-v5-global--Color--100); + /*color: var(--pf-v5-global--Color--200);*/ } .dsl-modal .dsl-fast-card .header p { @@ -140,3 +150,25 @@ .dsl-modal .dsl-fast-card .header { padding: 10px; } + +.dsl-modal .dsl-fast-card .fast-delete { + padding: 0; + margin: 0; + width: 16px; + height: 16px; + vertical-align: text-bottom; + position: absolute; + right: 0; + top: -1px; + visibility: hidden; +} + +.dsl-modal .dsl-fast-card .fast-delete .pf-v5-svg { + fill: var(--pf-v5-global--danger-color--100); + width: 16px; + height: 16px; +} + +.dsl-modal .dsl-fast-card:hover .fast-delete { + visibility: visible; +} \ No newline at end of file diff --git a/karavan-designer/src/designer/selector/DslSelector.tsx b/karavan-designer/src/designer/selector/DslSelector.tsx index 9e366515..50424e8c 100644 --- a/karavan-designer/src/designer/selector/DslSelector.tsx +++ b/karavan-designer/src/designer/selector/DslSelector.tsx @@ -16,12 +16,7 @@ */ import React, {useEffect, useState} from 'react'; import { - Badge, Button, - Card, - CardBody, - CardFooter, - CardHeader, Flex, FlexItem, Gallery, @@ -31,7 +26,6 @@ import { Tab, Tabs, TabTitleText, - Text, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, @@ -47,13 +41,15 @@ import {useRouteDesignerHook} from "../route/useRouteDesignerHook"; import {ComponentApi} from 'karavan-core/lib/api/ComponentApi'; import {KameletApi} from 'karavan-core/lib/api/KameletApi'; import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; -import {addPreferredElement, getPreferredElements} from "./DslPreferences"; +import {addPreferredElement, deletePreferredElement, getPreferredElements} from "./DslPreferences"; +import {DslFastCard} from "./DslFastCard"; +import {DslCard} from "./DslCard"; interface Props { tabIndex?: string | number } -export function DslSelector (props: Props) { +export function DslSelector(props: Props) { const [showSelector, showSteps, parentId, parentDsl, selectorTabIndex, setShowSelector, setSelectorTabIndex, selectedPosition, selectedLabels, addSelectedLabel, deleteSelectedLabel, clearSelectedLabels] = @@ -72,11 +68,14 @@ export function DslSelector (props: Props) { const [preferredKamelets, setPreferredKamelets] = useState<string[]>([]); useEffect(() => { + setPreferences(); + }, [selectedLabels]); + + function setPreferences() { setPreferredEip(getPreferredElements('eip')); setPreferredComponents(getPreferredElements('components')); setPreferredKamelets(getPreferredElements('kamelets')); - }, [selectedLabels]); - + } function selectTab(evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) { setSelectorTabIndex(eventKey); @@ -87,9 +86,13 @@ export function DslSelector (props: Props) { setFilter(''); setShowSelector(false); onDslSelect(dsl, parentId, selectedPosition); - const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); addPreferredElement(type, dsl) } + function deleteFast(evt: React.MouseEvent, dsl: DslMetaModel) { + evt.stopPropagation(); + deletePreferredElement(type, dsl); + setPreferences(); + } function searchInput() { return ( @@ -105,11 +108,11 @@ export function DslSelector (props: Props) { <FlexItem> <TextInputGroup> <TextInputGroupMain className="text-field" type="text" autoComplete={"off"} - value={filter} - onChange={(_, value) => setFilter(value)}/> + value={filter} + onChange={(_, value) => setFilter(value)}/> <TextInputGroupUtilities> <Button variant="plain" onClick={_ => setFilter('')}> - <TimesIcon /> + <TimesIcon/> </Button> </TextInputGroupUtilities> </TextInputGroup> @@ -118,50 +121,6 @@ export function DslSelector (props: Props) { ) } - function getCard(dsl: DslMetaModel, index: number) { - const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; - const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className="header-labels"> - <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> - {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && - <Badge isRead className="version labels">{dsl.version}</Badge> - } - {isCustom && <Badge className="custom">custom</Badge>} - </CardHeader> - <CardHeader> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-card-title'>{dsl.title}</Text> - </CardHeader> - <CardBody> - {/*<Text>{dsl.description}</Text>*/} - <Text className="pf-v5-u-color-200">{dsl.description}</Text> - </CardBody> - <CardFooter className="footer-labels"> - <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> - {labels.map((label, index) => <Badge key={label + "-" + index} isRead - className="labels">{label}</Badge>)} - </div> - - </CardFooter> - </Card> - ) - } - - function getFastCard(dsl: DslMetaModel, index: number) { - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card dsl-fast-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className='header'> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-fast-card-title'>{dsl.title}</Text> - </CardHeader> - </Card> - ) - } - function close() { setFilter(''); setShowSelector(false); @@ -175,7 +134,7 @@ export function DslSelector (props: Props) { } } - function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'):DslMetaModel[] { + function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'): DslMetaModel[] { return elements.filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (type !== 'eip' || selectedLabels.length === 0) { @@ -187,6 +146,7 @@ export function DslSelector (props: Props) { } const isEip = selectorTabIndex === 'eip'; + const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); const isRouteConfig = parentDsl === 'RouteConfigurationDefinition'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; @@ -219,6 +179,17 @@ export function DslSelector (props: Props) { ? filteredComponentElements : (navigation === 'kamelets' ? filteredKameletElements : filteredEipElements); + const fastElements = elements.filter((d: DslMetaModel) => { + if (isEip) { + return preferredElements.includes(d.dsl); + } else if (navigation === 'components') { + return d.uri && preferredElements.includes(d.uri) + } else { + return preferredElements.includes(d.name) + } + }).filter((_, i) => i < 7) + + return ( <Modal aria-label={title} @@ -237,7 +208,8 @@ export function DslSelector (props: Props) { onSelect={selectTab}> {parentDsl !== undefined && <Tab eventKey={"eip"} key={"tab-eip"} - title={<TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> + title={ + <TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> </Tab> } {!isRouteConfig && @@ -259,30 +231,26 @@ export function DslSelector (props: Props) { actions={{}}> <PageSection padding={{default: "noPadding"}} variant={dark ? "darker" : "light"}> {isEip && <ToggleGroup aria-label="Labels" isCompact> - {eipLabels.map(eipLabel => <ToggleGroupItem - key={eipLabel} - text={eipLabel} - buttonId={eipLabel} - isSelected={selectedLabels.includes(eipLabel)} - onChange={selected => selectLabel(eipLabel)} - />)} - <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} icon={<TimesIcon/>}/> + {eipLabels.map(eipLabel => + <ToggleGroupItem key={eipLabel} + text={eipLabel} + buttonId={eipLabel} + isSelected={selectedLabels.includes(eipLabel)} + onChange={selected => selectLabel(eipLabel)} + />)} + <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} + icon={<TimesIcon/>}/> </ToggleGroup>} - <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '150px'}}> - {showSelector && elements - .filter((d: DslMetaModel) => { - if (isEip) { - return preferredElements.includes(d.dsl); - } else if (navigation === 'components') { - return d.uri && preferredElements.includes(d.uri) - } else { - return preferredElements.includes(d.name) - } - }) - .filter((_, i) => i < 7).map((dsl: DslMetaModel, index: number) => getFastCard(dsl, index))} + <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" + minWidths={{default: '150px'}}> + {showSelector && fastElements.map((dsl: DslMetaModel, index: number) => + <DslFastCard dsl={dsl} index={index} onDslSelect={selectDsl} onDeleteFast={deleteFast}/> + )} </Gallery> <Gallery key={"gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '200px'}}> - {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => getCard(dsl, index))} + {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => + <DslCard dsl={dsl} index={index} onDslSelect={selectDsl}/> + )} </Gallery> </PageSection> </Modal> diff --git a/karavan-space/src/designer/selector/DslCard.tsx b/karavan-space/src/designer/selector/DslCard.tsx new file mode 100644 index 00000000..1e0b9a3f --- /dev/null +++ b/karavan-space/src/designer/selector/DslCard.tsx @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Badge, + Card, CardBody, CardFooter, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import {KameletApi} from "karavan-core/lib/api/KameletApi"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + const {dsl, index} = props; + const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; + const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card" + onClick={event => selectDsl(event, dsl)}> + <CardHeader className="header-labels"> + <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> + {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && + <Badge isRead className="version labels">{dsl.version}</Badge> + } + {isCustom && <Badge className="custom">custom</Badge>} + </CardHeader> + <CardHeader> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-card-title'>{dsl.title}</Text> + </CardHeader> + <CardBody> + {/*<Text>{dsl.description}</Text>*/} + <Text className="pf-v5-u-color-200">{dsl.description}</Text> + </CardBody> + <CardFooter className="footer-labels"> + <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> + {labels.map((label, index) => <Badge key={label + "-" + index} isRead + className="labels">{label}</Badge>)} + </div> + + </CardFooter> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-space/src/designer/selector/DslFastCard.tsx b/karavan-space/src/designer/selector/DslFastCard.tsx new file mode 100644 index 00000000..26bb8dfb --- /dev/null +++ b/karavan-space/src/designer/selector/DslFastCard.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import { + Button, + Card, + CardHeader, + Text, +} from '@patternfly/react-core'; +import './DslSelector.css'; +import {CamelUi} from "../utils/CamelUi"; +import {DslMetaModel} from "../utils/DslMetaModel"; +import TimesIcon from "@patternfly/react-icons/dist/esm/icons/outlined-times-circle-icon"; + +interface Props { + dsl: DslMetaModel, + index: number + onDslSelect: (evt: React.MouseEvent, dsl: DslMetaModel) => void + onDeleteFast: (evt: React.MouseEvent, dsl: DslMetaModel) => void +} + +export function DslFastCard (props: Props) { + + function selectDsl(evt: React.MouseEvent, dsl: DslMetaModel) { + props.onDslSelect(evt, dsl); + } + + function deleteFast(evt: React.MouseEvent) { + props.onDeleteFast(evt, dsl); + } + + const {dsl, index} = props; + + return ( + <Card key={dsl.dsl + index} isCompact isPlain isFlat isRounded className="dsl-card dsl-fast-card" + onClick={event => selectDsl(event, dsl)}> + <Button className='fast-delete' variant='link' icon={<TimesIcon/>} onClick={deleteFast}/> + <CardHeader className='header'> + {CamelUi.getIconForDsl(dsl)} + <Text className='dsl-fast-card-title'>{dsl.title}</Text> + </CardHeader> + </Card> + ) +} \ No newline at end of file diff --git a/karavan-space/src/designer/selector/DslPreferences.tsx b/karavan-space/src/designer/selector/DslPreferences.tsx index 87844b5a..9464b813 100644 --- a/karavan-space/src/designer/selector/DslPreferences.tsx +++ b/karavan-space/src/designer/selector/DslPreferences.tsx @@ -74,3 +74,16 @@ export function addPreferredElement(type: 'eip' | 'components' | 'kamelets', dsl console.log(e); } } + +export function deletePreferredElement(type: 'eip' | 'components' | 'kamelets', dsl: DslMetaModel) { + try { + const dslKey = type === 'eip' ? dsl.dsl : (type === 'components' ? dsl.uri : dsl.name); + const local = localStorage.getItem(PREFERRED_ELEMENTS_STORAGE_NAME); + const pes = local !== null ? new PreferredElements(JSON.parse(local)) : new PreferredElements(); + let list: PreferredElement[] = (pes as any)[type]; + (pes as any)[type] = [...list.filter(l => l.dslKey !== dslKey)]; + localStorage.setItem(PREFERRED_ELEMENTS_STORAGE_NAME, JSON.stringify(pes)); + } catch (e) { + console.log(e); + } +} diff --git a/karavan-space/src/designer/selector/DslSelector.css b/karavan-space/src/designer/selector/DslSelector.css index 279c1f41..4c54ffa3 100644 --- a/karavan-space/src/designer/selector/DslSelector.css +++ b/karavan-space/src/designer/selector/DslSelector.css @@ -27,6 +27,8 @@ .dsl-modal .dsl-card .pf-v5-c-card__header-main { display: flex; flex-direction: row; + justify-content: start; + overflow-x: hidden; } .dsl-modal .dsl-card .header-labels .pf-v5-c-card__header-main { @@ -120,15 +122,23 @@ .dsl-modal .labels { opacity: 0.5; font-weight: 200; - text-wrap: nowrap; + text-wrap: none; } -.dsl-modal .dsl-card:hover .labels { - opacity: 1; +.dsl-modal .dsl-card:hover .pf-v5-c-badge { + margin-right: 1px; +} + +.dsl-modal .dsl-card:hover { + background-color: var(--pf-v5-global--palette--black-200); +} + +.dsl-modal .dsl-card:hover .pf-v5-c-badge.pf-m-read { + --pf-v5-c-badge--BackgroundColor: var(--pf-v5-global--palette--black-150); } .dsl-modal .dsl-card:hover p { - color: var(--pf-v5-global--Color--100); + /*color: var(--pf-v5-global--Color--200);*/ } .dsl-modal .dsl-fast-card .header p { @@ -140,3 +150,25 @@ .dsl-modal .dsl-fast-card .header { padding: 10px; } + +.dsl-modal .dsl-fast-card .fast-delete { + padding: 0; + margin: 0; + width: 16px; + height: 16px; + vertical-align: text-bottom; + position: absolute; + right: 0; + top: -1px; + visibility: hidden; +} + +.dsl-modal .dsl-fast-card .fast-delete .pf-v5-svg { + fill: var(--pf-v5-global--danger-color--100); + width: 16px; + height: 16px; +} + +.dsl-modal .dsl-fast-card:hover .fast-delete { + visibility: visible; +} \ No newline at end of file diff --git a/karavan-space/src/designer/selector/DslSelector.tsx b/karavan-space/src/designer/selector/DslSelector.tsx index 9e366515..50424e8c 100644 --- a/karavan-space/src/designer/selector/DslSelector.tsx +++ b/karavan-space/src/designer/selector/DslSelector.tsx @@ -16,12 +16,7 @@ */ import React, {useEffect, useState} from 'react'; import { - Badge, Button, - Card, - CardBody, - CardFooter, - CardHeader, Flex, FlexItem, Gallery, @@ -31,7 +26,6 @@ import { Tab, Tabs, TabTitleText, - Text, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, @@ -47,13 +41,15 @@ import {useRouteDesignerHook} from "../route/useRouteDesignerHook"; import {ComponentApi} from 'karavan-core/lib/api/ComponentApi'; import {KameletApi} from 'karavan-core/lib/api/KameletApi'; import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; -import {addPreferredElement, getPreferredElements} from "./DslPreferences"; +import {addPreferredElement, deletePreferredElement, getPreferredElements} from "./DslPreferences"; +import {DslFastCard} from "./DslFastCard"; +import {DslCard} from "./DslCard"; interface Props { tabIndex?: string | number } -export function DslSelector (props: Props) { +export function DslSelector(props: Props) { const [showSelector, showSteps, parentId, parentDsl, selectorTabIndex, setShowSelector, setSelectorTabIndex, selectedPosition, selectedLabels, addSelectedLabel, deleteSelectedLabel, clearSelectedLabels] = @@ -72,11 +68,14 @@ export function DslSelector (props: Props) { const [preferredKamelets, setPreferredKamelets] = useState<string[]>([]); useEffect(() => { + setPreferences(); + }, [selectedLabels]); + + function setPreferences() { setPreferredEip(getPreferredElements('eip')); setPreferredComponents(getPreferredElements('components')); setPreferredKamelets(getPreferredElements('kamelets')); - }, [selectedLabels]); - + } function selectTab(evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) { setSelectorTabIndex(eventKey); @@ -87,9 +86,13 @@ export function DslSelector (props: Props) { setFilter(''); setShowSelector(false); onDslSelect(dsl, parentId, selectedPosition); - const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); addPreferredElement(type, dsl) } + function deleteFast(evt: React.MouseEvent, dsl: DslMetaModel) { + evt.stopPropagation(); + deletePreferredElement(type, dsl); + setPreferences(); + } function searchInput() { return ( @@ -105,11 +108,11 @@ export function DslSelector (props: Props) { <FlexItem> <TextInputGroup> <TextInputGroupMain className="text-field" type="text" autoComplete={"off"} - value={filter} - onChange={(_, value) => setFilter(value)}/> + value={filter} + onChange={(_, value) => setFilter(value)}/> <TextInputGroupUtilities> <Button variant="plain" onClick={_ => setFilter('')}> - <TimesIcon /> + <TimesIcon/> </Button> </TextInputGroupUtilities> </TextInputGroup> @@ -118,50 +121,6 @@ export function DslSelector (props: Props) { ) } - function getCard(dsl: DslMetaModel, index: number) { - const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : []; - const isCustom = KameletApi.getCustomKameletNames().includes(dsl.name); - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className="header-labels"> - <Badge isRead className="support-level labels">{dsl.supportLevel}</Badge> - {['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) && - <Badge isRead className="version labels">{dsl.version}</Badge> - } - {isCustom && <Badge className="custom">custom</Badge>} - </CardHeader> - <CardHeader> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-card-title'>{dsl.title}</Text> - </CardHeader> - <CardBody> - {/*<Text>{dsl.description}</Text>*/} - <Text className="pf-v5-u-color-200">{dsl.description}</Text> - </CardBody> - <CardFooter className="footer-labels"> - <div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}> - {labels.map((label, index) => <Badge key={label + "-" + index} isRead - className="labels">{label}</Badge>)} - </div> - - </CardFooter> - </Card> - ) - } - - function getFastCard(dsl: DslMetaModel, index: number) { - return ( - <Card key={dsl.dsl + index} isCompact className="dsl-card dsl-fast-card" - onClick={event => selectDsl(event, dsl)}> - <CardHeader className='header'> - {CamelUi.getIconForDsl(dsl)} - <Text className='dsl-fast-card-title'>{dsl.title}</Text> - </CardHeader> - </Card> - ) - } - function close() { setFilter(''); setShowSelector(false); @@ -175,7 +134,7 @@ export function DslSelector (props: Props) { } } - function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'):DslMetaModel[] { + function filterElements(elements: DslMetaModel[], type: 'eip' | 'components' | 'kamelets'): DslMetaModel[] { return elements.filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (type !== 'eip' || selectedLabels.length === 0) { @@ -187,6 +146,7 @@ export function DslSelector (props: Props) { } const isEip = selectorTabIndex === 'eip'; + const type = isEip ? 'eip' : (selectorTabIndex === 'components' ? 'components' : 'kamelets'); const isRouteConfig = parentDsl === 'RouteConfigurationDefinition'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; @@ -219,6 +179,17 @@ export function DslSelector (props: Props) { ? filteredComponentElements : (navigation === 'kamelets' ? filteredKameletElements : filteredEipElements); + const fastElements = elements.filter((d: DslMetaModel) => { + if (isEip) { + return preferredElements.includes(d.dsl); + } else if (navigation === 'components') { + return d.uri && preferredElements.includes(d.uri) + } else { + return preferredElements.includes(d.name) + } + }).filter((_, i) => i < 7) + + return ( <Modal aria-label={title} @@ -237,7 +208,8 @@ export function DslSelector (props: Props) { onSelect={selectTab}> {parentDsl !== undefined && <Tab eventKey={"eip"} key={"tab-eip"} - title={<TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> + title={ + <TabTitleText>{`Integration Patterns (${filteredEipElements?.length})`}</TabTitleText>}> </Tab> } {!isRouteConfig && @@ -259,30 +231,26 @@ export function DslSelector (props: Props) { actions={{}}> <PageSection padding={{default: "noPadding"}} variant={dark ? "darker" : "light"}> {isEip && <ToggleGroup aria-label="Labels" isCompact> - {eipLabels.map(eipLabel => <ToggleGroupItem - key={eipLabel} - text={eipLabel} - buttonId={eipLabel} - isSelected={selectedLabels.includes(eipLabel)} - onChange={selected => selectLabel(eipLabel)} - />)} - <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} icon={<TimesIcon/>}/> + {eipLabels.map(eipLabel => + <ToggleGroupItem key={eipLabel} + text={eipLabel} + buttonId={eipLabel} + isSelected={selectedLabels.includes(eipLabel)} + onChange={selected => selectLabel(eipLabel)} + />)} + <ToggleGroupItem key='clean' buttonId='clean' isSelected={false} onChange={clearSelectedLabels} + icon={<TimesIcon/>}/> </ToggleGroup>} - <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '150px'}}> - {showSelector && elements - .filter((d: DslMetaModel) => { - if (isEip) { - return preferredElements.includes(d.dsl); - } else if (navigation === 'components') { - return d.uri && preferredElements.includes(d.uri) - } else { - return preferredElements.includes(d.name) - } - }) - .filter((_, i) => i < 7).map((dsl: DslMetaModel, index: number) => getFastCard(dsl, index))} + <Gallery key={"fast-gallery-" + navigation} hasGutter className="dsl-gallery" + minWidths={{default: '150px'}}> + {showSelector && fastElements.map((dsl: DslMetaModel, index: number) => + <DslFastCard dsl={dsl} index={index} onDslSelect={selectDsl} onDeleteFast={deleteFast}/> + )} </Gallery> <Gallery key={"gallery-" + navigation} hasGutter className="dsl-gallery" minWidths={{default: '200px'}}> - {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => getCard(dsl, index))} + {showSelector && filteredElements.map((dsl: DslMetaModel, index: number) => + <DslCard dsl={dsl} index={index} onDslSelect={selectDsl}/> + )} </Gallery> </PageSection> </Modal>