diff --git a/package-lock.json b/package-lock.json index 75466eab9..6d87797b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.3-pre-4", + "version": "1.16.0-pre-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.3-pre-4", + "version": "1.16.0-pre-0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index be84c550e..61274e9ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.3-pre-4", + "version": "1.16.0-pre-0", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Common/GenericDescription/GenericDescription.tsx b/src/Common/GenericDescription/GenericDescription.tsx index 422f170ad..ab853257f 100644 --- a/src/Common/GenericDescription/GenericDescription.tsx +++ b/src/Common/GenericDescription/GenericDescription.tsx @@ -16,32 +16,25 @@ import { useEffect, useRef, useState } from 'react' import ReactMde from 'react-mde' -import Tippy from '@tippyjs/react' -import { ReactComponent as BoldIcon } from '../../Assets/Icon/ic-bold.svg' -import { ReactComponent as CheckedListIcon } from '../../Assets/Icon/ic-checked-list.svg' -import { ReactComponent as CodeIcon } from '../../Assets/Icon/ic-code.svg' -import { ReactComponent as HeaderIcon } from '../../Assets/Icon/ic-header.svg' -import { ReactComponent as ImageIcon } from '../../Assets/Icon/ic-image.svg' -import { ReactComponent as ItalicIcon } from '../../Assets/Icon/ic-italic.svg' -import { ReactComponent as LinkIcon } from '../../Assets/Icon/ic-link.svg' -import { ReactComponent as OrderedListIcon } from '../../Assets/Icon/ic-ordered-list.svg' -import { ReactComponent as Edit } from '../../Assets/Icon/ic-pencil.svg' -import { ReactComponent as QuoteIcon } from '../../Assets/Icon/ic-quote.svg' -import { ReactComponent as StrikethroughIcon } from '../../Assets/Icon/ic-strikethrough.svg' -import { ReactComponent as UnorderedListIcon } from '../../Assets/Icon/ic-unordered-list.svg' -import { ButtonWithLoader, ToastManager, ToastVariantType } from '../../Shared' +import { ReactComponent as Edit } from '@Icons/ic-pencil.svg' +import { ReactComponent as UnorderedListIcon } from '@Icons/ic-unordered-list.svg' + import { - DEFAULT_MARKDOWN_EDITOR_PREVIEW_MESSAGE, - MARKDOWN_EDITOR_COMMAND_ICON_TIPPY_CONTENT, - MARKDOWN_EDITOR_COMMAND_TITLE, - MARKDOWN_EDITOR_COMMANDS, -} from '../Markdown/constant' + Button, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, + Icon, + ToastManager, + ToastVariantType, +} from '../../Shared' +import { DEFAULT_MARKDOWN_EDITOR_PREVIEW_MESSAGE, MARKDOWN_EDITOR_COMMANDS } from '../Markdown/constant' import Markdown from '../Markdown/MarkDown' import { deepEqual, showError } from '..' import { DESCRIPTION_EMPTY_ERROR_MSG, DESCRIPTION_UNSAVED_CHANGES_MSG } from './constant' import { GenericDescriptionProps, MDEditorSelectedTabType } from './types' -import { getParsedUpdatedOnDate } from './utils' +import { getEditorCustomIcon, getParsedUpdatedOnDate } from './utils' import 'react-mde/lib/styles/css/react-mde-all.css' import './genericDescription.scss' @@ -50,16 +43,17 @@ const GenericDescription = ({ text, updatedBy, updatedOn, - isDescriptionPreview, tabIndex, updateDescription, title, minEditorHeight = 300, + emptyStateConfig, }: GenericDescriptionProps) => { const [isLoading, setIsLoading] = useState(false) - const [isEditDescriptionView, setIsEditDescriptionView] = useState(isDescriptionPreview) + const [editorView, setEditorView] = useState< + MDEditorSelectedTabType.PREVIEW | MDEditorSelectedTabType.WRITE | 'previewSaved' | 'empty' + >(text ? 'previewSaved' : 'empty') const [modifiedDescriptionText, setModifiedDescriptionText] = useState(text) - const [selectedTab, setSelectedTab] = useState<'write' | 'preview'>(MDEditorSelectedTabType.WRITE) const isDescriptionModified = !deepEqual(text, modifiedDescriptionText) const mdeRef = useRef(null) @@ -70,27 +64,25 @@ const GenericDescription = ({ const _date = getParsedUpdatedOnDate(updatedOn) const validateDescriptionText = (description: string): boolean => { - let isValid = true - if (description.length === 0) { + const descriptionLength = description.length + if (!descriptionLength) { ToastManager.showToast({ variant: ToastVariantType.error, description: DESCRIPTION_EMPTY_ERROR_MSG, }) - isValid = false } - return isValid + return !!descriptionLength } - const toggleDescriptionView = () => { + const handleCancel = () => { let isConfirmed: boolean = true - if (isDescriptionModified && !isEditDescriptionView) { + if (isDescriptionModified) { // eslint-disable-next-line no-alert isConfirmed = window.confirm(DESCRIPTION_UNSAVED_CHANGES_MSG) } if (isConfirmed) { setModifiedDescriptionText(text) - setIsEditDescriptionView(!isEditDescriptionView) - setSelectedTab(MDEditorSelectedTabType.WRITE) + setEditorView(text ? 'previewSaved' : 'empty') } } @@ -103,171 +95,53 @@ const GenericDescription = ({ try { setIsLoading(true) await updateDescription(trimmedDescription) - setIsEditDescriptionView(true) // Explicitly updating the state, since the modified state gets corrupted setModifiedDescriptionText(trimmedDescription) + setEditorView('previewSaved') } catch (error) { showError(error) + setEditorView(MDEditorSelectedTabType.WRITE) + setModifiedDescriptionText(text) } finally { setIsLoading(false) } } - // TODO: add commandName - // eslint-disable-next-line consistent-return - const editorCustomIcon = (commandName: string): JSX.Element => { - // eslint-disable-next-line default-case - switch (commandName) { - case MARKDOWN_EDITOR_COMMAND_TITLE.HEADER: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.BOLD: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.ITALIC: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.STRIKETHROUGH: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.LINK: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.QUOTE: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.CODE: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.IMAGE: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.UNORDERED_LIST: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.ORDERED_LIST: - return ( - -
- -
-
- ) - case MARKDOWN_EDITOR_COMMAND_TITLE.CHECKED_LIST: - return ( - -
- -
-
- ) - } + const handleWriteDescription = () => { + setEditorView(MDEditorSelectedTabType.WRITE) + } + + const handleTabChange = (tab: MDEditorSelectedTabType) => { + setEditorView(tab) + } + + if (editorView === 'empty') { + const { img, subtitle } = emptyStateConfig || {} + return ( +
+
+
+ {title} + {subtitle &&
{subtitle}
} +
+
+ {img && release-note} +
+ ) } return (
- {isEditDescriptionView ? ( + {editorView === 'previewSaved' ? (
@@ -283,7 +157,7 @@ const GenericDescription = ({
Edit
@@ -309,24 +183,19 @@ const GenericDescription = ({ editorCustomIcon(commandName)} + getIcon={(commandName: string) => getEditorCustomIcon(commandName)} toolbarCommands={MARKDOWN_EDITOR_COMMANDS} value={modifiedDescriptionText} onChange={setModifiedDescriptionText} minEditorHeight={minEditorHeight} minPreviewHeight={150} - selectedTab={selectedTab} - onTabChange={setSelectedTab} + selectedTab={editorView} + onTabChange={handleTabChange} generateMarkdownPreview={(markdown: string) => Promise.resolve( , @@ -335,12 +204,12 @@ const GenericDescription = ({ childProps={{ writeButton: { className: `tab-list__tab pointer fs-13 ${ - selectedTab === MDEditorSelectedTabType.WRITE && 'cb-5 fw-6 active active-tab' + editorView === MDEditorSelectedTabType.WRITE && 'cb-5 fw-6 active active-tab' }`, }, previewButton: { className: `tab-list__tab pointer fs-13 ${ - selectedTab === MDEditorSelectedTabType.PREVIEW && 'cb-5 fw-6 active active-tab' + editorView === MDEditorSelectedTabType.PREVIEW && 'cb-5 fw-6 active active-tab' }`, }, textArea: { @@ -348,28 +217,26 @@ const GenericDescription = ({ }, }} /> - {selectedTab === MDEditorSelectedTabType.WRITE && ( + {editorView === MDEditorSelectedTabType.WRITE && (
-
- - +
)} diff --git a/src/Common/GenericDescription/types.ts b/src/Common/GenericDescription/types.ts index ead6ad9db..802b5c9f7 100644 --- a/src/Common/GenericDescription/types.ts +++ b/src/Common/GenericDescription/types.ts @@ -18,11 +18,14 @@ export interface GenericDescriptionProps { text?: string updatedBy?: string updatedOn?: string - isDescriptionPreview: boolean updateDescription: (string) => Promise title: string tabIndex?: number minEditorHeight?: number + emptyStateConfig?: { + img: string + subtitle: JSX.Element + } } export enum MDEditorSelectedTabType { diff --git a/src/Common/GenericDescription/utils.ts b/src/Common/GenericDescription/utils.ts deleted file mode 100644 index 0f0ccd4ee..000000000 --- a/src/Common/GenericDescription/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024. Devtron Inc. - * - * Licensed 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 moment from 'moment' - -import { DATE_TIME_FORMATS, ZERO_TIME_STRING } from '@Common/Constants' - -export const getParsedUpdatedOnDate = (updatedOn: string) => { - if (!updatedOn || updatedOn === ZERO_TIME_STRING) { - return '' - } - - const _moment = moment(updatedOn) - - return _moment.isValid() ? _moment.format(DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT) : updatedOn -} diff --git a/src/Common/GenericDescription/utils.tsx b/src/Common/GenericDescription/utils.tsx new file mode 100644 index 000000000..b4e7f8c7e --- /dev/null +++ b/src/Common/GenericDescription/utils.tsx @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed 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 Tippy from '@tippyjs/react' +import moment from 'moment' + +import { ReactComponent as BoldIcon } from '@Icons/ic-bold.svg' +import { ReactComponent as CheckedListIcon } from '@Icons/ic-checked-list.svg' +import { ReactComponent as CodeIcon } from '@Icons/ic-code.svg' +import { ReactComponent as HeaderIcon } from '@Icons/ic-header.svg' +import { ReactComponent as ImageIcon } from '@Icons/ic-image.svg' +import { ReactComponent as ItalicIcon } from '@Icons/ic-italic.svg' +import { ReactComponent as LinkIcon } from '@Icons/ic-link.svg' +import { ReactComponent as OrderedListIcon } from '@Icons/ic-ordered-list.svg' +import { ReactComponent as QuoteIcon } from '@Icons/ic-quote.svg' +import { ReactComponent as StrikethroughIcon } from '@Icons/ic-strikethrough.svg' +import { ReactComponent as UnorderedListIcon } from '@Icons/ic-unordered-list.svg' +import { DATE_TIME_FORMATS, ZERO_TIME_STRING } from '@Common/Constants' +import { logExceptionToSentry } from '@Common/Helper' +import { MARKDOWN_EDITOR_COMMAND_ICON_TIPPY_CONTENT, MARKDOWN_EDITOR_COMMAND_TITLE } from '@Common/Markdown/constant' + +export const getParsedUpdatedOnDate = (updatedOn: string) => { + if (!updatedOn || updatedOn === ZERO_TIME_STRING) { + return '' + } + + const _moment = moment(updatedOn) + + return _moment.isValid() ? _moment.format(DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT) : updatedOn +} + +export const getEditorCustomIcon = (commandName: string): JSX.Element => { + switch (commandName) { + case MARKDOWN_EDITOR_COMMAND_TITLE.HEADER: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.BOLD: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.ITALIC: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.STRIKETHROUGH: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.LINK: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.QUOTE: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.CODE: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.IMAGE: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.UNORDERED_LIST: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.ORDERED_LIST: + return ( + +
+ +
+
+ ) + case MARKDOWN_EDITOR_COMMAND_TITLE.CHECKED_LIST: + return ( + +
+ +
+
+ ) + default: + logExceptionToSentry('Invalid command for MDE') + return null + } +} diff --git a/src/Shared/Components/FramerComponents/index.ts b/src/Shared/Components/FramerComponents/index.ts index fe454ac71..611b99a4f 100644 --- a/src/Shared/Components/FramerComponents/index.ts +++ b/src/Shared/Components/FramerComponents/index.ts @@ -1,3 +1,10 @@ -import { animate, AnimatePresence, motion, useMotionTemplate, useMotionValue } from 'framer-motion' +import { + animate, + AnimatePresence, + motion, + useAnimationControls, + useMotionTemplate, + useMotionValue, +} from 'framer-motion' -export { animate, AnimatePresence, motion, useMotionTemplate, useMotionValue } +export { animate, AnimatePresence, motion, useAnimationControls, useMotionTemplate, useMotionValue } diff --git a/src/Shared/Components/ProgressBar/ProgressBar.component.tsx b/src/Shared/Components/ProgressBar/ProgressBar.component.tsx index 7289bd466..f6f6b4ee3 100644 --- a/src/Shared/Components/ProgressBar/ProgressBar.component.tsx +++ b/src/Shared/Components/ProgressBar/ProgressBar.component.tsx @@ -28,7 +28,7 @@ export const ProgressBar = ({ isLoading, intervalTime = 50 }: ProgressBarProps)