karavan-vscode/webview/App.tsx (255 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) xunder 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 * as React from "react";
import {
Page, PageSection, Spinner, Text, TextVariants
} from "@patternfly/react-core";
import { KaravanDesigner } from "./designer/KaravanDesigner";
import vscode from "./vscode";
import { KameletApi } from "core/api/KameletApi";
import { ComponentApi } from "core/api/ComponentApi";
import { TemplateApi } from "./core/api/TemplateApi";
import { EventBus } from "./designer/utils/EventBus";
import { KnowledgebasePage } from "./knowledgebase/KnowledgebasePage";
import { TopologyTab } from "./topology/TopologyTab";
import {BeanFactoryDefinition} from "core/model/CamelDefinition";
import { IntegrationFile } from "core/model/IntegrationDefinition";
interface Props {
dark: boolean
}
interface State {
filename: string
relativePath: string
fullPath: string
yaml: string
key: string
loaded: boolean
loadingMessages: string[]
interval?: NodeJS.Timeout
scheduledYaml: string
hasChanges: boolean
page: "designer" | "knowledgebase" | 'topology'
active: boolean
tab?: "routes" | "rest" | "beans"
files: IntegrationFile[],
propertyPlaceholders: string[],
beans: BeanFactoryDefinition[]
}
class App extends React.Component<Props, State> {
public state: State = {
filename: '',
relativePath: '',
fullPath: '',
yaml: '',
key: '',
loaded: false,
loadingMessages: [],
scheduledYaml: '',
hasChanges: false,
page: "designer",
active: false,
files: [],
propertyPlaceholders: [],
beans: []
};
saveScheduledChanges = () => {
if (this.state.active && this.state.hasChanges) {
this.save(this.state.relativePath, this.state.scheduledYaml, false);
}
}
componentDidMount() {
window.addEventListener('message', this.onMessage, false);
vscode.postMessage({ command: 'getData' });
this.setState({ interval: setInterval(this.saveScheduledChanges, 2000) });
if (this.props.dark) {
const box = document.getElementsByTagName('html');
if (box != null && box.length > 0) {
box[0].classList.add('pf-v5-theme-dark');
// box.classList.remove('bg-yellow');
}
}
}
componentWillUnmount() {
if (this.state.interval) clearInterval(this.state.interval);
window.removeEventListener('message', this.onMessage, false);
}
onMessage = (event) => {
const message = event.data;
console.log("message.command", message.command);
switch (message.command) {
case 'kamelets':
KameletApi.saveKamelets(message.kamelets, true);
this.setState((prevState: State) => {
prevState.loadingMessages.push("Kamelets loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
case 'components':
ComponentApi.saveComponents(message.components, true);
this.setState((prevState: State) => {
prevState.loadingMessages.push("Components loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
case 'supportedComponents':
ComponentApi.saveSupportedComponents(message.components);
this.setState((prevState: State) => {
prevState.loadingMessages.push("Supported Components loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
case 'supportedOnly':
ComponentApi.setSupportedOnly(true);
break;
case 'files':
this.saveIntegrationFiles(message.files);
this.setState((prevState: State) => {
prevState.loadingMessages.push("Integrations loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
case 'templates':
const templates = message.templates;
const map = new Map(Object.keys(templates).map(key => [key, templates[key]]));
TemplateApi.saveTemplates(map, true);
this.setState((prevState: State) => {
prevState.loadingMessages.push("Templates loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
case 'javaCode':
const javaCode = message.javaCode;
const javaCodeMap = new Map(Object.keys(javaCode).map(key => [key, javaCode[key]]));
TemplateApi.saveJavaCodes(javaCodeMap, true);
break;
case 'open':
if (this.state.filename === '' && this.state.key === '') {
if (message.page !== "designer" && this.state.interval) clearInterval(this.state.interval);
this.setState({
page: message.page,
filename: message.filename,
yaml: message.yaml,
scheduledYaml: message.yaml,
relativePath: message.relativePath,
fullPath: message.fullPath,
key: Math.random().toString(),
loaded: true,
active: true,
tab: message.tab,
propertyPlaceholders: message.propertyPlaceholders,
beans: message.beans
});
}
break;
case 'activate':
this.setState({ loaded: false, filename: '', key: '', active: true, tab: message.tab });
vscode.postMessage({ command: 'getData', reread: true });
break;
case 'deactivate':
this.setState({ active: false, hasChanges: false });
break;
case 'downloadImage':
EventBus.sendCommand("downloadImage");
break;
case 'blockList':
const blockList = message.blockList;
const blockListMap = new Map(Object.keys(blockList).map(key => [key, blockList[key]])).forEach((list,key) => {
if (key === 'components-blocklist.txt') {
ComponentApi.saveBlockedComponentNames(list.split(/\r?\n/));
}
else if (key === 'kamelets-blocklist.txt') {
KameletApi.saveBlockedKameletNames(list.split(/\r?\n/));
}
});
this.setState((prevState: State) => {
prevState.loadingMessages.push("block lists loaded");
return { loadingMessages: prevState.loadingMessages }
});
break;
}
};
save(filename: string, yaml: string, propertyOnly: boolean) {
if (this.state.active) {
if (!propertyOnly) {
vscode.postMessage({ command: 'save', filename: filename, relativePath: this.state.relativePath, fullPath: this.state.fullPath, code: yaml });
this.setState({ scheduledYaml: yaml, hasChanges: false });
} else {
this.setState({ scheduledYaml: yaml, hasChanges: true });
}
}
}
saveJavCode(name: string, code: string) {
TemplateApi.saveJavaCode(name, code);
vscode.postMessage({ command: 'saveCode', name: name, yamlFullPath: this.state.fullPath, yamFileName: this.state.filename, code: code });
}
savePropertyPlaceholder(key: string, value: string) {
vscode.postMessage({ command: 'savePropertyPlaceholder', key: key, value: value });
}
saveIntegrationFiles(files: any) {
const f = Object.keys(files).map(key => new IntegrationFile(key, files[key]));
this.setState({ files: f });
}
onchangeBlockedList(type: string, name: string, checked: boolean) {
let fileContent = '';
if (type === "component") {
fileContent = ComponentApi.saveBlockedComponentName(name, checked).join('\n');
} else {
fileContent =KameletApi.saveBlockedKameletName(name, checked).join('\n');
}
vscode.postMessage({ command: 'saveBlockedList', key: type, value: fileContent });
}
public render() {
const { loadingMessages, filename, key, yaml, page, loaded, tab } = this.state;
const { dark } = this.props;
return (
<Page className="karavan">
{!loaded &&
<PageSection variant={dark ? "dark" : "light"} className="loading-page">
<Spinner className="progress-stepper" diameter="80px" aria-label="Loading..." />
{/* {loadingMessages.map(message => <Text component={TextVariants.h5}>{message}</Text>)} */}
<Text component={TextVariants.h5}>Loading...</Text>
</PageSection>
}
{loaded && page === "designer" &&
<KaravanDesigner
showCodeTab={false}
key={key}
filename={filename}
yaml={yaml}
onSave={(filename, yaml, propertyOnly) => this.save(filename, yaml, propertyOnly)}
tab={tab}
dark={dark}
onSaveCustomCode={(name, code) => this.saveJavCode(name, code)}
onGetCustomCode={(name, javaType) => {
let code = TemplateApi.getJavaCode(name);
if (code === undefined || code.length === 0) code = TemplateApi.generateCode(javaType, name);
return new Promise<string | undefined>(resolve => resolve(code))
}}
propertyPlaceholders={this.state.propertyPlaceholders}
onSavePropertyPlaceholder={(key, value) => this.savePropertyPlaceholder(key, value)}
beans={this.state.beans}
onInternalConsumerClick={(uri, name, routeId) => {
vscode.postMessage({ command: 'internalConsumerClick', uri: uri, name: name, routeId: routeId });
}}
files={this.state.files.map(f => new IntegrationFile(f.name, f.code))}
/>
}
{loaded && page === "knowledgebase" &&
<KnowledgebasePage
dark={dark}
showBlockCheckbox={true}
changeBlockList={(type: string, name: string, checked: boolean) => this.onchangeBlockedList(type, name, checked)}/>
}
{loaded && page === "topology" &&
<TopologyTab
hideToolbar={true}
files={this.state.files}
onClickAddRoute={() => vscode.postMessage({ command: 'createIntegration' })}
onClickAddREST={() => vscode.postMessage({ command: 'createIntegration' })}
onClickAddBean={() => vscode.postMessage({ command: 'createIntegration' })}
onClickAddKamelet={() => vscode.postMessage({ command: 'createIntegration' })}
onSetFile={(fileName) => vscode.postMessage({ command: 'openFile', fileName: fileName })}
/>
}
</Page>
)
}
}
export default App;