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 b6b4fc3  OpenAPi rest generator UI
b6b4fc3 is described below

commit b6b4fc3e91b36d2f0c71e6ceb6bcf176b4f1c0f8
Author: Marat Gubaidullin <marat.gubaidul...@gmail.com>
AuthorDate: Wed Nov 16 17:48:54 2022 -0500

    OpenAPi rest generator UI
---
 karavan-space/src/App.tsx                          |  30 +++--
 karavan-space/src/api/GeneratorApi.tsx             |  19 ++++
 karavan-space/src/{ => space}/GithubModal.tsx      |  24 ++--
 karavan-space/src/space/SpaceBus.ts                |  41 +++++++
 .../{SpaceDesignerPage.tsx => space/SpacePage.tsx} |  34 +++++-
 karavan-space/src/space/UploadModal.tsx            | 125 +++++++++++++++++++++
 6 files changed, 245 insertions(+), 28 deletions(-)

diff --git a/karavan-space/src/App.tsx b/karavan-space/src/App.tsx
index 1b598d7..9f12428 100644
--- a/karavan-space/src/App.tsx
+++ b/karavan-space/src/App.tsx
@@ -32,8 +32,11 @@ import EipIcon from 
"@patternfly/react-icons/dist/js/icons/topology-icon";
 import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
 import {KaravanIcon} from "./designer/utils/KaravanIcons";
 import './designer/karavan.css';
-import {SpaceDesignerPage} from "./SpaceDesignerPage";
-import {GithubModal} from "./GithubModal";
+import {SpacePage} from "./space/SpacePage";
+import {GithubModal} from "./space/GithubModal";
+import {Subscription} from "rxjs";
+import {DslPosition, EventBus} from "./designer/utils/EventBus";
+import {AlertMessage, SpaceBus} from "./space/SpaceBus";
 
 class ToastMessage {
     id: string = ''
@@ -72,6 +75,7 @@ interface State {
     githubModalIsOpen: boolean,
     pageId: string,
     alerts: ToastMessage[],
+    sub?: Subscription
 }
 
 class App extends React.Component<Props, State> {
@@ -96,6 +100,8 @@ class App extends React.Component<Props, State> {
     }
 
     componentDidMount() {
+        const sub = SpaceBus.onAlert()?.subscribe((evt: AlertMessage) => 
this.toast(evt.title, evt.message, evt.variant));
+        this.setState({sub: sub});
         Promise.all([
             fetch("kamelets/kamelets.yaml"),
             fetch("components/components.json")
@@ -119,16 +125,17 @@ class App extends React.Component<Props, State> {
         );
     }
 
+    componentWillUnmount() {
+        this.state.sub?.unsubscribe();
+    }
+
     save(filename: string, yaml: string, propertyOnly: boolean) {
         this.setState({name: filename, yaml: yaml});
         // console.log(yaml);
     }
 
-    closeGithubModal(close: boolean, toast: boolean, ok: boolean, message: 
string) {
-        this.setState({githubModalIsOpen: !close})
-        if (toast){
-            this.toast(ok ? "Success" : "Error", message, ok?'success' : 
'danger');
-        }
+    closeGithubModal() {
+        this.setState({githubModalIsOpen: false})
     }
 
     openGithubModal() {
@@ -181,7 +188,7 @@ class App extends React.Component<Props, State> {
         switch (pageId) {
             case "designer":
                 return (
-                    <SpaceDesignerPage
+                    <SpacePage
                         name={name}
                         yaml={yaml}
                         onSave={(filename, yaml1, propertyOnly) => 
this.save(filename, yaml1, propertyOnly)}
@@ -204,9 +211,9 @@ class App extends React.Component<Props, State> {
     }
 
     render() {
-        const {key, loaded, githubModalIsOpen, yaml, name} = this.state;
+        const {loaded, githubModalIsOpen, yaml, name} = this.state;
         return (
-            <Page key={key} className="karavan">
+            <Page className="karavan">
                 <AlertGroup isToast isLiveRegion>
                     {this.state.alerts.map((e: ToastMessage) => (
                         <Alert key={e.id} className="main-alert" 
variant={e.variant} title={e.title}
@@ -227,8 +234,7 @@ class App extends React.Component<Props, State> {
                             {loaded !== true && this.getSpinner()}
                             {loaded === true && this.getDesigner()}
                             {loaded === true && githubModalIsOpen &&
-                                <GithubModal yaml={yaml} filename={name} 
isOpen={githubModalIsOpen}
-                                             onClose={(close: boolean, t: 
boolean, ok, message) => this.closeGithubModal(close, t, ok, message)}/>}
+                                <GithubModal yaml={yaml} filename={name} 
isOpen={githubModalIsOpen} onClose={this.closeGithubModal}/>}
                         </FlexItem>
                     </Flex>
                 </>
diff --git a/karavan-space/src/api/GeneratorApi.tsx 
b/karavan-space/src/api/GeneratorApi.tsx
new file mode 100644
index 0000000..61627ff
--- /dev/null
+++ b/karavan-space/src/api/GeneratorApi.tsx
@@ -0,0 +1,19 @@
+export class GeneratorApi {
+
+    static async generate(filename: string, data: string) {
+        const response = await 
fetch("https://kameleon.dev/generator/openapi?filename="+ filename, {
+            method: 'POST',
+            mode: 'cors', // no-cors, *cors, same-origin
+            cache: 'no-cache',
+            credentials: 'same-origin',
+            headers: {
+                'Content-Type': filename.endsWith("json") ? 'application/json' 
: 'application/yaml'
+            },
+            redirect: 'follow',
+            referrerPolicy: 'no-referrer',
+            body: data
+        });
+        return response.text();
+    }
+
+}
diff --git a/karavan-space/src/GithubModal.tsx 
b/karavan-space/src/space/GithubModal.tsx
similarity index 91%
rename from karavan-space/src/GithubModal.tsx
rename to karavan-space/src/space/GithubModal.tsx
index 911bbbb..23c80da 100644
--- a/karavan-space/src/GithubModal.tsx
+++ b/karavan-space/src/space/GithubModal.tsx
@@ -7,18 +7,19 @@ import {
     Form,
     TextInputGroupMain, TextInputGroup, Switch, FlexItem, Flex, TextInput
 } from '@patternfly/react-core';
-import './designer/karavan.css';
-import {GithubApi, GithubParams} from "./api/GithubApi";
+import '../designer/karavan.css';
+import {GithubApi, GithubParams} from "../api/GithubApi";
 import GithubImageIcon from 
"@patternfly/react-icons/dist/esm/icons/github-icon";
-import {StorageApi} from "./api/StorageApi";
-import {KameletApi} from "../../karavan-core/lib/api/KameletApi";
-import {ComponentApi} from "../../karavan-core/lib/api/ComponentApi";
+import {StorageApi} from "../api/StorageApi";
+import {KameletApi} from "../../../karavan-core/lib/api/KameletApi";
+import {ComponentApi} from "../../../karavan-core/lib/api/ComponentApi";
+import {SpaceBus} from "./SpaceBus";
 
 interface Props {
     yaml: string,
     filename: string,
     isOpen: boolean,
-    onClose: (close: boolean, toast: boolean, ok: boolean, message: string) => 
void
+    onClose: () => void
 }
 
 interface State {
@@ -77,7 +78,7 @@ export class GithubModal extends React.Component<Props, 
State> {
                 }
             },
             reason => {
-                this.props.onClose?.call(this, false, true, false, 
reason?.toString());
+                SpaceBus.sendAlert('Error', reason.toString(), 'danger');
             });
     }
 
@@ -93,12 +94,12 @@ export class GithubModal extends React.Component<Props, 
State> {
             const email: string = (Array.isArray(data[1]) ? 
Array.from(data[1]).filter(d => d.primary === true)?.at(0)?.email : '') || '';
             this.setState({token: token, name: name, email:email, owner: 
login})
         }).catch(err =>
-            this.props.onClose?.call(this, false, true, false, err?.toString())
+            SpaceBus.sendAlert('Error', err.toString(), 'danger')
         );
     }
 
     closeModal = () => {
-        this.props.onClose?.call(this, true, false, true, '');
+        this.props.onClose?.call(this);
     }
 
     saveAndCloseModal = () => {
@@ -122,10 +123,11 @@ export class GithubModal extends React.Component<Props, 
State> {
                 token, this.props.yaml,
                 result => {
                     this.setState({pushing: false});
-                    this.props.onClose?.call(this, true, true, true, 'Saved')
+                    SpaceBus.sendAlert('Success', "Saved");
+                    this.props.onClose?.call(this)
                 },
                 reason => {
-                    this.props.onClose?.call(this, false, true, false, 
reason.toString())
+                    SpaceBus.sendAlert('Error', reason.toString(), 'danger');
                     this.setState({pushing: false});
                 }
             )
diff --git a/karavan-space/src/space/SpaceBus.ts 
b/karavan-space/src/space/SpaceBus.ts
new file mode 100644
index 0000000..2325749
--- /dev/null
+++ b/karavan-space/src/space/SpaceBus.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 {Subject} from 'rxjs';
+
+const alerts = new Subject<AlertMessage>();
+
+export class AlertMessage {
+    title: string;
+    message: string;
+    variant: 'success' | 'danger' | 'warning' | 'info' | 'default';
+
+
+    constructor(title: string, message: string, variant: "success" | "danger" 
| "warning" | "info" | "default") {
+        this.title = title;
+        this.message = message;
+        this.variant = variant;
+    }
+}
+
+export const SpaceBus = {
+    sendAlert: (
+        title: string,
+        message: string,
+        variant: "success" | "danger" | "warning" | "info" | "default" = 
'success'
+    ) => alerts.next(new AlertMessage(title, message, variant)),
+    onAlert: () => alerts.asObservable(),
+}
diff --git a/karavan-space/src/SpaceDesignerPage.tsx 
b/karavan-space/src/space/SpacePage.tsx
similarity index 83%
rename from karavan-space/src/SpaceDesignerPage.tsx
rename to karavan-space/src/space/SpacePage.tsx
index ebdbace..1cb9d7a 100644
--- a/karavan-space/src/SpaceDesignerPage.tsx
+++ b/karavan-space/src/space/SpacePage.tsx
@@ -21,12 +21,14 @@ import {
     ToolbarItem,
     PageSection, TextContent, Text, PageSectionVariants, Flex, FlexItem, 
Badge, Button, Tooltip, ToggleGroup, ToggleGroupItem
 } from '@patternfly/react-core';
-import './designer/karavan.css';
+import '../designer/karavan.css';
 import DownloadIcon from 
"@patternfly/react-icons/dist/esm/icons/download-icon";
 import DownloadImageIcon from 
"@patternfly/react-icons/dist/esm/icons/image-icon";
 import GithubImageIcon from 
"@patternfly/react-icons/dist/esm/icons/github-icon";
-import {KaravanDesigner} from "./designer/KaravanDesigner";
+import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
+import {KaravanDesigner} from "../designer/KaravanDesigner";
 import Editor from "@monaco-editor/react";
+import {UploadModal} from "./UploadModal";
 
 interface Props {
     name: string,
@@ -37,19 +39,24 @@ interface Props {
 }
 
 interface State {
+    key: string,
     karavanDesignerRef: any,
+    showUploadModal: boolean,
     mode: "design" | "code",
 }
 
-export class SpaceDesignerPage extends React.Component<Props, State> {
+export class SpacePage extends React.Component<Props, State> {
 
     public state: State = {
+        key: Math.random().toString(),
         karavanDesignerRef: React.createRef(),
         mode: "design",
+        showUploadModal: false
     }
 
     save(filename: string, yaml: string, propertyOnly: boolean) {
         this.props.onSave?.call(this, filename, yaml, propertyOnly);
+        this.setState({key: Math.random().toString()})
     }
 
     download = () => {
@@ -72,10 +79,20 @@ export class SpaceDesignerPage extends 
React.Component<Props, State> {
         this.props.onPush?.call(this, 'github');
     }
 
+    openUploadModal = () => {
+        this.setState({showUploadModal: true})
+    }
+
+    addYaml = (yaml: string | undefined) => {
+        this.setState({showUploadModal: false });
+        this.save(this.props.name, this.props.yaml + "\n" + yaml, false);
+    }
+
     getDesigner = () => {
         const {name, yaml} = this.props;
         return (
             <KaravanDesigner
+                key={this.state.key}
                 dark={this.props.dark}
                 ref={this.state.karavanDesignerRef}
                 filename={name}
@@ -104,7 +121,7 @@ export class SpaceDesignerPage extends 
React.Component<Props, State> {
 
 
     render() {
-        const {mode} = this.state;
+        const {mode, showUploadModal} = this.state;
         return (
             <PageSection className="kamelet-section designer-page" 
padding={{default: 'noPadding'}}>
                 <PageSection className="tools-section" padding={{default: 
'noPadding'}}
@@ -151,6 +168,13 @@ export class SpaceDesignerPage extends 
React.Component<Props, State> {
                                             </Button>
                                         </Tooltip>
                                     </ToolbarItem>
+                                    <ToolbarItem>
+                                        <Tooltip content="Upload OpenAPI" 
position={"bottom"}>
+                                            <Button variant="secondary" 
icon={<UploadIcon/>} onClick={e => this.openUploadModal()}>
+                                                OpenAPI
+                                            </Button>
+                                        </Tooltip>
+                                    </ToolbarItem>
                                 </ToolbarContent>
                             </Toolbar>
                         </FlexItem>
@@ -158,7 +182,7 @@ export class SpaceDesignerPage extends 
React.Component<Props, State> {
                 </PageSection>
                 {mode === 'design' && this.getDesigner()}
                 {mode === 'code' && this.getEditor()}
-
+                <UploadModal isOpen={showUploadModal} onClose={yaml => 
this.addYaml(yaml)}/>
             </PageSection>
         );
     }
diff --git a/karavan-space/src/space/UploadModal.tsx 
b/karavan-space/src/space/UploadModal.tsx
new file mode 100644
index 0000000..1177d06
--- /dev/null
+++ b/karavan-space/src/space/UploadModal.tsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import {
+    TextInput,
+    Button, Modal, FormGroup, ModalVariant, Switch, Form, FileUpload, Radio
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {GeneratorApi} from "../api/GeneratorApi";
+import {SpaceBus} from "./SpaceBus";
+
+interface Props {
+    isOpen: boolean,
+    onClose: (yaml: string | undefined) => void
+}
+
+interface State {
+    data: string
+    filename: string
+    isLoading: boolean
+    isRejected: boolean
+    generateRest: boolean
+    generateRoutes: boolean
+    generating: boolean
+}
+
+export class UploadModal extends React.Component<Props, State> {
+
+    public state: State = {
+        data: '',
+        filename: '',
+        isLoading: false,
+        isRejected: false,
+        generateRest: true,
+        generateRoutes: true,
+        generating: false
+    };
+
+    closeModal = (yaml: string | undefined) => {
+        this.props.onClose?.call(this, yaml);
+    }
+
+    saveAndCloseModal = () => {
+        this.setState({generating: true});
+        const {filename, data} = this.state;
+        GeneratorApi.generate(filename, data).then(value => {
+            SpaceBus.sendAlert('Success', 'Generated REST DSL');
+            this.setState({generating: false});
+            this.closeModal(value);
+        }).catch(reason => {
+            SpaceBus.sendAlert('Error', reason.toString(), 'danger');
+            this.setState({generating: false});
+        })
+    }
+
+    handleFileInputChange = (event: React.ChangeEvent<HTMLInputElement> | 
React.DragEvent<HTMLElement>, file: File) => this.setState({filename: 
file.name});
+    handleFileReadStarted = (fileHandle: File) => this.setState({isLoading: 
true});
+    handleFileReadFinished = (fileHandle: File) => this.setState({isLoading: 
false});
+    handleTextOrDataChange = (data: string) => this.setState({data: data});
+    handleFileRejected = (acceptedOrRejected: File[], event: 
React.DragEvent<HTMLElement>) => this.setState({isRejected: true});
+    handleClear = (event: React.MouseEvent<HTMLButtonElement>) => 
this.setState({
+        filename: '',
+        data: '',
+        isRejected: false
+    });
+
+
+    render() {
+        const {generating} = this.state;
+        const fileNotUploaded = (this.state.filename === '' || this.state.data 
=== '');
+        const isDisabled = fileNotUploaded || generating;
+        const accept = '.json, .yaml';
+        return (
+            <Modal
+                title="Upload OpenAPI"
+                variant={ModalVariant.small}
+                isOpen={this.props.isOpen}
+                onClose={() => this.closeModal(undefined)}
+                actions={[
+                    <Button isLoading={generating} key="confirm" 
variant="primary" onClick={this.saveAndCloseModal} 
isDisabled={isDisabled}>Save</Button>,
+                    <Button key="cancel" variant="secondary" onClick={event => 
this.closeModal(undefined)}>Cancel</Button>
+                ]}
+            >
+                <Form>
+                    <FormGroup fieldId="upload">
+                        <FileUpload
+                            id="file-upload"
+                            value={this.state.data}
+                            filename={this.state.filename}
+                            type="text"
+                            hideDefaultPreview
+                            browseButtonText="Upload"
+                            isLoading={this.state.isLoading}
+                            onFileInputChange={this.handleFileInputChange}
+                            onDataChange={data => 
this.handleTextOrDataChange(data)}
+                            onTextChange={text => 
this.handleTextOrDataChange(text)}
+                            onReadStarted={this.handleFileReadStarted}
+                            onReadFinished={this.handleFileReadFinished}
+                            allowEditingUploadedText={false}
+                            onClearClick={this.handleClear}
+                            dropzoneProps={{accept: accept, onDropRejected: 
this.handleFileRejected}}
+                            validated={this.state.isRejected ? 'error' : 
'default'}
+                        />
+                    </FormGroup>
+                    {/*<FormGroup fieldId="generateRest">*/}
+                    {/*    <Switch*/}
+                    {/*        id="generate-rest"*/}
+                    {/*        label="Generate REST DSL"*/}
+                    {/*        labelOff="Do not generate REST DSL"*/}
+                    {/*        isChecked={this.state.generateRest}*/}
+                    {/*        onChange={checked => 
this.setState({generateRest: checked})}*/}
+                    {/*    />*/}
+                    {/*</FormGroup>*/}
+                    {/*{this.state.generateRest && <FormGroup 
fieldId="generateRoutes">*/}
+                    {/*    <Switch*/}
+                    {/*        id="generate-routes"*/}
+                    {/*        label="Generate Routes"*/}
+                    {/*        labelOff="Do not generate Routes"*/}
+                    {/*        isChecked={this.state.generateRoutes}*/}
+                    {/*        onChange={checked => 
this.setState({generateRoutes: checked})}*/}
+                    {/*    />*/}
+                    {/*</FormGroup>}*/}
+                </Form>
+            </Modal>
+        )
+    }
+};
\ No newline at end of file

Reply via email to