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 d14e09e Saas feature38 (#435) d14e09e is described below commit d14e09e8ffd6f9cf676ba52bb226180b6212f173 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Thu Jul 28 21:45:00 2022 -0400 Saas feature38 (#435) * Rollback errorrously deleted Project Classes * Cleanup --- karavan-app/src/main/webapp/package-lock.json | 6 +- karavan-app/src/main/webapp/package.json | 2 +- karavan-designer/src/App.tsx | 16 -- karavan-designer/src/builder/BuilderPage.tsx | 298 --------------------- karavan-designer/src/builder/FileSelector.tsx | 97 ------- karavan-designer/src/builder/ProfileSelector.tsx | 148 ---------- karavan-designer/src/builder/PropertiesTable.tsx | 148 ---------- karavan-vscode/package.json | 2 +- karavan-vscode/webview/builder/BuilderPage.tsx | 298 --------------------- karavan-vscode/webview/builder/FileSelector.tsx | 97 ------- karavan-vscode/webview/builder/ProfileSelector.tsx | 148 ---------- karavan-vscode/webview/builder/PropertiesTable.tsx | 148 ---------- 12 files changed, 5 insertions(+), 1403 deletions(-) diff --git a/karavan-app/src/main/webapp/package-lock.json b/karavan-app/src/main/webapp/package-lock.json index f397c3c..ed941bd 100644 --- a/karavan-app/src/main/webapp/package-lock.json +++ b/karavan-app/src/main/webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "karavan", - "version": "0.0.16", + "version": "3.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "karavan", - "version": "0.0.16", + "version": "3.18.0", "dependencies": { "@monaco-editor/react": "4.3.1", "@patternfly/patternfly": "4.194.4", @@ -40,7 +40,7 @@ } }, "../../../../karavan-core": { - "version": "0.0.16", + "version": "3.18.0", "license": "Apache-2.0", "dependencies": { "@types/js-yaml": "^4.0.5", diff --git a/karavan-app/src/main/webapp/package.json b/karavan-app/src/main/webapp/package.json index e066815..1e64f4a 100644 --- a/karavan-app/src/main/webapp/package.json +++ b/karavan-app/src/main/webapp/package.json @@ -3,7 +3,7 @@ "version": "3.18.0", "private": true, "scripts": { - "copy-designer": "cp -r ../../../../karavan-designer/src/designer src && cp -r ../../../../karavan-designer/src/kamelets src && cp -r ../../../../karavan-designer/src/components src && cp -r ../../../../karavan-designer/src/eip src && cp -r ../../../../karavan-designer/src/builder src", + "copy-designer": "cp -r ../../../../karavan-designer/src/designer src && cp -r ../../../../karavan-designer/src/kamelets src && cp -r ../../../../karavan-designer/src/components src && cp -r ../../../../karavan-designer/src/eip src", "start": "npm run copy-designer && react-scripts start", "build": "npm run copy-designer && react-scripts build", "prod": "npm run copy-designer && react-scripts build --dest && rsync -a build/* ../resources/META-INF/resources" diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index af4a5a8..35930b4 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -24,8 +24,6 @@ import {KaravanDesigner} from "./designer/KaravanDesigner"; import {KameletsPage} from "./kamelets/KameletsPage"; import {ComponentsPage} from "./components/ComponentsPage"; import {EipPage} from "./eip/EipPage"; -import {BuilderPage} from "./builder/BuilderPage"; -import {ProjectModel, StepStatus} from "karavan-core/lib/model/ProjectModel"; interface Props { page: "designer" | "kamelets" | "components" | "eip" | "builder"; @@ -127,14 +125,6 @@ class App extends React.Component<Props, State> { } public render() { - const project = ProjectModel.createNew({}); - // project.properties.set("message", "Hello Placeholder!") - // project.properties.set("camel.jbang.classpathFiles", "application.properties") - // project.properties.set("camel.main.routesIncludePattern", "file:demo.yaml") - // project.properties.set("camel.component.properties.location", "file:application.properties") - project.status.active = true; - project.status.export = new StepStatus({status:"progress"}); - project.status.package = new StepStatus({status:"progress"}); return ( <Page className="karavan"> {this.props.page === "designer" && <KaravanDesigner key={this.state.key} filename={this.state.name} yaml={this.state.yaml} @@ -144,12 +134,6 @@ class App extends React.Component<Props, State> { {this.props.page === "kamelets" && <KameletsPage dark={document.body.className.includes('vscode-dark')} />} {this.props.page === "components" && <ComponentsPage dark={document.body.className.includes('vscode-dark')} />} {this.props.page === "eip" && <EipPage dark={document.body.className.includes('vscode-dark')} />} - {this.props.page === "builder" && <BuilderPage dark={document.body.className.includes('vscode-dark')} project={project} - onChange={project => { - // console.log("routesIncludePattern", project.routesIncludePattern); - // console.log("classpathFiles", project.classpathFiles); - }} - files={'demo.yaml,CustomProcessor.java,script.groovy,docker-compose.yaml,README.MD'}/>} </Page> ); } diff --git a/karavan-designer/src/builder/BuilderPage.tsx b/karavan-designer/src/builder/BuilderPage.tsx deleted file mode 100644 index 43c27d5..0000000 --- a/karavan-designer/src/builder/BuilderPage.tsx +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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, - Button, - Card, - CardBody, - CardHeader, - CardHeaderMain, - CardTitle, - Flex, - FlexItem, - Form, - FormGroup, - InputGroup, - PageSection, - PageSectionVariants, - Popover, - PopoverPosition, - ProgressStep, - ProgressStepper, - Spinner, - Text, - TextContent, - TextInput, - Toolbar, - ToolbarContent, - ToolbarItem, -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; -import InProgressIcon from '@patternfly/react-icons/dist/esm/icons/in-progress-icon'; -import AutomationIcon from '@patternfly/react-icons/dist/esm/icons/bundle-icon'; -import PendingIcon from '@patternfly/react-icons/dist/esm/icons/pending-icon'; -import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; -import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; -import ProjectIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon'; -import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; -import RunIcon from '@patternfly/react-icons/dist/esm/icons/play-circle-icon'; -import {ProjectModel, StepStatus} from "karavan-core/lib/model/ProjectModel"; -import {PropertiesTable} from "./PropertiesTable"; - -interface Props { - project: ProjectModel, - dark: boolean - files: string - onChange?: (project: ProjectModel) => void - onAction?: (action: "start" | "stop" | "undeploy" | "run", project: ProjectModel) => void -} - -interface State { - project: ProjectModel, - key?: string, - isOpen?: boolean -} - -export class BuilderPage extends React.Component<Props, State> { - - public state: State = { - project: this.props.project, - }; - interval: any; - - componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { - const project = this.state.project; - if (project) this.props.onChange?.call(this, project); - } - - componentDidMount() { - this.interval = setInterval(() => this.setState(state => ({key: Math.random().toString()})), 1000); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - getHelp(text: string) { - return <Popover - aria-label={text} - position={PopoverPosition.left} - bodyContent={text}> - <Button variant="plain" onClick={e => { - }}> - <HelpIcon/> - </Button> - </Popover> - } - - getField(name: string, label: string, type: 'text' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' | 'time' | 'url', - value: any, help: string, onChange: (val: any) => void, isRequired: boolean = false, enabled: boolean = true) { - return <FormGroup label={label} fieldId={name} isRequired={isRequired}> - <InputGroup> - <TextInput isRequired={isRequired} isDisabled={!enabled} className="text-field" type={type} id={name} name={name} value={value} - onChange={val => onChange?.call(this, val)}/> - {this.getHelp(help)} - </InputGroup> - </FormGroup> - } - - getCardHeader(title: string, icon: any) { - return <CardHeader> - <CardHeaderMain> - <CardTitle className="card-header"> - {icon}{title} - </CardTitle> - </CardHeaderMain> - </CardHeader> - } - - getProjectForm() { - return ( - <Card className="builder-card" isCompact style={{width: "100%"}}> - {this.getCardHeader("Artifact", <ProjectIcon/>)} - <CardBody> - <Form isHorizontal> - {/*{this.getField("name", "Name", "text", this.state.profile.project.name, "Project name",*/} - {/* val => this.setState(state => {state.profile.project.name= val; return state}), true)}*/} - {/*{this.getField("version", "Version", "text", this.state.profile.project.version, "Project version",*/} - {/* val => this.setState(state => {state.profile.project.version= val; return state}), true)}*/} - </Form> - </CardBody> - </Card> - ) - } - - getProgressIcon(status?: 'pending' | 'progress' | 'done' | 'error') { - switch (status) { - case "pending": - return <PendingIcon/>; - case "progress": - return <Spinner isSVG size="md"/> - case "done": - return <CheckCircleIcon/>; - case "error": - return <ExclamationCircleIcon/>; - default: - return undefined; - } - } - - getDescription(stepStatus?: StepStatus) { - const now = Date.now(); - let time = 0; - if (stepStatus?.status === 'progress') { - time = stepStatus?.startTime ? (now - stepStatus.startTime) / 1000 : 0; - } else if (stepStatus?.status === 'done' && stepStatus?.endTime) { - time = (stepStatus?.endTime - stepStatus.startTime) / 1000 - } - return time === 0 ? "" : Math.round(time) + "s"; - } - - getProgress() { - const {status} = this.state.project; - return ( - <ProgressStepper isCenterAligned style={{visibility: "visible"}}> - <ProgressStep variant="pending" id="export" titleId="export" aria-label="export" - description={this.getDescription(status.export)} - icon={this.getProgressIcon(status.export?.status)}>Export - </ProgressStep> - <ProgressStep variant="pending" isCurrent id="package" titleId="package" aria-label="package" - description={this.getDescription(status.package)} - icon={this.getProgressIcon(status.package?.status)}>Package - </ProgressStep> - </ProgressStepper> - ) - } - - getHeader() { - return ( - <PageSection className="tools-section" variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> - <Flex className="tools" direction={{default: 'row'}} justifyContent={{default: 'justifyContentSpaceBetween'}} spaceItems={{default: 'spaceItemsLg'}}> - <FlexItem> - <TextContent className="header"> - <Text component="h2">Build Runner</Text> - <Badge isRead className="labels">Powered by Camel JBang & Maven</Badge> - </TextContent> - </FlexItem> - <FlexItem> - <Toolbar id="toolbar-group-types"> - <ToolbarContent> - <ToolbarItem> - {/*<ProfileSelector profiles={profiles.map(p => p.name)}*/} - {/* profile={profile.name}*/} - {/* onDelete={profile => {*/} - {/* this.setState(state => {*/} - {/* state.profiles.splice(state.profiles.findIndex(p => p.name === profile), 1);*/} - {/* return {*/} - {/* profiles: state.profiles,*/} - {/* profile: this.props.profiles.at(0) || Profile.createNew("application"),*/} - {/* tab: state.tab*/} - {/* };*/} - {/* })*/} - {/* }}*/} - {/* onChange={profileName => {*/} - {/* const prof = profiles.find(p => p.name === profileName);*/} - {/* if (prof) {*/} - {/* this.setState({profile: prof, key: Math.random().toString()});*/} - {/* } else {*/} - {/* this.setState(state => {*/} - {/* const newProfile = Profile.createNew(profileName);*/} - {/* newProfile.project = new ProjectModel(this.state.profile.project);*/} - {/* state.profiles.push(newProfile);*/} - {/* return {profiles: state.profiles, profile: newProfile, tab: state.tab};*/} - {/* })*/} - {/* }*/} - {/* }}/>*/} - </ToolbarItem> - </ToolbarContent> - </Toolbar> - </FlexItem> - </Flex> - </PageSection> - ) - } - - onButtonClick(action: "start" | "stop" | "undeploy" | "run") { - this.props.onAction?.call(this, action, this.state.project); - } - - getFooter() { - const active = false; - const label = active ? "Stop" : "Package"; - const icon = active ? <InProgressIcon/> : <AutomationIcon/>; - return <div key={this.state.key} className="footer"> - <div className="progress"> - {active && this.getProgress()} - </div> - <div className="buttons"> - <Toolbar id="toolbar-items"> - <ToolbarContent> - {!active && <ToolbarItem> - <Button variant="secondary" isSmall onClick={event => this.onButtonClick("undeploy")}>Undeploy</Button> - </ToolbarItem>} - <ToolbarItem> - <Button variant="primary" isSmall icon={icon} onClick={event => this.onButtonClick(active ? "stop" : "start")}>{label}</Button> - </ToolbarItem> - <ToolbarItem> - <Button variant="primary" isSmall icon={<RunIcon/>} onClick={event => this.onButtonClick("run")}>Run</Button> - </ToolbarItem> - </ToolbarContent> - </Toolbar> - </div> - </div> - } - - getPropertiesForm() { - return ( - <div className="center"> - <div className="center-column"> - <Card className="builder-card" isCompact style={{width: "100%"}}> - {this.getCardHeader("Properties", <ClipboardIcon/>)} - <CardBody> - <PropertiesTable properties={this.state.project.properties} - onChange={properties => this.setState(state => { - state.project.properties = properties; - return state - })}/> - </CardBody> - </Card> - </div> - </div> - ) - } - - render() { - return ( - <PageSection className="project-builder" variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light} - padding={{default: 'noPadding'}}> - <div style={{height: "100%", display: "flex", flexDirection: "column"}}> - <div> - {this.getHeader()} - </div> - <div style={{overflow: "auto", flexGrow: 1}}> - {this.getPropertiesForm()} - </div> - <div> - {this.getFooter()} - </div> - </div> - </PageSection> - ) - } -} \ No newline at end of file diff --git a/karavan-designer/src/builder/FileSelector.tsx b/karavan-designer/src/builder/FileSelector.tsx deleted file mode 100644 index ead44b9..0000000 --- a/karavan-designer/src/builder/FileSelector.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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, - FormGroup, - Checkbox, PopoverPosition, Popover, InputGroup -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; - -interface Props { - label: string - help: string - files: string - filesSelected: string - onChange: (files: string) => void - source: boolean -} - -interface State { - selected: [] -} - -export class FileSelector extends React.Component<Props, State> { - - public state: State = { - selected: [] - }; - - isChecked(file: string) { - const finalFile = this.props.source ? "file:" + file : file; - const s = this.props.filesSelected ? this.props.filesSelected.split(",").map(value => value.trim()) : []; - return s.includes(finalFile); - } - - onChange(file: string, checked: boolean) { - const finalFile = this.props.source ? "file:" + file : file; - const s = this.props.filesSelected.split(",").map(f => f.trim()).filter(f => f.length > 0); - const already = s.includes(finalFile); - if (checked && !already) { - s.push(finalFile); - this.props.onChange?.call(this, s.join(",")); - } else if (!checked) { - const result = s.filter(f => f !== finalFile); - this.props.onChange?.call(this, result.join(",")); - } - } - - getFiles(): string[] { - const allFiles = (this.props.files ? this.props.files.split(",") : []); - if (this.props.source){ - const extensions = ['yaml', 'yml', 'java', 'js', 'kt', 'groovy', 'xml']; - return allFiles.filter(file => { - const extension = file.split(".").pop() || ''; - return extensions.includes(extension); - }).map(file => file.replace("file:", "")) - } - return allFiles; - } - - render() { - const files = this.getFiles(); - return ( - <FormGroup label={this.props.label} fieldId="files"> - <InputGroup> - <div style={{width:"100%"}}> - {files.map(file => { - const key = file + this.props.source; - return <Checkbox key={key} label={file} isChecked={this.isChecked(file)} onChange={checked => this.onChange(file, checked)} id={key} name={key}/> - })} - </div> - <Popover aria-label="files" position={PopoverPosition.left} - bodyContent={this.props.help}> - <Button variant="plain" onClick={e => {}}> - <HelpIcon/> - </Button> - </Popover> - </InputGroup> - </FormGroup> - ) - } -}; \ No newline at end of file diff --git a/karavan-designer/src/builder/ProfileSelector.tsx b/karavan-designer/src/builder/ProfileSelector.tsx deleted file mode 100644 index ef140a4..0000000 --- a/karavan-designer/src/builder/ProfileSelector.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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, - Flex, - FlexItem, Form, FormGroup, InputGroup, Modal, ModalVariant, Tab, Tabs, TextInput, ToggleGroup, ToggleGroupItem, Tooltip, TooltipPosition -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; - -interface Props { - profiles: string[] - profile: string - onChange?: (profile: string) => void - onDelete?: (profile: string) => void -} - -interface State { - isSelectorOpen?: boolean - showDeleteConfirmation?: boolean - showCreate?: boolean - newProfile?: string -} - -export class ProfileSelector extends React.Component<Props, State> { - - public state: State = { - isSelectorOpen: false, - }; - - onSelect(profile?: string){ - if (profile) this.props.onChange?.call(this, profile); - this.setState({isSelectorOpen: false}); - } - - deleteProfile(){ - if (this.props.profile) this.props.onDelete?.call(this, this.props.profile); - this.setState({showDeleteConfirmation: false}); - } - - saveAndCloseCreateModal = () => { - if (this.state.newProfile && this.state.newProfile.length > 0) this.props.onChange?.call(this, this.state.newProfile); - this.closeModal(); - } - - closeModal = () => { - this.setState({showCreate: false, newProfile: undefined}); - } - - onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => { - if (event.key === 'Enter' && this.state.newProfile !== undefined) { - this.saveAndCloseCreateModal(); - } - } - - createModalForm() { - return ( - <Modal - title="Create new profile" - className='profile-modal' - variant={ModalVariant.small} - isOpen={this.state.showCreate} - onClose={this.closeModal} - onKeyDown={this.onKeyDown} - actions={[ - <Button key="confirm" variant="primary" onClick={this.saveAndCloseCreateModal}>Save</Button>, - <Button key="cancel" variant="secondary" onClick={this.closeModal}>Cancel</Button> - ]} - > - <Form isHorizontal> - <FormGroup label="Profile" fieldId="profile" isRequired> - <TextInput className="text-field" type="text" id="profile" name="profile" - value={this.state.newProfile} - onChange={e => this.setState({newProfile: e})}/> - </FormGroup> - </Form> - </Modal> - ) - } - - getDeleteConfirmation() { - return (<Modal - className="modal-delete" - title="Confirmation" - isOpen={this.state.showDeleteConfirmation} - onClose={() => this.setState({showDeleteConfirmation: false})} - actions={[ - <Button key="confirm" variant="primary" onClick={e => this.deleteProfile()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> - ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> - <div>Delete profile {this.props.profile}</div> - </Modal>) - } - - render() { - const profile = this.props.profile; - const tabs = this.props.profiles.map(p => - <ToggleGroupItem key={p} text={p} buttonId={p} isSelected={profile === p} - onChange={selected => selected ? this.onSelect(p) : {}}/> - ); - return ( - <Flex> - <FlexItem> - <p className="profile-caption">Profile:</p> - </FlexItem> - <FlexItem> - <InputGroup> - <Tooltip - aria-label="Add profile" - position={TooltipPosition.bottom} - content="Create new profile"> - <Button variant={"plain"} icon={<AddIcon/>} onClick={event => this.setState({showCreate: true})}/> - </Tooltip> - <ToggleGroup aria-label="Select target"> - {tabs} - </ToggleGroup> - {this.props.profiles.length > 1 && <Tooltip - aria-label="Delete profile" - position={TooltipPosition.bottomEnd} - content="Delete selected profile"> - <Button variant={"plain"} icon={<DeleteIcon/>} onClick={event => this.setState({showDeleteConfirmation: true})}/> - </Tooltip>} - </InputGroup> - </FlexItem> - {this.createModalForm()} - {this.getDeleteConfirmation()} - </Flex> - ) - } -} \ No newline at end of file diff --git a/karavan-designer/src/builder/PropertiesTable.tsx b/karavan-designer/src/builder/PropertiesTable.tsx deleted file mode 100644 index e3f5958..0000000 --- a/karavan-designer/src/builder/PropertiesTable.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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, Flex, FlexItem, - Modal, - PageSection, - Panel, - PanelMain, - PanelMainBody, - TextInput -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; -import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; -import {ProjectProperty} from "karavan-core/lib/model/ProjectModel"; - -interface Props { - properties: ProjectProperty[] - onChange?: (properties: ProjectProperty[]) => void -} - -interface State { - properties: ProjectProperty[] - showDeleteConfirmation: boolean - deleteId?: string -} - -export class PropertiesTable extends React.Component<Props, State> { - - public state: State = { - properties: this.props.properties, - showDeleteConfirmation: false, - }; - - sendUpdate = (props: ProjectProperty[]) => { - this.props.onChange?.call(this, props); - } - - changeProperty(p: ProjectProperty, field: "key" | "value", val?: string) { - const key: string = field === 'key' && val !== undefined ? val : p.key; - const value: any = field === 'value' ? val : p.value; - const property: ProjectProperty = {id: p.id, key: key, value: value}; - const props = this.state.properties.map(prop => prop.id === property.id ? property : prop); - this.setState({properties: props}); - this.sendUpdate(props); - } - - startDelete(id: string) { - this.setState({showDeleteConfirmation: true, deleteId: id}); - } - - confirmDelete() { - const props = this.state.properties.filter(p => p.id !== this.state.deleteId); - this.setState({properties: props, showDeleteConfirmation: false, deleteId: undefined}); - this.sendUpdate(props); - } - - addProperty() { - const props = [...this.state.properties]; - props.push(ProjectProperty.createNew("", "")) - this.setState({properties: props, showDeleteConfirmation: false, deleteId: undefined}); - this.sendUpdate(props); - } - - getDeleteConfirmation() { - return (<Modal - className="modal-delete" - title="Confirmation" - isOpen={this.state.showDeleteConfirmation} - onClose={() => this.setState({showDeleteConfirmation: false})} - actions={[ - <Button key="confirm" variant="primary" onClick={e => this.confirmDelete()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> - ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> - <div>Delete property?</div> - </Modal>) - } - - getTextInputField(property: ProjectProperty, field: "key" | "value", readOnly: boolean) { - return (<TextInput isDisabled={readOnly} isRequired={true} className="text-field" type={"text"} id={"key"} name={"key"} - value={field === "key" ? property.key : property.value} - onChange={val => this.changeProperty(property, field, val)}/>) - } - - render() { - const properties = this.state.properties; - return ( - <PageSection padding={{default: "noPadding"}}> - {properties.length > 0 && - <TableComposable aria-label="Property table" variant='compact' borders={false} - className="project-properties"> - <Thead> - <Tr> - <Th key='name'>Name</Th> - <Th key='value'>Value</Th> - <Td></Td> - </Tr> - </Thead> - <Tbody> - {properties.map((property, idx: number) => { - const readOnly = property.key.startsWith("camel.jbang"); - return ( - <Tr key={property.id}> - <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td> - <Td noPadding width={10} dataLabel="value">{this.getTextInputField(property, "value", readOnly)}</Td> - <Td noPadding isActionCell dataLabel="delete"> - {!readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"} - onClick={event => this.startDelete(property.id)}/>} - </Td> - </Tr> - )})} - </Tbody> - </TableComposable>} - <Panel> - <PanelMain> - <PanelMainBody> - <Flex direction={{default:"row"}} > - <FlexItem align={{ default: 'alignRight' }}> - <Button isInline variant={"primary"} icon={<PlusIcon/>} - className={"add-button"} - onClick={event => this.addProperty()}>Add property</Button> - </FlexItem> - </Flex> - </PanelMainBody> - </PanelMain> - </Panel> - </PageSection> - ) - } -} \ No newline at end of file diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json index 1cc46e5..135ec8e 100644 --- a/karavan-vscode/package.json +++ b/karavan-vscode/package.json @@ -381,7 +381,7 @@ ] }, "scripts": { - "copy-designer": "cp -r ../karavan-designer/src/designer webview && cp -r ../karavan-designer/src/kamelets webview && cp -r ../karavan-designer/src/components webview && cp -r ../karavan-designer/src/eip webview && cp -r ../karavan-designer/src/builder webview", + "copy-designer": "cp -r ../karavan-designer/src/designer webview && cp -r ../karavan-designer/src/kamelets webview && cp -r ../karavan-designer/src/components webview && cp -r ../karavan-designer/src/eip webview", "vscode:prepublish": "npm run copy-designer && npm run package", "compile": "npm run copy-designer && cross-env NODE_ENV=development webpack --progress", "watch": "npm run copy-designer && cross-env NODE_ENV=development webpack --progress --watch", diff --git a/karavan-vscode/webview/builder/BuilderPage.tsx b/karavan-vscode/webview/builder/BuilderPage.tsx deleted file mode 100644 index 43c27d5..0000000 --- a/karavan-vscode/webview/builder/BuilderPage.tsx +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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, - Button, - Card, - CardBody, - CardHeader, - CardHeaderMain, - CardTitle, - Flex, - FlexItem, - Form, - FormGroup, - InputGroup, - PageSection, - PageSectionVariants, - Popover, - PopoverPosition, - ProgressStep, - ProgressStepper, - Spinner, - Text, - TextContent, - TextInput, - Toolbar, - ToolbarContent, - ToolbarItem, -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; -import InProgressIcon from '@patternfly/react-icons/dist/esm/icons/in-progress-icon'; -import AutomationIcon from '@patternfly/react-icons/dist/esm/icons/bundle-icon'; -import PendingIcon from '@patternfly/react-icons/dist/esm/icons/pending-icon'; -import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; -import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; -import ProjectIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon'; -import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; -import RunIcon from '@patternfly/react-icons/dist/esm/icons/play-circle-icon'; -import {ProjectModel, StepStatus} from "karavan-core/lib/model/ProjectModel"; -import {PropertiesTable} from "./PropertiesTable"; - -interface Props { - project: ProjectModel, - dark: boolean - files: string - onChange?: (project: ProjectModel) => void - onAction?: (action: "start" | "stop" | "undeploy" | "run", project: ProjectModel) => void -} - -interface State { - project: ProjectModel, - key?: string, - isOpen?: boolean -} - -export class BuilderPage extends React.Component<Props, State> { - - public state: State = { - project: this.props.project, - }; - interval: any; - - componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { - const project = this.state.project; - if (project) this.props.onChange?.call(this, project); - } - - componentDidMount() { - this.interval = setInterval(() => this.setState(state => ({key: Math.random().toString()})), 1000); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - getHelp(text: string) { - return <Popover - aria-label={text} - position={PopoverPosition.left} - bodyContent={text}> - <Button variant="plain" onClick={e => { - }}> - <HelpIcon/> - </Button> - </Popover> - } - - getField(name: string, label: string, type: 'text' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' | 'time' | 'url', - value: any, help: string, onChange: (val: any) => void, isRequired: boolean = false, enabled: boolean = true) { - return <FormGroup label={label} fieldId={name} isRequired={isRequired}> - <InputGroup> - <TextInput isRequired={isRequired} isDisabled={!enabled} className="text-field" type={type} id={name} name={name} value={value} - onChange={val => onChange?.call(this, val)}/> - {this.getHelp(help)} - </InputGroup> - </FormGroup> - } - - getCardHeader(title: string, icon: any) { - return <CardHeader> - <CardHeaderMain> - <CardTitle className="card-header"> - {icon}{title} - </CardTitle> - </CardHeaderMain> - </CardHeader> - } - - getProjectForm() { - return ( - <Card className="builder-card" isCompact style={{width: "100%"}}> - {this.getCardHeader("Artifact", <ProjectIcon/>)} - <CardBody> - <Form isHorizontal> - {/*{this.getField("name", "Name", "text", this.state.profile.project.name, "Project name",*/} - {/* val => this.setState(state => {state.profile.project.name= val; return state}), true)}*/} - {/*{this.getField("version", "Version", "text", this.state.profile.project.version, "Project version",*/} - {/* val => this.setState(state => {state.profile.project.version= val; return state}), true)}*/} - </Form> - </CardBody> - </Card> - ) - } - - getProgressIcon(status?: 'pending' | 'progress' | 'done' | 'error') { - switch (status) { - case "pending": - return <PendingIcon/>; - case "progress": - return <Spinner isSVG size="md"/> - case "done": - return <CheckCircleIcon/>; - case "error": - return <ExclamationCircleIcon/>; - default: - return undefined; - } - } - - getDescription(stepStatus?: StepStatus) { - const now = Date.now(); - let time = 0; - if (stepStatus?.status === 'progress') { - time = stepStatus?.startTime ? (now - stepStatus.startTime) / 1000 : 0; - } else if (stepStatus?.status === 'done' && stepStatus?.endTime) { - time = (stepStatus?.endTime - stepStatus.startTime) / 1000 - } - return time === 0 ? "" : Math.round(time) + "s"; - } - - getProgress() { - const {status} = this.state.project; - return ( - <ProgressStepper isCenterAligned style={{visibility: "visible"}}> - <ProgressStep variant="pending" id="export" titleId="export" aria-label="export" - description={this.getDescription(status.export)} - icon={this.getProgressIcon(status.export?.status)}>Export - </ProgressStep> - <ProgressStep variant="pending" isCurrent id="package" titleId="package" aria-label="package" - description={this.getDescription(status.package)} - icon={this.getProgressIcon(status.package?.status)}>Package - </ProgressStep> - </ProgressStepper> - ) - } - - getHeader() { - return ( - <PageSection className="tools-section" variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> - <Flex className="tools" direction={{default: 'row'}} justifyContent={{default: 'justifyContentSpaceBetween'}} spaceItems={{default: 'spaceItemsLg'}}> - <FlexItem> - <TextContent className="header"> - <Text component="h2">Build Runner</Text> - <Badge isRead className="labels">Powered by Camel JBang & Maven</Badge> - </TextContent> - </FlexItem> - <FlexItem> - <Toolbar id="toolbar-group-types"> - <ToolbarContent> - <ToolbarItem> - {/*<ProfileSelector profiles={profiles.map(p => p.name)}*/} - {/* profile={profile.name}*/} - {/* onDelete={profile => {*/} - {/* this.setState(state => {*/} - {/* state.profiles.splice(state.profiles.findIndex(p => p.name === profile), 1);*/} - {/* return {*/} - {/* profiles: state.profiles,*/} - {/* profile: this.props.profiles.at(0) || Profile.createNew("application"),*/} - {/* tab: state.tab*/} - {/* };*/} - {/* })*/} - {/* }}*/} - {/* onChange={profileName => {*/} - {/* const prof = profiles.find(p => p.name === profileName);*/} - {/* if (prof) {*/} - {/* this.setState({profile: prof, key: Math.random().toString()});*/} - {/* } else {*/} - {/* this.setState(state => {*/} - {/* const newProfile = Profile.createNew(profileName);*/} - {/* newProfile.project = new ProjectModel(this.state.profile.project);*/} - {/* state.profiles.push(newProfile);*/} - {/* return {profiles: state.profiles, profile: newProfile, tab: state.tab};*/} - {/* })*/} - {/* }*/} - {/* }}/>*/} - </ToolbarItem> - </ToolbarContent> - </Toolbar> - </FlexItem> - </Flex> - </PageSection> - ) - } - - onButtonClick(action: "start" | "stop" | "undeploy" | "run") { - this.props.onAction?.call(this, action, this.state.project); - } - - getFooter() { - const active = false; - const label = active ? "Stop" : "Package"; - const icon = active ? <InProgressIcon/> : <AutomationIcon/>; - return <div key={this.state.key} className="footer"> - <div className="progress"> - {active && this.getProgress()} - </div> - <div className="buttons"> - <Toolbar id="toolbar-items"> - <ToolbarContent> - {!active && <ToolbarItem> - <Button variant="secondary" isSmall onClick={event => this.onButtonClick("undeploy")}>Undeploy</Button> - </ToolbarItem>} - <ToolbarItem> - <Button variant="primary" isSmall icon={icon} onClick={event => this.onButtonClick(active ? "stop" : "start")}>{label}</Button> - </ToolbarItem> - <ToolbarItem> - <Button variant="primary" isSmall icon={<RunIcon/>} onClick={event => this.onButtonClick("run")}>Run</Button> - </ToolbarItem> - </ToolbarContent> - </Toolbar> - </div> - </div> - } - - getPropertiesForm() { - return ( - <div className="center"> - <div className="center-column"> - <Card className="builder-card" isCompact style={{width: "100%"}}> - {this.getCardHeader("Properties", <ClipboardIcon/>)} - <CardBody> - <PropertiesTable properties={this.state.project.properties} - onChange={properties => this.setState(state => { - state.project.properties = properties; - return state - })}/> - </CardBody> - </Card> - </div> - </div> - ) - } - - render() { - return ( - <PageSection className="project-builder" variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light} - padding={{default: 'noPadding'}}> - <div style={{height: "100%", display: "flex", flexDirection: "column"}}> - <div> - {this.getHeader()} - </div> - <div style={{overflow: "auto", flexGrow: 1}}> - {this.getPropertiesForm()} - </div> - <div> - {this.getFooter()} - </div> - </div> - </PageSection> - ) - } -} \ No newline at end of file diff --git a/karavan-vscode/webview/builder/FileSelector.tsx b/karavan-vscode/webview/builder/FileSelector.tsx deleted file mode 100644 index ead44b9..0000000 --- a/karavan-vscode/webview/builder/FileSelector.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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, - FormGroup, - Checkbox, PopoverPosition, Popover, InputGroup -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; - -interface Props { - label: string - help: string - files: string - filesSelected: string - onChange: (files: string) => void - source: boolean -} - -interface State { - selected: [] -} - -export class FileSelector extends React.Component<Props, State> { - - public state: State = { - selected: [] - }; - - isChecked(file: string) { - const finalFile = this.props.source ? "file:" + file : file; - const s = this.props.filesSelected ? this.props.filesSelected.split(",").map(value => value.trim()) : []; - return s.includes(finalFile); - } - - onChange(file: string, checked: boolean) { - const finalFile = this.props.source ? "file:" + file : file; - const s = this.props.filesSelected.split(",").map(f => f.trim()).filter(f => f.length > 0); - const already = s.includes(finalFile); - if (checked && !already) { - s.push(finalFile); - this.props.onChange?.call(this, s.join(",")); - } else if (!checked) { - const result = s.filter(f => f !== finalFile); - this.props.onChange?.call(this, result.join(",")); - } - } - - getFiles(): string[] { - const allFiles = (this.props.files ? this.props.files.split(",") : []); - if (this.props.source){ - const extensions = ['yaml', 'yml', 'java', 'js', 'kt', 'groovy', 'xml']; - return allFiles.filter(file => { - const extension = file.split(".").pop() || ''; - return extensions.includes(extension); - }).map(file => file.replace("file:", "")) - } - return allFiles; - } - - render() { - const files = this.getFiles(); - return ( - <FormGroup label={this.props.label} fieldId="files"> - <InputGroup> - <div style={{width:"100%"}}> - {files.map(file => { - const key = file + this.props.source; - return <Checkbox key={key} label={file} isChecked={this.isChecked(file)} onChange={checked => this.onChange(file, checked)} id={key} name={key}/> - })} - </div> - <Popover aria-label="files" position={PopoverPosition.left} - bodyContent={this.props.help}> - <Button variant="plain" onClick={e => {}}> - <HelpIcon/> - </Button> - </Popover> - </InputGroup> - </FormGroup> - ) - } -}; \ No newline at end of file diff --git a/karavan-vscode/webview/builder/ProfileSelector.tsx b/karavan-vscode/webview/builder/ProfileSelector.tsx deleted file mode 100644 index ef140a4..0000000 --- a/karavan-vscode/webview/builder/ProfileSelector.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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, - Flex, - FlexItem, Form, FormGroup, InputGroup, Modal, ModalVariant, Tab, Tabs, TextInput, ToggleGroup, ToggleGroupItem, Tooltip, TooltipPosition -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; - -interface Props { - profiles: string[] - profile: string - onChange?: (profile: string) => void - onDelete?: (profile: string) => void -} - -interface State { - isSelectorOpen?: boolean - showDeleteConfirmation?: boolean - showCreate?: boolean - newProfile?: string -} - -export class ProfileSelector extends React.Component<Props, State> { - - public state: State = { - isSelectorOpen: false, - }; - - onSelect(profile?: string){ - if (profile) this.props.onChange?.call(this, profile); - this.setState({isSelectorOpen: false}); - } - - deleteProfile(){ - if (this.props.profile) this.props.onDelete?.call(this, this.props.profile); - this.setState({showDeleteConfirmation: false}); - } - - saveAndCloseCreateModal = () => { - if (this.state.newProfile && this.state.newProfile.length > 0) this.props.onChange?.call(this, this.state.newProfile); - this.closeModal(); - } - - closeModal = () => { - this.setState({showCreate: false, newProfile: undefined}); - } - - onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => { - if (event.key === 'Enter' && this.state.newProfile !== undefined) { - this.saveAndCloseCreateModal(); - } - } - - createModalForm() { - return ( - <Modal - title="Create new profile" - className='profile-modal' - variant={ModalVariant.small} - isOpen={this.state.showCreate} - onClose={this.closeModal} - onKeyDown={this.onKeyDown} - actions={[ - <Button key="confirm" variant="primary" onClick={this.saveAndCloseCreateModal}>Save</Button>, - <Button key="cancel" variant="secondary" onClick={this.closeModal}>Cancel</Button> - ]} - > - <Form isHorizontal> - <FormGroup label="Profile" fieldId="profile" isRequired> - <TextInput className="text-field" type="text" id="profile" name="profile" - value={this.state.newProfile} - onChange={e => this.setState({newProfile: e})}/> - </FormGroup> - </Form> - </Modal> - ) - } - - getDeleteConfirmation() { - return (<Modal - className="modal-delete" - title="Confirmation" - isOpen={this.state.showDeleteConfirmation} - onClose={() => this.setState({showDeleteConfirmation: false})} - actions={[ - <Button key="confirm" variant="primary" onClick={e => this.deleteProfile()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> - ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> - <div>Delete profile {this.props.profile}</div> - </Modal>) - } - - render() { - const profile = this.props.profile; - const tabs = this.props.profiles.map(p => - <ToggleGroupItem key={p} text={p} buttonId={p} isSelected={profile === p} - onChange={selected => selected ? this.onSelect(p) : {}}/> - ); - return ( - <Flex> - <FlexItem> - <p className="profile-caption">Profile:</p> - </FlexItem> - <FlexItem> - <InputGroup> - <Tooltip - aria-label="Add profile" - position={TooltipPosition.bottom} - content="Create new profile"> - <Button variant={"plain"} icon={<AddIcon/>} onClick={event => this.setState({showCreate: true})}/> - </Tooltip> - <ToggleGroup aria-label="Select target"> - {tabs} - </ToggleGroup> - {this.props.profiles.length > 1 && <Tooltip - aria-label="Delete profile" - position={TooltipPosition.bottomEnd} - content="Delete selected profile"> - <Button variant={"plain"} icon={<DeleteIcon/>} onClick={event => this.setState({showDeleteConfirmation: true})}/> - </Tooltip>} - </InputGroup> - </FlexItem> - {this.createModalForm()} - {this.getDeleteConfirmation()} - </Flex> - ) - } -} \ No newline at end of file diff --git a/karavan-vscode/webview/builder/PropertiesTable.tsx b/karavan-vscode/webview/builder/PropertiesTable.tsx deleted file mode 100644 index e3f5958..0000000 --- a/karavan-vscode/webview/builder/PropertiesTable.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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, Flex, FlexItem, - Modal, - PageSection, - Panel, - PanelMain, - PanelMainBody, - TextInput -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; -import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; -import {ProjectProperty} from "karavan-core/lib/model/ProjectModel"; - -interface Props { - properties: ProjectProperty[] - onChange?: (properties: ProjectProperty[]) => void -} - -interface State { - properties: ProjectProperty[] - showDeleteConfirmation: boolean - deleteId?: string -} - -export class PropertiesTable extends React.Component<Props, State> { - - public state: State = { - properties: this.props.properties, - showDeleteConfirmation: false, - }; - - sendUpdate = (props: ProjectProperty[]) => { - this.props.onChange?.call(this, props); - } - - changeProperty(p: ProjectProperty, field: "key" | "value", val?: string) { - const key: string = field === 'key' && val !== undefined ? val : p.key; - const value: any = field === 'value' ? val : p.value; - const property: ProjectProperty = {id: p.id, key: key, value: value}; - const props = this.state.properties.map(prop => prop.id === property.id ? property : prop); - this.setState({properties: props}); - this.sendUpdate(props); - } - - startDelete(id: string) { - this.setState({showDeleteConfirmation: true, deleteId: id}); - } - - confirmDelete() { - const props = this.state.properties.filter(p => p.id !== this.state.deleteId); - this.setState({properties: props, showDeleteConfirmation: false, deleteId: undefined}); - this.sendUpdate(props); - } - - addProperty() { - const props = [...this.state.properties]; - props.push(ProjectProperty.createNew("", "")) - this.setState({properties: props, showDeleteConfirmation: false, deleteId: undefined}); - this.sendUpdate(props); - } - - getDeleteConfirmation() { - return (<Modal - className="modal-delete" - title="Confirmation" - isOpen={this.state.showDeleteConfirmation} - onClose={() => this.setState({showDeleteConfirmation: false})} - actions={[ - <Button key="confirm" variant="primary" onClick={e => this.confirmDelete()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> - ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> - <div>Delete property?</div> - </Modal>) - } - - getTextInputField(property: ProjectProperty, field: "key" | "value", readOnly: boolean) { - return (<TextInput isDisabled={readOnly} isRequired={true} className="text-field" type={"text"} id={"key"} name={"key"} - value={field === "key" ? property.key : property.value} - onChange={val => this.changeProperty(property, field, val)}/>) - } - - render() { - const properties = this.state.properties; - return ( - <PageSection padding={{default: "noPadding"}}> - {properties.length > 0 && - <TableComposable aria-label="Property table" variant='compact' borders={false} - className="project-properties"> - <Thead> - <Tr> - <Th key='name'>Name</Th> - <Th key='value'>Value</Th> - <Td></Td> - </Tr> - </Thead> - <Tbody> - {properties.map((property, idx: number) => { - const readOnly = property.key.startsWith("camel.jbang"); - return ( - <Tr key={property.id}> - <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td> - <Td noPadding width={10} dataLabel="value">{this.getTextInputField(property, "value", readOnly)}</Td> - <Td noPadding isActionCell dataLabel="delete"> - {!readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"} - onClick={event => this.startDelete(property.id)}/>} - </Td> - </Tr> - )})} - </Tbody> - </TableComposable>} - <Panel> - <PanelMain> - <PanelMainBody> - <Flex direction={{default:"row"}} > - <FlexItem align={{ default: 'alignRight' }}> - <Button isInline variant={"primary"} icon={<PlusIcon/>} - className={"add-button"} - onClick={event => this.addProperty()}>Add property</Button> - </FlexItem> - </Flex> - </PanelMainBody> - </PanelMain> - </Panel> - </PageSection> - ) - } -} \ No newline at end of file