From bd1defcc2e087b298d388a29ec7bd56df2139f83 Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Tue, 21 Mar 2023 14:59:16 +0530 Subject: [PATCH 1/9] initial commit Signed-off-by: msivasubramaniaan --- .eslintignore | 1 + package.json | 16 ++ src/openshift/component.ts | 6 + .../create-component/app/component.scss | 126 ++++++++++++++ .../create-component/app/component.tsx | 163 ++++++++++++++++++ src/webview/create-component/app/index.html | 52 ++++++ src/webview/create-component/app/index.tsx | 12 ++ src/webview/create-component/app/loading.tsx | 30 ++++ .../create-component/app/tsconfig.json | 28 +++ .../create-component/app/vsCodeMessage.ts | 37 ++++ .../create-component/createComponentLoader.ts | 92 ++++++++++ .../create-component/webpack.config.js | 66 +++++++ tsconfig.json | 1 + 13 files changed, 630 insertions(+) create mode 100644 src/webview/create-component/app/component.scss create mode 100644 src/webview/create-component/app/component.tsx create mode 100644 src/webview/create-component/app/index.html create mode 100644 src/webview/create-component/app/index.tsx create mode 100644 src/webview/create-component/app/loading.tsx create mode 100644 src/webview/create-component/app/tsconfig.json create mode 100644 src/webview/create-component/app/vsCodeMessage.ts create mode 100644 src/webview/create-component/createComponentLoader.ts create mode 100644 src/webview/create-component/webpack.config.js diff --git a/.eslintignore b/.eslintignore index 31d36814d..f9f7a5bf3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,6 +12,7 @@ src/webview/cluster src/webview/webpack.config.js out src/webview/common +src/webview/create-component src/webview/create-service src/webview/devfile-registry src/webview/welcome diff --git a/package.json b/package.json index 3c5bff2f6..ac05f79cf 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dev:compile:log-view": "webpack --mode development --config src/webview/log/webpack.config.js", "dev:compile:describe-view": "webpack --mode development --config src/webview/describe/webpack.config.js", "dev:compile:cluster-view": "webpack --mode development --config src/webview/cluster/webpack.config.js", + "dev:compile:create-component-view": "webpack --mode development --config src/webview/create-component/webpack.config.js", "dev:compile:create-service-view": "webpack --mode development --config src/webview/create-service/webpack.config.js", "dev:compile:devfile-registry-view": "webpack --mode development --config src/webview/devfile-registry/webpack.config.js", "dev:compile:welcome-view": "webpack --mode development --config src/webview/welcome/webpack.config.js", @@ -66,6 +67,7 @@ "dev:run:welcome-view": "webpack-dev-server --port 9222 --mode development --config src/webview/welcome/webpack.config.js", "dev:run:git-import-view": "webpack-dev-server --port 9222 --mode development --config src/webview/git-import/webpack.config.js", "dev:run:helm-chart-view": "webpack-dev-server --port 9222 --mode development --config src/webview/helm-chart/webpack.config.js", + "dev:run:create-component-view": "webpack-dev-server --mode development --config src/webview/create-component/webpack.config.js", "dev:run:create-service-view": "webpack-dev-server --mode development --config src/webview/create-service/webpack.config.js", "watch": "tsc -watch -p ./", "clean": "shx rm -rf out/build out/coverage out/src out/test out/tools out/test-resources out/logViewer", @@ -451,6 +453,15 @@ "dark": "images/title/dark/workspace-new.svg" } }, + { + "command": "openshift.component.createComponentNew", + "title": "New Component Workflow", + "category": "OpenShift", + "icon": { + "light": "images/title/light/workspace-new.svg", + "dark": "images/title/dark/workspace-new.svg" + } + }, { "command": "openshift.component.createFromRootWorkspaceFolder", "title": "New OpenShift Component", @@ -1118,6 +1129,11 @@ "when": "view == openshiftComponentsView", "group": "navigation" }, + { + "command": "openshift.component.createComponentNew", + "when": "view == openshiftComponentsView", + "group": "navigation" + }, { "command": "openshift.component.openImportFromGit", "when": "view == openshiftComponentsView", diff --git a/src/openshift/component.ts b/src/openshift/component.ts index 969094e63..3a6f29410 100644 --- a/src/openshift/component.ts +++ b/src/openshift/component.ts @@ -25,6 +25,7 @@ import { ComponentWorkspaceFolder } from '../odo/workspace'; import LogViewLoader from '../webview/log/LogViewLoader'; import GitImportLoader from '../webview/git-import/gitImportLoader'; import { CliChannel } from '../cli'; +import CreateComponentLoader from '../webview/create-component/createComponentLoader'; function createCancelledResult(stepName: string): any { const cancelledResult: any = new String(''); @@ -459,6 +460,11 @@ export class Component extends OpenShiftItem { return Component.createFromRootWorkspaceFolder(workspacePath, [], { componentTypeName: compTypeName, projectName: starterProjectName, registryName: regName }); } + @vsCommand('openshift.component.createComponentNew') + static async createComponent(): Promise { + await CreateComponentLoader.loadView('Create Component'); + } + @vsCommand('openshift.component.openImportFromGit') static async importFromGit(): Promise { await GitImportLoader.loadView('Git Import'); diff --git a/src/webview/create-component/app/component.scss b/src/webview/create-component/app/component.scss new file mode 100644 index 000000000..d1e245ce3 --- /dev/null +++ b/src/webview/create-component/app/component.scss @@ -0,0 +1,126 @@ +body { + &.vscode-light { + background-color: var(--color-background--darken-05); + } +} + +button { + padding: 0; +} + +.margin { + margin: 3rem; +} + +.mainContainer, +.formContainer, +.form { + display: flex; + flex-direction: column; + font-family: var(--vscode-font-family); + // overflow-y: scroll; +} + +.title { + width: 100%; + margin-bottom: 1rem; +} + +.sub { + margin: 1rem 0rem; +} + +.subTitle { + margin-bottom: 2rem; + max-width: 59rem; + justify-content: center; + word-spacing: 5px; + text-align: justify; + color: var(--vscode-foreground); + font-weight: normal; + font-size: 14px; +} + +.buttonStyle { + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + color: var(--vscode-button-foreground); + + &:hover { + background-color: '#BE0000' !important; + } + + text-transform: none; +} + +.buttonDiv { + margin: 2rem 0rem; + position: fixed; +} + +.buttonDivWithBottom { + display: inline-flex; + border-top: 1px solid; + // border-color: inherit; + width: 100%; + height: 3rem; + background-color: var(--vscode-editor-background) !important; + backdrop-filter: blur(15px); + padding-top: 1rem; + margin-top: 2rem; + position: fixed; + bottom: -3%; +} + +.strategyContainer { + display: flex; + flex-direction: row; + border-top: 3px solid; + max-width: 55rem; + margin-top: 2rem; + padding-top: 0.5rem; + padding-left: 1rem; + min-height: 2rem; +} + +.cardContainer { + display: flex; + flex-direction: column; + margin-top: 1rem; +} + +.strategySuccess { + border-color: green; + background-color: darkseagreen !important; +} + +.strategyWarning { + border-color: orange; + background-color: burlywood !important; +} + +.MuiFormHelperText-root { + color: var(--vscode-foreground) !important; + margin-left: 0px !important; +} + +.Mui-error { + color: #EE0000 !important; +} + +.MuiOutlinedInput-input { + padding: 5px 14px !important; +} + +.MuiFormControl-root { + border: none; + max-width: 50%; + &:hover { + border: none; + } +} + +.MuiAccordionSummary-root { + padding: 0px !important; +} diff --git a/src/webview/create-component/app/component.tsx b/src/webview/create-component/app/component.tsx new file mode 100644 index 000000000..0b0b32fcc --- /dev/null +++ b/src/webview/create-component/app/component.tsx @@ -0,0 +1,163 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import React from 'react'; +import { Button, InputLabel, TextField, Typography } from '@mui/material'; +import { VSCodeMessage } from './vsCodeMessage'; +import { ComponentTypeDescription } from '../../../odo/componentType'; +import { DefaultProps } from '../../common/propertyTypes'; +import './component.scss'; + +export interface CompTypeDesc extends ComponentTypeDescription { + selected: boolean; +} + +declare module 'react' { + interface HTMLAttributes extends AriaAttributes, DOMAttributes { + // extends React's HTMLAttributes + directory?: string; // remember to make these attributes optional.... + webkitdirectory?: string; + } +} + +export class CreateComponent extends React.Component { + + constructor(props: DefaultProps | Readonly) { + super(props); + this.state = { + componentName: { + value: '', + error: false, + helpText: '' + } + } + } + + validateComponentName = (value: string): void => { + VSCodeMessage.postMessage({ + action: 'validateComponentName', + compName: value + }) + } + + initalize(close = false) { + if (close) { + + } + this.state = { + componentName: { + value: '', + error: false, + helpText: '' + } + }; + } + + componentDidMount(): void { + VSCodeMessage.onMessage((message) => { + if (message.data.action === 'validateComponentName') { + this.setState({ + componentName: { + value: message.data.componentName, + error: message.data.error, + helpText: message.data.helpText, + } + }); + } + }); + } + + createComponent = (): void => { + VSCodeMessage.postMessage({ + action: 'createComponent' + }); + } + + selectFolder = (): void => { + VSCodeMessage.postMessage({ + action: 'selectFolder' + }) + } + + handleCreateBtnDisable(): boolean { + return true; + } + + render(): React.ReactNode { + const { componentName } = this.state; + return ( +
+
+ Create Component +
+
+ Create a component in the local workspace using the selected starter project and associated `devfile.yaml` configuration. Devfile is a manifest file that contains information about various resources (URL, Storage, Services, etc.) that correspond to your component. +
+
+
+
+ + Component Name + + this.validateComponentName(e.target.value)} + id='bootstrap-input' + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + backgroundColor: 'var(--vscode-settings-textInputBackground)' + } + }} + style={{ width: '30%', paddingTop: '10px' }} + helperText={componentName.helpText} /> +
+
+ + Select folder for component + + +
+
+ + +
+
+
+
+ ) + } +} diff --git a/src/webview/create-component/app/index.html b/src/webview/create-component/app/index.html new file mode 100644 index 000000000..756850842 --- /dev/null +++ b/src/webview/create-component/app/index.html @@ -0,0 +1,52 @@ + + + + <% if (production) { %> + + <% } %> + + + Git Import + + + + +
+ + diff --git a/src/webview/create-component/app/index.tsx b/src/webview/create-component/app/index.tsx new file mode 100644 index 000000000..04aaced60 --- /dev/null +++ b/src/webview/create-component/app/index.tsx @@ -0,0 +1,12 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import * as ReactDOM from 'react-dom'; +import * as React from 'react'; +import { CreateComponent } from './component'; + +ReactDOM.render( + , + document.getElementById('root'), +); diff --git a/src/webview/create-component/app/loading.tsx b/src/webview/create-component/app/loading.tsx new file mode 100644 index 000000000..c006c78a9 --- /dev/null +++ b/src/webview/create-component/app/loading.tsx @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import React from 'react'; +import { Box, CircularProgress, Typography } from '@mui/material'; + +interface LoadProps extends React.AllHTMLAttributes { + title: string +} + +export const LoadScreen: React.FC = ({ + title +}: LoadProps) => { + return ( +
+
+ + + + {title} +
+
+ ); +} diff --git a/src/webview/create-component/app/tsconfig.json b/src/webview/create-component/app/tsconfig.json new file mode 100644 index 000000000..6cbe384d7 --- /dev/null +++ b/src/webview/create-component/app/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "target": "es6", + "outDir": "configViewer", + "lib": [ + "es6", + "dom" + ], + "jsx": "react", + "sourceMap": true, + "noUnusedLocals": true, + // "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true, + "typeRoots": [ + "../../../../node_modules/@types", + "../../@types" + ], + "baseUrl": ".", + "resolveJsonModule": true, + "esModuleInterop": true, + }, + "exclude": [ + "node_modules" + ] +} diff --git a/src/webview/create-component/app/vsCodeMessage.ts b/src/webview/create-component/app/vsCodeMessage.ts new file mode 100644 index 000000000..aac1a4b7a --- /dev/null +++ b/src/webview/create-component/app/vsCodeMessage.ts @@ -0,0 +1,37 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +declare const acquireVsCodeApi: Function; + +interface VSCodeApi { + getState: () => any; + setState: (newState: any) => any; + postMessage: (message: any) => void; +} + +class VSCodeWrapper { + private readonly vscodeApi: VSCodeApi = acquireVsCodeApi(); + + /** + * Send message to the extension framework. + * @param message + */ + public postMessage(message: any): void { + this.vscodeApi.postMessage(message); + } + + /** + * Add listener for messages from extension framework. + * @param callback called when the extension sends a message + * @returns function to clean up the message eventListener. + */ + public onMessage(callback: (message: any) => void): () => void { + window.addEventListener('message', callback); + return () => window.removeEventListener('message', callback); + } +} + +// Singleton to prevent multiple fetches of VsCodeAPI. +export const VSCodeMessage: VSCodeWrapper = new VSCodeWrapper(); diff --git a/src/webview/create-component/createComponentLoader.ts b/src/webview/create-component/createComponentLoader.ts new file mode 100644 index 000000000..19ddfe568 --- /dev/null +++ b/src/webview/create-component/createComponentLoader.ts @@ -0,0 +1,92 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import { ExtensionID } from '../../util/constants'; +import OpenShiftItem from '../../openshift/openshiftItem'; +import { selectWorkspaceFolder } from '../../util/workspace'; + +let panel: vscode.WebviewPanel; + +async function createComponentMessageListener(event: any): Promise { + switch (event?.action) { + case 'validateComponentName': + validateComponentName(event) + break; + case 'selectFolder': + selectWorkspaceFolder(); + break; + default: + break + } +} + +export default class CreateComponentLoader { + static get extensionPath() { + return vscode.extensions.getExtension(ExtensionID).extensionPath + } + + static async loadView(title: string): Promise { + const localResourceRoot = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'out', 'componentViewer')); + if (panel) { + panel.reveal(vscode.ViewColumn.One); + } else { + panel = vscode.window.createWebviewPanel('CreateComponentView', title, vscode.ViewColumn.One, { + enableScripts: true, + localResourceRoots: [localResourceRoot], + retainContextWhenHidden: true + }); + panel.iconPath = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'images/gitImport/git.svg')); + panel.webview.html = CreateComponentLoader.getWebviewContent(CreateComponentLoader.extensionPath, panel); + panel.onDidDispose(() => { + panel = undefined; + }); + panel.webview.onDidReceiveMessage(createComponentMessageListener); + } + return panel; + } + + private static getWebviewContent(extensionPath: string, p: vscode.WebviewPanel): string { + // Local path to main script run in the webview + const reactAppRootOnDisk = path.join(extensionPath, 'out', 'componentViewer'); + const reactAppPathOnDisk = vscode.Uri.file( + path.join(reactAppRootOnDisk, 'componentViewer.js'), + ); + const reactAppUri = p.webview.asWebviewUri(reactAppPathOnDisk); + const htmlString: Buffer = fs.readFileSync(path.join(reactAppRootOnDisk, 'index.html')); + const meta = ``; + return `${htmlString}` + .replace('%COMMAND%', '') + .replace('%PLATFORM%', process.platform) + .replace('componentViewer.js', `${reactAppUri}`) + .replace('%BASE_URL%', `${reactAppUri}`) + .replace('', meta); + } + + static refresh(): void { + if (panel) { + panel?.webview.postMessage({ action: 'loadingComponents' }); + } + } +} + +function validateComponentName(event: any) { + let validationMessage = OpenShiftItem.emptyName(`Required ${event.compName}`, event.compName.trim()); + if (!validationMessage) validationMessage = OpenShiftItem.validateMatches(`Not a valid ${event.compName}. + Please use lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`, event.compName); + if (!validationMessage) validationMessage = OpenShiftItem.lengthName(`${event.compName} should be between 2-63 characters`, event.compName, 0); + panel?.webview.postMessage({ + action: event.action, + error: !validationMessage ? false : true, + helpText: !validationMessage ? 'A unique name given to the component that will be used to name associated resources.' : validationMessage, + componentName: event.compName + }); +} diff --git a/src/webview/create-component/webpack.config.js b/src/webview/create-component/webpack.config.js new file mode 100644 index 000000000..723ad8c35 --- /dev/null +++ b/src/webview/create-component/webpack.config.js @@ -0,0 +1,66 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +const path = require("path"); + +const HtmlWebPackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +const isProduction = process.argv[process.argv.indexOf('--mode') + 1] === 'production'; + +module.exports = { + entry: { + componentViewer: "./src/webview/create-component/app/index.tsx" + }, + output: { + path: path.resolve(__dirname, "../../../out", "componentViewer"), + filename: "[name].js" + }, + devtool: "eval-source-map", + resolve: { + extensions: [".js", ".ts", ".tsx", ".json"] + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + loader: "ts-loader", + }, + { + test: /\.(css|scss)$/, + use: [ + { + loader: "style-loader", + }, + { + loader: "css-loader", + }, + { + loader: "sass-loader", + } + ] + }, + { + test: /\.(png|jpg|jpeg|gif|svg|woff2?|ttf|eot|otf)(\?.*$|$)/, + loader: 'file-loader', + options: { + name: 'assets/[name].[ext]', + }, + }, + ] + }, + performance: { + hints: false, + }, + plugins: [ + new HtmlWebPackPlugin({ + template: path.resolve(__dirname, 'app', 'index.html'), + filename: 'index.html', + templateParameters: { + production: isProduction + } + }) + ], +}; diff --git a/tsconfig.json b/tsconfig.json index e46df39d1..1785da19c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,6 +28,7 @@ "src/webview/log/app", "src/webview/describe/app", "src/webview/cluster/app", + "src/webview/create-component/app", "src/webview/create-service/app", "src/webview/devfile-registry/app", "src/webview/welcome/app", From a3864c20eff9a3b53c14541a86a8d382cbb3d139 Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Thu, 20 Apr 2023 16:25:25 +0530 Subject: [PATCH 2/9] added dropdown for file seltion Signed-off-by: msivasubramaniaan --- src/util/workspace.ts | 7 ++ .../create-component/app/component.scss | 19 +++ .../create-component/app/component.tsx | 111 +++++++++++++----- .../create-component/createComponentLoader.ts | 8 +- 4 files changed, 116 insertions(+), 29 deletions(-) diff --git a/src/util/workspace.ts b/src/util/workspace.ts index d2c277bcd..295ea6868 100644 --- a/src/util/workspace.ts +++ b/src/util/workspace.ts @@ -42,6 +42,13 @@ function createWorkspaceFolderItem(wsFolder: WorkspaceFolder) { }; } +export function selectWorkspaceFolders(): WorkspaceFolderItem[] { + if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { + return workspace.workspaceFolders.filter(isComponentFilter).map(createWorkspaceFolderItem); + } + return []; +} + export async function selectWorkspaceFolder(): Promise { let folders: WorkspaceFolderItem[] = []; if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { diff --git a/src/webview/create-component/app/component.scss b/src/webview/create-component/app/component.scss index d1e245ce3..e00dac7a9 100644 --- a/src/webview/create-component/app/component.scss +++ b/src/webview/create-component/app/component.scss @@ -113,6 +113,25 @@ button { padding: 5px 14px !important; } +.MuiOutlinedInput-input:disabled { + -webkit-text-fill-color: white !important; +} + +.MuiSelect-select, .MuiMenu-paper { + background-color: var(--vscode-dropdown-background) !important; + color: var(--vscode-dropdown-foreground) !important; + border: 0.5px solid var(--vscode-dropdown-border) !important; +} + +.MuiMenuItem-root { + background-color: var(--vscode-dropdown-background) !important; + color: var(--vscode-dropdown-foreground) !important; +} + +.MuiMenuItem-root:hover { + background-color: var(--vscode-button-hoverBackground) !important; +} + .MuiFormControl-root { border: none; max-width: 50%; diff --git a/src/webview/create-component/app/component.tsx b/src/webview/create-component/app/component.tsx index 0b0b32fcc..eb20e314e 100644 --- a/src/webview/create-component/app/component.tsx +++ b/src/webview/create-component/app/component.tsx @@ -3,7 +3,8 @@ * Licensed under the MIT License. See LICENSE file in the project root for license information. *-----------------------------------------------------------------------------------------------*/ import React from 'react'; -import { Button, InputLabel, TextField, Typography } from '@mui/material'; +import { QuickPickItem, Uri } from 'vscode'; +import { Button, InputLabel, MenuItem, TextField, Typography } from '@mui/material'; import { VSCodeMessage } from './vsCodeMessage'; import { ComponentTypeDescription } from '../../../odo/componentType'; import { DefaultProps } from '../../common/propertyTypes'; @@ -13,6 +14,10 @@ export interface CompTypeDesc extends ComponentTypeDescription { selected: boolean; } +interface WorkspaceFolderItem extends QuickPickItem { + uri: Uri; +} + declare module 'react' { interface HTMLAttributes extends AriaAttributes, DOMAttributes { // extends React's HTMLAttributes @@ -22,22 +27,29 @@ declare module 'react' { } export class CreateComponent extends React.Component { constructor(props: DefaultProps | Readonly) { super(props); this.state = { - componentName: { - value: '', + component: { + name: '', error: false, - helpText: '' - } + helpText: '', + }, + wsFolderItems: undefined, + wsFolderPath: '' } + VSCodeMessage.postMessage({ + action: 'selectFolder' + }); } validateComponentName = (value: string): void => { @@ -52,11 +64,13 @@ export class CreateComponent extends React.Component { if (message.data.action === 'validateComponentName') { this.setState({ - componentName: { - value: message.data.componentName, + component: { + name: message.data.componentName, error: message.data.error, helpText: message.data.helpText, } }); + } else if (message.data.action === 'selectFolder') { + if (message.data.wsFolderItems.length > 0) { + this.setState({ wsFolderPath: message.data.wsFolderItems[0].uri.fsPath }); + } + this.setState({ + wsFolderItems: message.data.wsFolderItems + }); } }); } @@ -86,12 +107,21 @@ export class CreateComponent extends React.Component { + if (event.target.value === 'New Folder') { + + } + this.setState({ + wsFolderPath: event.target.value + }) + } + handleCreateBtnDisable(): boolean { return true; } render(): React.ReactNode { - const { componentName } = this.state; + const { component, wsFolderItems, wsFolderPath } = this.state; return (
@@ -111,8 +141,8 @@ export class CreateComponent extends React.Component this.validateComponentName(e.target.value)} id='bootstrap-input' sx={{ @@ -122,7 +152,7 @@ export class CreateComponent extends React.Component + helperText={component.helpText} />
- Select folder for component + Select Folder for adding component - +
+ {wsFolderItems && this.handleWsFolderDropDownChange(e)} + value={wsFolderPath} + disabled={wsFolderItems?.length <= 1} + id='bootstrap-input' + select + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + backgroundColor: 'var(--vscode-settings-textInputBackground)' + } + }} + style={{ width: '30%', paddingTop: '10px' }}> + {...wsFolderItems?.map((wsFolderItem: WorkspaceFolderItem) => ( + + {wsFolderItem.uri.fsPath} + + ))} + + New Folder + + + } + { wsFolderItems?.length === 0 && + } +
+
+ +
} +
+ {compDescriptions && <> + + Devfiles + option.devfileData.devfile.metadata.language} + getOptionLabel={(option) => option.devfileData.devfile.metadata.displayName + '/' + option.registry.name} + renderOption={(props, option) => ( + img': { mr: 2, flexShrink: 0 } }} {...props}> + + {option.devfileData.devfile.metadata.displayName} / {option.registry.name} + + )} + renderInput={(params) => ( + + )} + renderGroup={(params) => ( +
  • + {params.group} + {params.children} +
  • + )} + onChange={(e, v) => this.handleDevFileChange(e, v)} /> + } + {selectedComponentDesc && selectedComponentDesc.starterProjects?.length > 0 && +
    + + Add Starter Project + this.includeStarterProject(e)} /> + + {this.state.incudeStarterProject && option} + renderInput={(params) => ( + + )} + renderGroup={(params) => ( +
  • + {params.group} + {params.children} +
  • + )} + onChange={(e, v) => this.handleStarterProjectChange(e, v)} />} +
    } - }} - style={{ width: '30%', paddingTop: '10px' }}> - - New Folder - - {...wsFolderItems?.map((wsFolderItem: Uri) => ( - - {wsFolderItem.fsPath} - - ))} -
    - } - {wsFolderItems?.length === 0 && - } +
    + + +
    +
    + - {compDescriptions &&
    - - Devfiles - - option.devfileData.devfile.metadata.language} - getOptionLabel={(option) => option.devfileData.devfile.metadata.displayName + '/' + option.registry.name} - renderOption={(props, option) => ( - img': { mr: 2, flexShrink: 0 } }} {...props}> - - {option.devfileData.devfile.metadata.displayName} / {option.registry.name} - - )} - renderInput={(params) => ( - - )} - renderGroup={(params) => ( -
  • - {params.group} - {params.children} -
  • - )} - onChange={(e, v) => this.handleDevFileChange(e, v)} - /> -
    - } - {selectedComponentDesc && selectedComponentDesc.starterProjects?.length > 0 &&
    - - Add Starter Project - this.includeStarterProject(e)} /> - - {this.state.incudeStarterProject && option} - renderInput={(params) => ( - - )} - renderGroup={(params) => ( -
  • - {params.group} - {params.children} -
  • - )} - onChange={(e, v) => this.handleStarterProjectChange(e, v)} - /> - } -
    - } -
    - - -
    - - - + } + ) } } diff --git a/src/webview/create-component/app/loading.tsx b/src/webview/create-component/app/loading.tsx deleted file mode 100644 index c006c78a9..000000000 --- a/src/webview/create-component/app/loading.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * Copyright (c) Red Hat, Inc. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - *-----------------------------------------------------------------------------------------------*/ -import React from 'react'; -import { Box, CircularProgress, Typography } from '@mui/material'; - -interface LoadProps extends React.AllHTMLAttributes { - title: string -} - -export const LoadScreen: React.FC = ({ - title -}: LoadProps) => { - return ( -
    -
    - - - - {title} -
    -
    - ); -} diff --git a/src/webview/create-component/createComponentLoader.ts b/src/webview/create-component/createComponentLoader.ts index e468b9fea..d0694ef0f 100644 --- a/src/webview/create-component/createComponentLoader.ts +++ b/src/webview/create-component/createComponentLoader.ts @@ -15,8 +15,11 @@ let panel: vscode.WebviewPanel; async function createComponentMessageListener(event: any): Promise { switch (event?.action) { - case 'validateComponentName': - validateComponentName(event) + case 'validateCompName': + validateName(event) + break; + case 'validateAppName': + validateName(event) break; case 'selectFolder': const workspaceFolderItems = event.noWSFolder ? await selectWorkspaceFolder(true, 'Select Component Folder') : selectWorkspaceFolders(); @@ -26,18 +29,7 @@ async function createComponentMessageListener(event: any): Promise { }); break; case 'getAllComponents': - const componentDescriptions = Array.from(ComponentTypesView.instance.getCompDescriptions()).map((compDescription: CompTypeDesc) => { - if (compDescription.devfileData.devfile.metadata.name === 'java-quarkus') { - compDescription.priority = 3; - } else if (compDescription.devfileData.devfile.metadata.name === 'nodejs') { - compDescription.priority = 2; - } else if (compDescription.devfileData.devfile.metadata.name.indexOf('python') !== -1) { - compDescription.priority = 1; - } else { - compDescription.priority = -1; - } - return compDescription; - }); + const componentDescriptions: CompTypeDesc[] = await getComponents(); panel?.webview.postMessage( { action: event.action, @@ -46,14 +38,30 @@ async function createComponentMessageListener(event: any): Promise { ); break; case 'createComponent': - const folderPathUri = vscode.Uri.parse(event.folderPath); - vscode.commands.executeCommand('openshift.component.createFromRootWorkspaceFolder', folderPathUri, undefined, { + panel?.webview.postMessage( + { + action: 'createComponent', + showLoadScreen: true + } + ); + const folderPathUri = vscode.Uri.from(event.folderPath); + await vscode.commands.executeCommand('openshift.component.createFromRootWorkspaceFolder', folderPathUri, undefined, { componentTypeName: event.componentTypeName, projectName: event.projectName, registryName: event.registryName, - compName: event.componentName - }) + compName: event.componentName, + applicationName: event.appName + }); + panel?.webview.postMessage( + { + action: 'createComponent', + showLoadScreen: false + } + ); + case 'close': { + panel?.dispose(); break; + } default: break } @@ -64,7 +72,7 @@ export default class CreateComponentLoader { return vscode.extensions.getExtension(ExtensionID).extensionPath } - static async loadView(title: string): Promise { + static async loadView(title: string, component: CompTypeDesc, starterProjectName: string): Promise { const localResourceRoot = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'out', 'componentViewer')); if (panel) { panel.reveal(vscode.ViewColumn.One); @@ -81,6 +89,13 @@ export default class CreateComponentLoader { }); panel.webview.onDidReceiveMessage(createComponentMessageListener); } + if (component && starterProjectName) { + panel?.webview.postMessage({ + action: 'InputFromDevFile', + selectedComponent: component, + selectedPro: starterProjectName + }); + } return panel; } @@ -113,15 +128,32 @@ export default class CreateComponentLoader { } } -function validateComponentName(event: any) { - let validationMessage = OpenShiftItem.emptyName(`Required ${event.compName}`, event.compName.trim()); - if (!validationMessage) validationMessage = OpenShiftItem.validateMatches(`Not a valid ${event.compName}. - Please use lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`, event.compName); - if (!validationMessage) validationMessage = OpenShiftItem.lengthName(`${event.compName} should be between 2-63 characters`, event.compName, 0); +function validateName(event: any) { + let validationMessage = event.action !== 'validateAppName' ? OpenShiftItem.emptyName(`Required ${event.name}`, event.name.trim()) : null; + if (!validationMessage) validationMessage = OpenShiftItem.validateMatches(`Not a valid ${event.name}. + Please use lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`, event.name); + if (!validationMessage) validationMessage = OpenShiftItem.lengthName(`${event.name} should be between 2-63 characters`, event.name, 0); panel?.webview.postMessage({ action: event.action, error: !validationMessage ? false : true, - helpText: !validationMessage ? 'A unique name given to the component that will be used to name associated resources.' : validationMessage, - componentName: event.compName + helpText: !validationMessage ? event.action === 'validateAppName' ? 'Appended with \'-app\' if not endswith app' : 'A unique name given to the component that will be used to name associated resources.' : validationMessage, + name: event.name + }); +} + +async function getComponents(): Promise { + const componentDescs = ComponentTypesView.instance.getCompDescriptions(); + const componentDescriptions = Array.from(componentDescs).map((compDescription: CompTypeDesc) => { + if (compDescription.devfileData.devfile.metadata.name === 'java-quarkus') { + compDescription.priority = 3; + } else if (compDescription.devfileData.devfile.metadata.name === 'nodejs') { + compDescription.priority = 2; + } else if (compDescription.devfileData.devfile.metadata.name.indexOf('python') !== -1) { + compDescription.priority = 1; + } else { + compDescription.priority = -1; + } + return compDescription; }); + return componentDescriptions; } diff --git a/src/webview/devfile-registry/app/cardItem.tsx b/src/webview/devfile-registry/app/cardItem.tsx index ed0e58674..c7e5a2b21 100644 --- a/src/webview/devfile-registry/app/cardItem.tsx +++ b/src/webview/devfile-registry/app/cardItem.tsx @@ -76,8 +76,8 @@ export class CardItem extends React.Component { ); break; case 'createComponent': - const registryName = event.registryName; - vscode.commands.executeCommand('openshift.componentType.newComponent', starterProject, registryName); + //const registryName = event.registryName; + vscode.commands.executeCommand('openshift.component.createFromLocal', event.compDescription,starterProject); break; case 'cloneToWorkSpace': vscode.commands.executeCommand('openshift.componentType.cloneStarterProjectRepository', starterProject); From 04500a16f2788f8b14fa64178de3e322ce22a810 Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Mon, 24 Apr 2023 21:49:56 +0530 Subject: [PATCH 6/9] added flow flow code Signed-off-by: msivasubramaniaan --- src/openshift/component.ts | 2 +- .../create-component/app/component.tsx | 7 +++-- .../create-component/createComponentLoader.ts | 27 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/openshift/component.ts b/src/openshift/component.ts index 6e43ca456..461a14796 100644 --- a/src/openshift/component.ts +++ b/src/openshift/component.ts @@ -427,7 +427,7 @@ export class Component extends OpenShiftItem { @vsCommand('openshift.component.createFromLocal') static async createComponent(component: CompTypeDesc, starterProjectName: string): Promise { - await CreateComponentLoader.loadView('Create Component', component, starterProjectName); + await CreateComponentLoader.loadView('Create component', component, starterProjectName); } @vsCommand('openshift.component.openImportFromGit') diff --git a/src/webview/create-component/app/component.tsx b/src/webview/create-component/app/component.tsx index 621326ffe..446dfcffd 100644 --- a/src/webview/create-component/app/component.tsx +++ b/src/webview/create-component/app/component.tsx @@ -128,7 +128,6 @@ export class CreateComponent extends React.Component value.devfileData.devfile.metadata.name === selectedComponentDesc.devfileData.devfile.metadata.name && + value.registry.name === selectedComponentDesc.registry.name) : []; + const getInputValue = filterList.length > 0 ? filterList[0].devfileData.devfile.metadata.displayName + '/' + filterList[0].registry.name : '' return ( <> {compDescriptions.length === 0 ? : @@ -314,6 +316,7 @@ export class CreateComponent extends React.Component {this.state.incudeStarterProject && { + const webViewTitle = component ? `${title}-${component.devfileData.devfile.metadata.displayName}` : title; const localResourceRoot = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'out', 'componentViewer')); - if (panel) { - panel.reveal(vscode.ViewColumn.One); - } else { - panel = vscode.window.createWebviewPanel('CreateComponentView', title, vscode.ViewColumn.One, { - enableScripts: true, - localResourceRoots: [localResourceRoot], - retainContextWhenHidden: true - }); - panel.iconPath = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'images/gitImport/git.svg')); - panel.webview.html = CreateComponentLoader.getWebviewContent(CreateComponentLoader.extensionPath, panel); - panel.onDidDispose(() => { - panel = undefined; - }); - panel.webview.onDidReceiveMessage(createComponentMessageListener); - } + panel = vscode.window.createWebviewPanel('CreateComponentView', webViewTitle, vscode.ViewColumn.One, { + enableScripts: true, + localResourceRoots: [localResourceRoot], + retainContextWhenHidden: true + }); + panel.iconPath = vscode.Uri.file(path.join(CreateComponentLoader.extensionPath, 'images/gitImport/git.svg')); + panel.webview.html = CreateComponentLoader.getWebviewContent(CreateComponentLoader.extensionPath, panel); + panel.onDidDispose(() => { + panel = undefined; + }); + panel.webview.onDidReceiveMessage(createComponentMessageListener); if (component && starterProjectName) { panel?.webview.postMessage({ action: 'InputFromDevFile', From 4aaf2067dcc3d4f1666d35423fe9c721792ae16c Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Thu, 27 Apr 2023 17:35:45 +0530 Subject: [PATCH 7/9] fixed css issue on theme based Signed-off-by: msivasubramaniaan --- .../create-component/app/component.scss | 7 +- .../create-component/app/component.tsx | 113 ++++++++++++------ .../create-component/createComponentLoader.ts | 13 +- 3 files changed, 90 insertions(+), 43 deletions(-) diff --git a/src/webview/create-component/app/component.scss b/src/webview/create-component/app/component.scss index 6ee838151..7e34fe83c 100644 --- a/src/webview/create-component/app/component.scss +++ b/src/webview/create-component/app/component.scss @@ -111,17 +111,12 @@ button { .MuiOutlinedInput-input { padding: 5px 14px !important; -} - -.MuiOutlinedInput-input:disabled, .Mui-disabled { - -webkit-text-fill-color: white !important; + border: 0.5px solid var(--vscode-dropdown-border) !important; } .MuiSelect-select, .MuiMenu-paper, .MuiAutocomplete-inputRoot { background-color: var(--vscode-dropdown-background) !important; color: var(--vscode-dropdown-foreground) !important; - border: 0.5px solid var(--vscode-dropdown-border) !important; - } .MuiInputBase-input { diff --git a/src/webview/create-component/app/component.tsx b/src/webview/create-component/app/component.tsx index 446dfcffd..ebfe84c56 100644 --- a/src/webview/create-component/app/component.tsx +++ b/src/webview/create-component/app/component.tsx @@ -4,7 +4,7 @@ *-----------------------------------------------------------------------------------------------*/ import React from 'react'; import { Uri } from 'vscode'; -import { Autocomplete, Box, Button, InputLabel, MenuItem, Switch, TextField, Typography, darken, lighten, styled } from '@mui/material'; +import { Autocomplete, Box, Button, InputLabel, Switch, TextField, Typography, darken, lighten, styled } from '@mui/material'; import { VSCodeMessage } from './vsCodeMessage'; import { DefaultProps, CompTypeDesc } from '../../common/propertyTypes'; import { ascName } from '../../common/util'; @@ -37,7 +37,8 @@ export class CreateComponent extends React.Component { constructor(props: DefaultProps | Readonly) { @@ -59,7 +60,8 @@ export class CreateComponent extends React.Component { - if (event.target.value === 'New Folder') { + handleWsFolderDropDownChange = (event: any, value: Uri | string): void => { + console.log('Value:::',value); + if (typeof value === 'string' && value === 'New Folder') { VSCodeMessage.postMessage({ action: 'selectFolder', noWSFolder: true }); - } else { + } else if (typeof value === 'object') { this.setState({ - wsFolderPath: event.target.value + wsFolderPath: value }); } } @@ -208,11 +216,15 @@ export class CreateComponent extends React.Component value.devfileData.devfile.metadata.name === selectedComponentDesc.devfileData.devfile.metadata.name && value.registry.name === selectedComponentDesc.registry.name) : []; const getInputValue = filterList.length > 0 ? filterList[0].devfileData.devfile.metadata.displayName + '/' + filterList[0].registry.name : '' + const folderDropDownItems: any[] = []; + folderDropDownItems.push('New Folder'); + folderDropDownItems.push(...wsFolderItems); + console.log(folderDropDownItems); return ( <> {compDescriptions.length === 0 ? : @@ -243,7 +255,11 @@ export class CreateComponent extends React.Component
    - {wsFolderItems.length > 0 && this.handleWsFolderDropDownChange(e)} - value={wsFolderPath.fsPath ? wsFolderPath.fsPath : ''} - disabled={wsFolderItems?.length == 0 || autoSelectDisable} - id='bootstrap-input' - select - sx={{ - input: { - color: 'var(--vscode-settings-textInputForeground)', - backgroundColor: 'var(--vscode-settings-textInputBackground)' - } - }} - style={{ width: '30%', paddingTop: '10px' }}> - - New Folder - - {...wsFolderItems?.map((wsFolderItem: Uri) => ( - - {wsFolderItem.fsPath} - - ))} - } + {wsFolderItems.length > 0 && + option.fsPath ? option.fsPath : option} + renderInput={(params) => ( + + )} + renderGroup={(params) => ( +
  • + {params.group} + {params.children} +
  • + )} + onChange={(e, v) => this.handleWsFolderDropDownChange(e, v)} /> + } {wsFolderItems?.length === 0 &&
    {compDescriptions && <> - } {selectedComponentDesc && selectedComponentDesc.starterProjects?.length > 0 &&
    - this.includeStarterProject(e)} /> - {this.state.incudeStarterProject && - Git Import + Create component