diff --git a/src/plugins/console/public/application/containers/file_tree/__tests__/__snapshots__/file_tree.test.tsx.snap b/src/plugins/console/public/application/components/__tests__/__snapshots__/file_tree.test.tsx.snap similarity index 100% rename from src/plugins/console/public/application/containers/file_tree/__tests__/__snapshots__/file_tree.test.tsx.snap rename to src/plugins/console/public/application/components/__tests__/__snapshots__/file_tree.test.tsx.snap diff --git a/src/plugins/console/public/application/containers/file_tree/__tests__/file_tree.test.tsx b/src/plugins/console/public/application/components/__tests__/file_tree.test.tsx similarity index 94% rename from src/plugins/console/public/application/containers/file_tree/__tests__/file_tree.test.tsx rename to src/plugins/console/public/application/components/__tests__/file_tree.test.tsx index e97bbbf248fdb..18dabac00e493 100644 --- a/src/plugins/console/public/application/containers/file_tree/__tests__/file_tree.test.tsx +++ b/src/plugins/console/public/application/components/__tests__/file_tree.test.tsx @@ -17,20 +17,16 @@ * under the License. */ -import '../../../models/legacy_core_editor/legacy_core_editor.test.mocks'; +import '../../models/legacy_core_editor/legacy_core_editor.test.mocks'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { FileTreeEntry } from '../../../components/file_tree/file_tree_entry'; +import { FileTreeEntry } from '../../components/file_tree/file_tree_entry'; -import { serviceContextMock } from '../../../contexts/services_context.mock'; -import { - TextObjectsContextProvider, - ServicesContextProvider, - ContextValue, -} from '../../../contexts'; +import { serviceContextMock } from '../../contexts/services_context.mock'; +import { TextObjectsContextProvider, ServicesContextProvider, ContextValue } from '../../contexts'; import { FileTree } from '../file_tree'; diff --git a/src/plugins/console/public/application/containers/file_tree/__tests__/helpers.ts b/src/plugins/console/public/application/components/__tests__/helpers.ts similarity index 100% rename from src/plugins/console/public/application/containers/file_tree/__tests__/helpers.ts rename to src/plugins/console/public/application/components/__tests__/helpers.ts diff --git a/src/plugins/console/public/application/components/file_tree/delete_file_modal.tsx b/src/plugins/console/public/application/components/delete_file_modal.tsx similarity index 100% rename from src/plugins/console/public/application/components/file_tree/delete_file_modal.tsx rename to src/plugins/console/public/application/components/delete_file_modal.tsx diff --git a/src/plugins/console/public/application/components/edit_file_modal.tsx b/src/plugins/console/public/application/components/edit_file_modal.tsx new file mode 100644 index 0000000000000..817ce658e6ec0 --- /dev/null +++ b/src/plugins/console/public/application/components/edit_file_modal.tsx @@ -0,0 +1,120 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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, { useState, FunctionComponent } from 'react'; +import { + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiButtonEmpty, + EuiButton, + EuiFormRow, + EuiFieldText, + EuiText, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface Props { + initialFileName?: string; + onClose: () => void; + onSubmit: (fileName: string) => void; +} + +export const EditFileModal: FunctionComponent = ({ onClose, initialFileName, onSubmit }) => { + const [fileName, setFileName] = useState(initialFileName ?? ''); + const [isPristine, setIsPristine] = useState(true); + const [errors, setErrors] = useState(null); + const isInvalid = Boolean(!isPristine && errors); + + return ( + + + + Delete File + + +
{ + if (e) { + e.preventDefault(); + } + if (isPristine) { + setErrors('Please provide a file name'); + return; + } + if (!errors) { + onSubmit(fileName); + } + }} + > + + + { + if (isPristine) { + setIsPristine(false); + } + const name = event.target.value; + setErrors(!name ? 'File name is required' : null); + setFileName(name); + }} + /> + + + {errors && ( + <> + + + {errors} + + + + )} + + + + + {i18n.translate('console.editModal.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + onSubmit(fileName)}> + {initialFileName + ? i18n.translate('console.editModal.editButtonLabel', { + defaultMessage: 'Edit', + }) + : i18n.translate('console.editModal.createButtonLabel', { + defaultMessage: 'Create', + })} + + +
+
+
+ ); +}; diff --git a/src/plugins/console/public/application/components/file_tree/icons_and_copy/save_error.tsx b/src/plugins/console/public/application/components/file_save_error_icon.tsx similarity index 96% rename from src/plugins/console/public/application/components/file_tree/icons_and_copy/save_error.tsx rename to src/plugins/console/public/application/components/file_save_error_icon.tsx index edf01e624907f..168fff260ec44 100644 --- a/src/plugins/console/public/application/components/file_tree/icons_and_copy/save_error.tsx +++ b/src/plugins/console/public/application/components/file_save_error_icon.tsx @@ -31,6 +31,6 @@ export const FileSaveErrorIcon: FunctionComponent = ({ errorMessage }) => values: { errorMessage }, })} > - + ); diff --git a/src/plugins/console/public/application/components/file_tree/file_actions_bar.tsx b/src/plugins/console/public/application/components/file_tree/file_actions_bar.tsx deleted file mode 100644 index 381503aa83671..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/file_actions_bar.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent, useState } from 'react'; -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPopover, -} from '@elastic/eui'; - -import { FileForm } from './forms'; - -export interface Props { - onCreate: (fileName: string) => void; - onFilter: () => void; - disabled?: boolean; - fileActionInProgress?: boolean; -} - -export const FileActionsBar: FunctionComponent = ({ - onCreate, - onFilter, - disabled, - fileActionInProgress, -}) => { - const [showCreateFilePopover, setShowCreateFilePopover] = useState(false); - - return ( - - - {fileActionInProgress && ( - - - - )} - - - { - setShowCreateFilePopover(false); - onFilter(); - }} - color="text" - aria-label={i18n.translate('console.fileTree.forms.createSearchToggleAriaLabel', { - defaultMessage: 'Toggle file filter', - })} - iconType="search" - /> - - - document.querySelector('.conAppFileNameTextField')! as HTMLElement} - isOpen={showCreateFilePopover && !disabled} - closePopover={() => setShowCreateFilePopover(false)} - button={ - { - setShowCreateFilePopover(true); - }} - color="text" - aria-label={i18n.translate('console.fileTree.forms.createButtonAriaLabel', { - defaultMessage: 'Create a file', - })} - iconType="plusInCircle" - data-test-subj="consoleCreateFileButton" - /> - } - > - { - onCreate(fileName); - setShowCreateFilePopover(false); - }} - /> - - - - ); -}; diff --git a/src/plugins/console/public/application/components/file_tree/file_search_bar.tsx b/src/plugins/console/public/application/components/file_tree/file_search_bar.tsx deleted file mode 100644 index 86bf93d739c8f..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/file_search_bar.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch } from '@elastic/eui'; - -export interface Props { - searchValue: string; - onChange: (value: string) => void; -} - -export const FileSearchBar: FunctionComponent = ({ searchValue, onChange }) => { - return ( - - - ref?.focus()} - compressed - placeholder={i18n.translate('console.fileTree.searchBarPlaceholderText', { - defaultMessage: 'Enter a file name', - })} - onChange={event => { - onChange(event.target.value); - }} - value={searchValue} - /> - - - ); -}; diff --git a/src/plugins/console/public/application/components/file_tree/file_tree.tsx b/src/plugins/console/public/application/components/file_tree/file_tree.tsx deleted file mode 100644 index 298ee3fc3716c..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/file_tree.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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, { FunctionComponent, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FileTreeEntry, Props as FileEntryProps } from './file_tree_entry'; -import { FileActionsBar } from './file_actions_bar'; -import { FileSearchBar } from './file_search_bar'; - -export interface Props { - entries: FileEntryProps[]; - onCreate: (fileName: string) => void; - onSearchFilter: (search: string | undefined) => void; - searchFilter?: string; - disabled?: boolean; -} - -export const FileTree: FunctionComponent = ({ - entries, - onSearchFilter, - disabled, - searchFilter, - onCreate, -}) => { - const [showFileSearchBar, setShowFileSearchBar] = useState(false); - - return ( -
- - {/* File Action Bar */} - - { - if (showFileSearchBar) { - onSearchFilter(undefined); - } - setShowFileSearchBar(!showFileSearchBar); - }} - /> - - {/* File Search Bar */} - {showFileSearchBar && ( - - { - onSearchFilter(searchTerm); - }} - searchValue={searchFilter ?? ''} - /> - - )} - - {entries.map((props, idx) => ( - - - - ))} - -
- ); -}; diff --git a/src/plugins/console/public/application/components/file_tree/file_tree_entry.tsx b/src/plugins/console/public/application/components/file_tree/file_tree_entry.tsx deleted file mode 100644 index 6ef105afed759..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/file_tree_entry.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiButtonIcon, EuiFieldText } from '@elastic/eui'; - -import { FileSaveErrorIcon } from './icons_and_copy'; - -export interface EditHandlerArg { - id: string; - name: string; -} - -export interface Props { - id: string; - name: string; - onSelect: (id: string) => void; - onDelete: (id: string) => void; - onEdit: (props: EditHandlerArg) => void; - className?: string; - canDelete?: boolean; - canEdit?: boolean; - displayName?: React.ReactNode; - ariaLabel?: string; - error?: string; -} - -export const FileTreeEntry: FunctionComponent = ({ - name, - displayName, - className, - onSelect, - canDelete, - onDelete, - canEdit, - onEdit, - id, - ariaLabel, - error, -}) => { - const [isEditing, setIsEditing] = useState(false); - const [nameValue, setNameValue] = useState(undefined); - - const renderInputField = () => { - const handleSubmit = () => { - // Don't allow empty names to be saved and don't - // save the same name again... - if (nameValue && nameValue !== name && onEdit) { - onEdit({ name: nameValue, id }); - } - setNameValue(undefined); - setIsEditing(false); - }; - return ( - - ref?.focus()} - compressed - onBlur={() => { - handleSubmit(); - }} - onChange={event => { - setNameValue(event.target.value); - }} - value={nameValue} - /> - - ); - }; - - const renderEntry = () => ( - - - { - if (event.keyCode === 13 /* Enter */) { - event.preventDefault(); - onSelect(id); - } - }} - > - {error && ( - - - - )} - {displayName ?? name} - - - - ); - - return ( - onSelect(id)} - > - {isEditing ? renderInputField() : renderEntry()} - {/* File Actions */} - - - {canEdit && ( - - { - event.preventDefault(); - event.stopPropagation(); - setIsEditing(true); - setNameValue(name); - }} - color="primary" - iconType="pencil" - /> - - )} - {canDelete && ( - - { - event.preventDefault(); - event.stopPropagation(); - onDelete(id); - }} - className="conApp__fileTree__entry__actionButton" - color="danger" - iconType="trash" - /> - - )} - - - - ); -}; diff --git a/src/plugins/console/public/application/components/file_tree/forms/file_form.tsx b/src/plugins/console/public/application/components/file_tree/forms/file_form.tsx deleted file mode 100644 index 12fdc7f36163b..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/forms/file_form.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent, useState } from 'react'; -import { EuiButton, EuiFieldText, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; - -interface Props { - initial?: { - fileName: string; - }; - onSubmit: (fileName: string) => void; - isSubmitting: boolean; -} - -export const FileForm: FunctionComponent = ({ onSubmit, isSubmitting, initial }) => { - const [fileName, setFileName] = useState(initial?.fileName ?? ''); - const [isPristine, setIsPristine] = useState(true); - const [errors, setErrors] = useState(null); - const isInvalid = Boolean(!isPristine && errors); - return ( - <> -
{ - if (e) { - e.preventDefault(); - } - if (isPristine) { - setErrors('Please provide a file name'); - return; - } - if (!errors) { - onSubmit(fileName); - } - }} - > - - { - if (isPristine) { - setIsPristine(false); - } - const name = event.target.value; - setErrors(!name ? 'File name is required' : null); - setFileName(name); - }} - /> - - {errors && ( - <> - - - {errors} - - - - )} - - - {!initial - ? i18n.translate('console.fileTree.forms.createButtonLabel', { - defaultMessage: 'Create', - }) - : i18n.translate('console.fileTree.forms.editButtonLabel', { - defaultMessage: 'Edit', - })} - - - - - ); -}; diff --git a/src/plugins/console/public/application/components/file_tree/forms/index.ts b/src/plugins/console/public/application/components/file_tree/forms/index.ts deleted file mode 100644 index 5bc1bb640f6b6..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/forms/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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. - */ - -export { FileForm } from './file_form'; diff --git a/src/plugins/console/public/application/components/file_tree/icons_and_copy/index.ts b/src/plugins/console/public/application/components/file_tree/icons_and_copy/index.ts deleted file mode 100644 index b06dcd4a274c8..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/icons_and_copy/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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. - */ - -export { FileSaveErrorIcon } from './save_error'; -export { FileSavedIcon } from './saved'; diff --git a/src/plugins/console/public/application/components/file_tree/icons_and_copy/saved.tsx b/src/plugins/console/public/application/components/file_tree/icons_and_copy/saved.tsx deleted file mode 100644 index 2f6363989a451..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/icons_and_copy/saved.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; -import { EuiToolTip, EuiIcon } from '@elastic/eui'; - -export const FileSavedIcon: FunctionComponent = () => ( - - - -); diff --git a/src/plugins/console/public/application/components/file_tree/index.ts b/src/plugins/console/public/application/components/file_tree/index.ts deleted file mode 100644 index d48f70dfa72dd..0000000000000 --- a/src/plugins/console/public/application/components/file_tree/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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. - */ - -export { FileForm } from './forms'; -export { FileActionsBar } from './file_actions_bar'; -export { DeleteFileModal } from './delete_file_modal'; -export { FileSearchBar } from './file_search_bar'; -export { FileTree } from './file_tree'; -export * from './icons_and_copy'; diff --git a/src/plugins/console/public/application/components/files_popover.tsx b/src/plugins/console/public/application/components/files_popover.tsx new file mode 100644 index 0000000000000..2e58a65059d85 --- /dev/null +++ b/src/plugins/console/public/application/components/files_popover.tsx @@ -0,0 +1,141 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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, { FunctionComponent, useState } from 'react'; +import { flow } from 'fp-ts/lib/function'; +import { + EuiListGroup, + EuiSpacer, + EuiListGroupItem, + EuiButtonEmpty, + EuiFieldSearch, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { sortTextObjectsAsc, TextObjectWithId } from '../../../common/text_object'; +import { useTextObjectsActionContext, useTextObjectsReadContext } from '../contexts'; + +type EnhancedTextObjectWithId = TextObjectWithId & { + displayName?: JSX.Element; +}; + +const addDefaultValues = (textObjects: TextObjectWithId[]): TextObjectWithId[] => + textObjects.map(textObject => ({ + ...textObject, + name: textObject.isScratchPad ? 'Default' : textObject.name ?? `Untitled`, + })); + +const filterTextObjects = ( + searchTerm: string, + textObjects: TextObjectWithId[] +): EnhancedTextObjectWithId[] => + textObjects.flatMap(textObject => { + const idx = textObject?.name?.toLowerCase().indexOf(searchTerm.toLowerCase()); + if (typeof idx === 'number' && idx > -1) { + return [ + { + ...textObject, + displayName: ( + <> + {textObject!.name!.slice(0, idx)} + {{textObject!.name!.slice(idx, idx + searchTerm.length)}} + {textObject!.name!.slice(idx + searchTerm.length, textObject!.name!.length)} + + ), + }, + ]; + } + return []; + }); + +const searchAndSort = (searchTerm: string | undefined) => + flow( + (textObjects: TextObjectWithId[]) => + searchTerm ? filterTextObjects(searchTerm, textObjects) : textObjects, + sortTextObjectsAsc + ); + +export const FilesPopover: FunctionComponent = () => { + const disabled = false; // TODO: This should be linked to isSubmitting + + const [searchFilter, setSearchFilter] = useState(undefined); + + const { textObjects } = useTextObjectsReadContext(); + const dispatch = useTextObjectsActionContext(); + + const prepareData = flow(addDefaultValues, searchAndSort(searchFilter)); + + const filteredTextObjects: EnhancedTextObjectWithId[] = prepareData( + Object.values(textObjects).filter(Boolean) + ); + + // TODO: Identify default file and remove current file from the list + // className: classNames({ + // conApp__fileTree__scratchPadEntry: isScratchPad, + // 'conApp__fileTree__entry--selected': id === currentTextObjectId, + // }), + + return ( +
+ ref?.focus()} + compressed + onChange={event => { + setSearchFilter(event.target.value); + }} + value={searchFilter ?? ''} + /> + + + + { + dispatch({ + type: 'setCreateFileModalVisible', + payload: true, + }); + }} + data-test-subj="consoleCreateFileButton" + > + {i18n.translate('console.fileTree.forms.createButtonAriaLabel', { + defaultMessage: 'Create a file', + })} + + + + {filteredTextObjects.map(({ id, displayName, name }) => ( + { + dispatch({ + type: 'setCurrent', + payload: id, + }); + }} + label={displayName ?? name} + size="s" + /> + ))} + +
+ ); +}; diff --git a/src/plugins/console/public/application/components/index.ts b/src/plugins/console/public/application/components/index.ts index e13d894055487..0d51496869a5e 100644 --- a/src/plugins/console/public/application/components/index.ts +++ b/src/plugins/console/public/application/components/index.ts @@ -19,9 +19,12 @@ export { NetworkRequestStatusBar } from './network_request_status_bar'; export { SomethingWentWrongCallout } from './something_went_wrong_callout'; -export { TopNavMenuItem, TopNavMenu } from './top_nav_menu'; +export { TopNavMenu } from './top_nav_menu'; export { ConsoleMenu } from './console_menu'; export { WelcomePanel } from './welcome_panel'; export { AutocompleteOptions, DevToolsSettingsModal } from './settings_modal'; +export { DeleteFileModal } from './delete_file_modal'; +export { EditFileModal } from './edit_file_modal'; export { HelpPanel } from './help_panel'; -export * from './file_tree'; +export { FileSaveErrorIcon } from './file_save_error_icon'; +export { FilesPopover } from './files_popover'; diff --git a/src/plugins/console/public/application/components/network_request_status_bar/network_request_status_bar.tsx b/src/plugins/console/public/application/components/network_request_status_bar/network_request_status_bar.tsx index 9a8718d73ff67..4b3b60f56025c 100644 --- a/src/plugins/console/public/application/components/network_request_status_bar/network_request_status_bar.tsx +++ b/src/plugins/console/public/application/components/network_request_status_bar/network_request_status_bar.tsx @@ -119,6 +119,16 @@ export const NetworkRequestStatusBar: FunctionComponent = ({ ); + } else { + content = ( + + + {i18n.translate('console.responseCodePlaceholderContent', { + defaultMessage: 'No request sent yet', + })} + + + ); } return ( diff --git a/src/plugins/console/public/application/components/top_nav_menu.tsx b/src/plugins/console/public/application/components/top_nav_menu.tsx index 92f3f32da9a4e..f0cb87b96921c 100644 --- a/src/plugins/console/public/application/components/top_nav_menu.tsx +++ b/src/plugins/console/public/application/components/top_nav_menu.tsx @@ -18,37 +18,66 @@ */ import React, { FunctionComponent } from 'react'; -import { EuiTabs, EuiTab } from '@elastic/eui'; - -export interface TopNavMenuItem { - id: string; - label: string; - description: string; - onClick: () => void; - testId: string; -} +import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; interface Props { - disabled?: boolean; - items: TopNavMenuItem[]; + onClickHistory: () => void; + onClickSettings: () => void; + onClickHelp: () => void; } -export const TopNavMenu: FunctionComponent = ({ items, disabled }) => { - return ( - - {items.map((item, idx) => { - return ( - - {item.label} - - ); - })} - - ); -}; +export const TopNavMenu: FunctionComponent = ({ + onClickHistory, + onClickSettings, + onClickHelp, +}) => ( + + + + + {i18n.translate('console.topNav.helpTabLabel', { + defaultMessage: 'Help', + })} + + + + + + + + {i18n.translate('console.topNav.historyTabLabel', { + defaultMessage: 'History', + })} + + + + + + {i18n.translate('console.topNav.settingsTabLabel', { + defaultMessage: 'Settings', + })} + + + + + + +); diff --git a/src/plugins/console/public/application/containers/editor/editor.tsx b/src/plugins/console/public/application/containers/editor/editor.tsx index 1493847182748..6d20d7c752cc3 100644 --- a/src/plugins/console/public/application/containers/editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/editor.tsx @@ -19,10 +19,9 @@ import React, { useCallback, memo, useState, useEffect } from 'react'; import { debounce } from 'lodash'; -import { EuiProgress, EuiControlBar, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiProgress } from '@elastic/eui'; -import { Panel, PanelsContainer } from '../../../../../kibana_react/public'; -import { Editor as EditorUI, EditorOutput } from './legacy/console_editor'; +import { PanelsContainer } from '../../../../../kibana_react/public'; import { StorageKeys } from '../../../services'; import { useServicesContext, @@ -30,8 +29,8 @@ import { useTextObjectsReadContext, } from '../../contexts'; -import { addDefaultValues } from '../file_tree/file_tree'; -import { NetworkRequestStatusBar, FileSaveErrorIcon, FileSavedIcon } from '../../components'; +import { RequestPanel } from './request_panel'; +import { ResponsePanel } from './response_panel'; const INITIAL_PANEL_WIDTH = 50; const PANEL_MIN_WIDTH = '100px'; @@ -50,20 +49,12 @@ export const Editor = memo(() => { const [initialTextValue, setInitialTextValue] = useState(); - const { - textObjects, - currentTextObjectId, - persistingTextObjectWithId, - textObjectsSaveError, - } = useTextObjectsReadContext(); + const { currentTextObjectId } = useTextObjectsReadContext(); const { requestInFlight, - lastResult: { data: requestData, error: requestError }, + lastResult: { data, error }, } = useRequestReadContext(); - const lastDatum = requestData?.[requestData.length - 1] ?? requestError; - - const currentTextObject = textObjects[currentTextObjectId]; const [firstPanelWidth, secondPanelWidth] = storage.get(StorageKeys.WIDTH, [ INITIAL_PANEL_WIDTH, @@ -93,97 +84,20 @@ export const Editor = memo(() => { ) : null} + - - {currentTextObject && initialTextValue != null && ( - <> - - - {persistingTextObjectWithId === currentTextObjectId ? ( - - ) : textObjectsSaveError[currentTextObjectId] ? ( - - ) : ( - - )} - - ), - }, - ]} - /> - - )} - - + - <> - - - ), - }, - ]} - /> - - + requestInFlight={requestInFlight} + data={data} + error={error} + /> ); diff --git a/src/plugins/console/public/application/containers/editor/request_panel.tsx b/src/plugins/console/public/application/containers/editor/request_panel.tsx new file mode 100644 index 0000000000000..c1c653ea48fd4 --- /dev/null +++ b/src/plugins/console/public/application/containers/editor/request_panel.tsx @@ -0,0 +1,189 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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, { useState, memo } from 'react'; +import { + EuiFlexGroup, + EuiButtonIcon, + EuiButtonToggle, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { Panel } from '../../../../../kibana_react/public'; +import { useTextObjectsCRUD } from '../../hooks/text_objects'; +import { useTextObjectsReadContext } from '../../contexts'; +import { DeleteFileModal, FileSaveErrorIcon, FilesPopover } from '../../components'; + +import { Editor as EditorUI } from './legacy/console_editor'; + +interface Props { + initialWidth: number; + minWidth: string; + initialTextValue?: string; +} + +export const RequestPanel = memo(({ initialWidth, minWidth, initialTextValue }) => { + const [isFileOptionsOpen, setFileOptionsOpen] = useState(false); + const [isFileTreeOpen, setFileTreeOpen] = useState(false); + const [isDeleteModalVisible, setDeleteModalVisible] = useState(false); + const textObjectsCRUD = useTextObjectsCRUD(); + + const { + textObjects, + currentTextObjectId, + persistingTextObjectWithId, + textObjectsSaveError, + } = useTextObjectsReadContext(); + + const currentTextObject = textObjects[currentTextObjectId]; + + let content; + + if (currentTextObject && initialTextValue != null) { + const fileOptions = [ + {}}> + {i18n.translate('console.cloneFileButtonLabel', { + defaultMessage: 'Clone', + })} + , + + {}} + disabled={currentTextObject.isScratchPad} + > + {i18n.translate('console.renameFileButtonLabel', { + defaultMessage: 'Rename', + })} + , + + setDeleteModalVisible(true)} + disabled={currentTextObject.isScratchPad} + > + {i18n.translate('console.deleteFileButtonLabel', { + defaultMessage: 'Delete', + })} + , + ]; + + content = ( + <> +
+ + + + + setFileTreeOpen(e.target.checked)} + iconType={isFileTreeOpen ? 'folderOpen' : 'folderClosed'} + label={isFileTreeOpen ? 'Close file menu' : 'Open file menu'} + /> + + + + {currentTextObject.name} + + + + {persistingTextObjectWithId === currentTextObjectId ? ( + + {i18n.translate('console.saveFileStatusText', { + defaultMessage: 'Saving...', + })} + + ) : textObjectsSaveError[currentTextObjectId] ? ( + + ) : null} + + + + + + + setFileOptionsOpen(!isFileOptionsOpen)} + /> + } + isOpen={isFileOptionsOpen} + closePopover={() => setFileOptionsOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + +
+ + {isDeleteModalVisible && ( + setDeleteModalVisible(false)} + onDeleteConfirmation={() => { + textObjectsCRUD.delete(currentTextObject).finally(() => { + setDeleteModalVisible(false); + }); + }} + /> + )} + + + {isFileTreeOpen && ( + + + + )} + + +
+ +
+
+
+ + ); + } + + return ( + + {content} + + ); +}); diff --git a/src/plugins/console/public/application/containers/editor/response_panel.tsx b/src/plugins/console/public/application/containers/editor/response_panel.tsx new file mode 100644 index 0000000000000..958126b96e130 --- /dev/null +++ b/src/plugins/console/public/application/containers/editor/response_panel.tsx @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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, { memo } from 'react'; + +import { Panel } from '../../../../../kibana_react/public'; +import { NetworkRequestStatusBar } from '../../components'; +import { EditorOutput } from './legacy/console_editor'; + +interface Props { + initialWidth: number; + minWidth: string; + requestInFlight: boolean; + data?: any; + error?: any; +} + +export const ResponsePanel = memo( + ({ initialWidth, minWidth, requestInFlight, data, error }) => { + const lastDatum = data?.[data.length - 1] ?? error; + + return ( + + <> +
+ +
+ +
+ +
+ +
+ ); + } +); diff --git a/src/plugins/console/public/application/containers/file_tree/file_tree.tsx b/src/plugins/console/public/application/containers/file_tree/file_tree.tsx deleted file mode 100644 index 7070c2f43d1bf..0000000000000 --- a/src/plugins/console/public/application/containers/file_tree/file_tree.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { flow } from 'fp-ts/lib/function'; -import React, { useState, FunctionComponent } from 'react'; -import classNames from 'classnames'; - -import { sortTextObjectsAsc, TextObjectWithId } from '../../../../common/text_object'; - -import { useTextObjectsCRUD } from '../../hooks/text_objects'; -import { DeleteFileModal, FileTree as FileTreeComponent } from '../../components'; -import { useTextObjectsActionContext, useTextObjectsReadContext } from '../../contexts'; - -import { filterTextObjects, EnhancedTextObjectWithId } from './filter_text_objects'; - -export const addDefaultValues = (textObjects: TextObjectWithId[]): TextObjectWithId[] => - textObjects.map(textObject => ({ - ...textObject, - name: textObject.isScratchPad ? 'Default' : textObject.name ?? `Untitled`, - })); - -const searchAndSort = (searchTerm: string | undefined) => - flow( - (textObjects: TextObjectWithId[]) => - searchTerm ? filterTextObjects(searchTerm, textObjects) : textObjects, - sortTextObjectsAsc - ); - -export const FileTree: FunctionComponent = () => { - const [searchFilter, setSearchFilter] = useState(undefined); - const [isFileActionInProgress, setIsFileActionInProgress] = useState(false); - const [idToDelete, setIdToDelete] = useState(undefined); - - const textObjectsCRUD = useTextObjectsCRUD(); - const { textObjects, currentTextObjectId, textObjectsSaveError } = useTextObjectsReadContext(); - const dispatch = useTextObjectsActionContext(); - - const prepareData = flow(addDefaultValues, searchAndSort(searchFilter)); - - const filteredTextObjects: EnhancedTextObjectWithId[] = prepareData( - Object.values(textObjects).filter(Boolean) - ); - - return ( - <> - { - setIsFileActionInProgress(true); - textObjectsCRUD - .create({ - // We don't set a text value here so that the default text value is - // set for new files - textObject: { - updatedAt: Date.now(), - createdAt: Date.now(), - name: fileName, - }, - }) - .finally(() => setIsFileActionInProgress(false)); - }} - disabled={isFileActionInProgress} - searchFilter={searchFilter} - onSearchFilter={string => setSearchFilter(string)} - entries={filteredTextObjects.map(({ isScratchPad, name, id, displayName }) => { - return { - id, - className: classNames({ - conApp__fileTree__scratchPadEntry: isScratchPad, - 'conApp__fileTree__entry--selected': id === currentTextObjectId, - }), - name: name!, - error: textObjectsSaveError[id], - displayName, - onSelect: () => { - dispatch({ - type: 'setCurrent', - payload: id, - }); - }, - canDelete: !isScratchPad, - onDelete: deleteId => { - setIdToDelete(deleteId); - }, - canEdit: !isScratchPad, - onEdit: ({ name: fileName, id: idToEdit }) => { - setIsFileActionInProgress(true); - textObjectsCRUD - .update({ - textObject: { - id: idToEdit, - name: fileName, - }, - }) - .finally(() => { - setIsFileActionInProgress(false); - }); - }, - }; - })} - /> - {idToDelete && ( - setIdToDelete(undefined)} - onDeleteConfirmation={() => { - setIsFileActionInProgress(true); - setIdToDelete(undefined); - textObjectsCRUD.delete(textObjects[idToDelete]!).finally(() => { - setIsFileActionInProgress(false); - }); - }} - /> - )} - - ); -}; diff --git a/src/plugins/console/public/application/containers/file_tree/filter_text_objects.tsx b/src/plugins/console/public/application/containers/file_tree/filter_text_objects.tsx deleted file mode 100644 index 1af31dc38287f..0000000000000 --- a/src/plugins/console/public/application/containers/file_tree/filter_text_objects.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { TextObjectWithId } from '../../../../common/text_object'; - -export type EnhancedTextObjectWithId = TextObjectWithId & { - displayName?: JSX.Element; -}; - -export const filterTextObjects = ( - searchTerm: string, - textObjects: TextObjectWithId[] -): EnhancedTextObjectWithId[] => - textObjects.flatMap(textObject => { - const idx = textObject?.name?.toLowerCase().indexOf(searchTerm.toLowerCase()); - if (typeof idx === 'number' && idx > -1) { - return [ - { - ...textObject, - displayName: ( - <> - {textObject!.name!.slice(0, idx)} - {{textObject!.name!.slice(idx, idx + searchTerm.length)}} - {textObject!.name!.slice(idx + searchTerm.length, textObject!.name!.length)} - - ), - }, - ]; - } - return []; - }); diff --git a/src/plugins/console/public/application/containers/file_tree/index.ts b/src/plugins/console/public/application/containers/file_tree/index.ts deleted file mode 100644 index 28e5103c38e02..0000000000000 --- a/src/plugins/console/public/application/containers/file_tree/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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. - */ - -export { FileTree } from './file_tree'; diff --git a/src/plugins/console/public/application/containers/main/get_top_nav.ts b/src/plugins/console/public/application/containers/main/get_top_nav.ts deleted file mode 100644 index 32229fb64537d..0000000000000 --- a/src/plugins/console/public/application/containers/main/get_top_nav.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 { i18n } from '@kbn/i18n'; - -interface Props { - onClickFiles: () => void; - onClickHistory: () => void; - onClickSettings: () => void; - onClickHelp: () => void; -} - -export function getTopNavConfig({ - onClickFiles, - onClickHistory, - onClickSettings, - onClickHelp, -}: Props) { - return [ - { - id: 'files', - label: i18n.translate('console.topNav.filesTabLabel', { - defaultMessage: 'Files', - }), - description: i18n.translate('console.topNav.filesTabDescription', { - defaultMessage: 'Files', - }), - onClick: () => { - onClickFiles(); - }, - testId: 'consoleHistoryButton', - }, - { - id: 'history', - label: i18n.translate('console.topNav.historyTabLabel', { - defaultMessage: 'History', - }), - description: i18n.translate('console.topNav.historyTabDescription', { - defaultMessage: 'History', - }), - onClick: () => { - onClickHistory(); - }, - testId: 'consoleHistoryButton', - }, - { - id: 'settings', - label: i18n.translate('console.topNav.settingsTabLabel', { - defaultMessage: 'Settings', - }), - description: i18n.translate('console.topNav.settingsTabDescription', { - defaultMessage: 'Settings', - }), - onClick: () => { - onClickSettings(); - }, - testId: 'consoleSettingsButton', - }, - { - id: 'help', - label: i18n.translate('console.topNav.helpTabLabel', { - defaultMessage: 'Help', - }), - description: i18n.translate('console.topNav.helpTabDescription', { - defaultMessage: 'Help', - }), - onClick: () => { - onClickHelp(); - }, - testId: 'consoleHelpButton', - }, - ]; -} diff --git a/src/plugins/console/public/application/containers/main/main.tsx b/src/plugins/console/public/application/containers/main/main.tsx index c04e60da308f5..cb76005d50c56 100644 --- a/src/plugins/console/public/application/containers/main/main.tsx +++ b/src/plugins/console/public/application/containers/main/main.tsx @@ -23,20 +23,31 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPageContent } from '@elastic/eu import { ConsoleHistory } from '../console_history'; import { Editor } from '../editor'; import { Settings } from '../settings'; -import { FileTree } from '../file_tree'; -import { TopNavMenu, WelcomePanel, HelpPanel, SomethingWentWrongCallout } from '../../components'; - -import { useServicesContext, useEditorContext } from '../../contexts'; +import { + TopNavMenu, + WelcomePanel, + HelpPanel, + EditFileModal, + SomethingWentWrongCallout, +} from '../../components'; +import { + useServicesContext, + useEditorContext, + useTextObjectsReadContext, + useTextObjectsActionContext, +} from '../../contexts'; import { useDataInit } from '../../hooks'; - -import { getTopNavConfig } from './get_top_nav'; +import { useTextObjectsCRUD } from '../../hooks/text_objects'; export function Main() { const { services: { storage }, } = useServicesContext(); + const textObjectsCRUD = useTextObjectsCRUD(); + const { isCreateFileModalVisible } = useTextObjectsReadContext(); + const dispatch = useTextObjectsActionContext(); const [{ ready: editorsReady }] = useEditorContext(); const [showWelcome, setShowWelcomePanel] = useState( @@ -46,7 +57,6 @@ export function Main() { const [showingHistory, setShowHistory] = useState(false); const [showSettings, setShowSettings] = useState(false); const [showHelp, setShowHelp] = useState(false); - const [showFileTree, setShowFileTree] = useState(false); const renderConsoleHistory = () => { return editorsReady ? setShowHistory(false)} /> : null; @@ -73,16 +83,14 @@ export function Main() { setShowFileTree(!showFileTree), - onClickHistory: () => setShowHistory(!showingHistory), - onClickSettings: () => setShowSettings(true), - onClickHelp: () => setShowHelp(!showHelp), - })} + onClickHistory={() => setShowHistory(!showingHistory)} + onClickSettings={() => setShowSettings(true)} + onClickHelp={() => setShowHelp(!showHelp)} /> + {showingHistory ? {renderConsoleHistory()} : null} + - {showFileTree && ( - - - - )} @@ -114,6 +117,35 @@ export function Main() { {showSettings ? setShowSettings(false)} /> : null} {showHelp ? setShowHelp(false)} /> : null} + + {isCreateFileModalVisible && ( + { + dispatch({ + type: 'setCreateFileModalVisible', + payload: false, + }); + }} + onSubmit={fileName => { + textObjectsCRUD + .create({ + // We don't set a text value here so that the default text value is + // set for new files + textObject: { + updatedAt: Date.now(), + createdAt: Date.now(), + name: fileName, + }, + }) + .finally(() => { + dispatch({ + type: 'setCreateFileModalVisible', + payload: false, + }); + }); + }} + /> + )} ); } diff --git a/src/plugins/console/public/application/stores/text_object.ts b/src/plugins/console/public/application/stores/text_object.ts index 3e18582418ef2..ea91141c42fd1 100644 --- a/src/plugins/console/public/application/stores/text_object.ts +++ b/src/plugins/console/public/application/stores/text_object.ts @@ -41,6 +41,7 @@ export interface Store { currentTextObjectId: string; textObjects: Record; textObjectsSaveError: Record; + isCreateFileModalVisible: boolean; } export const initialValue: Store = { @@ -48,6 +49,7 @@ export const initialValue: Store = { currentTextObjectId: '', textObjects: {}, textObjectsSaveError: {}, + isCreateFileModalVisible: false, }; type TextObjectUpsertPayload = Partial & IdObject; @@ -60,12 +62,18 @@ export type Action = | { type: 'upsertAndSetCurrent'; payload: TextObjectUpsertPayload } | { type: 'delete'; payload: string } | { type: 'saveError'; payload: { textObjectId: string; error: Error | string } } - | { type: 'clearSaveError'; payload: { textObjectId: string } }; + | { type: 'clearSaveError'; payload: { textObjectId: string } } + | { type: 'setCreateFileModalVisible'; payload: boolean }; export const reducer: Reducer = (state, action) => produce( state, draft => { + if (action.type === 'setCreateFileModalVisible') { + draft.isCreateFileModalVisible = action.payload; + return; + } + if (action.type === 'setSavingTextObject') { draft.persistingTextObjectWithId = action.payload; return; diff --git a/src/plugins/console/public/styles/_app.scss b/src/plugins/console/public/styles/_app.scss index 78f8b157ec3e0..35f35c145edaf 100644 --- a/src/plugins/console/public/styles/_app.scss +++ b/src/plugins/console/public/styles/_app.scss @@ -1,11 +1,6 @@ // TODO: Move all of the styles here (should be modularised by, e.g., CSS-in-JS or CSS modules). @import '@elastic/eui/src/components/header/variables'; -@mixin fullHeightMinusControlBarSmall { - // Full height, minus the EuiControlBar height (size "s"); - height: calc(100% - 40px); -} - #consoleRoot { height: calc(100vh - calc(#{$euiHeaderChildSize} * 2)); display: flex; @@ -20,8 +15,42 @@ flex: 1 1 auto; } +.conApp__menuBar { + padding: $euiSizeXS 0; +} + +.conApp__fileListContainer { + padding: 0 $euiSizeS; +} + +.conApp__fileList { + @include euiYScrollWithShadows; + + // Override EuiListGroup styles. + padding-left: 0; + padding-right: 0; + + .euiListGroupItem { + margin-top: 0; + } +} + +.conAppContainer { + position: relative; + display: flex; + flex: 1 1 auto; +} + +.conAppPanel { + flex-direction: column; +} + +.conAppPanelHeader { + padding: $euiSizeS; + flex: 0 1 auto; +} + .conApp__editor { - @include fullHeightMinusControlBarSmall; width: 100%; display: flex; flex: 0 0 auto; @@ -35,7 +64,6 @@ } .conApp__output { - @include fullHeightMinusControlBarSmall; display: flex; flex: 1 1 1px; } diff --git a/src/plugins/console/public/styles/components/_file_tree.scss b/src/plugins/console/public/styles/components/_file_tree.scss deleted file mode 100644 index 4ee40b2fc63ae..0000000000000 --- a/src/plugins/console/public/styles/components/_file_tree.scss +++ /dev/null @@ -1,75 +0,0 @@ -.conApp__fileTree { - position: relative; - height: 100%; - - background-color: $euiColorLightestShade; - min-width: $euiButtonMinWidth * 2.5; - - &__spinner { - padding-right: 5px; - } - - &__actionBar { - padding: 0 5px; - background-color: $euiColorMediumShade; - color: $euiColorLightestShade; - } - - &__scratchPadEntry { - font-style: italic; - } - &__entryContainer { - // Absolute position this to fill the entire file - // tree area. This provides the element with the correct - // height for scrolling. - position: absolute; - top:0; - bottom:0; - left:0; - right:0; - overflow: auto; - } - - &__errorIconContainer { - margin-right: 5px; - } - - &__entry { - font-size: $euiSizeM; - padding: 2px 5px; - height: $euiFormControlHeight * 0.9; - - &--selected { - background-color: $euiColorLightShade; - } - - @mixin transitionToShowActionButton { - transition: opacity .2s; - opacity: 100%; - } - - &:hover &__actionButton { - @include transitionToShowActionButton - } - - &:focus &__actionButton { - @include transitionToShowActionButton - } - - &__actionButton { - // Default is hidden - opacity: 0; - &:focus { - @include transitionToShowActionButton - } - } - - &__fileName { - cursor: pointer; - } - - &:focus { - background-color: $euiColorLightShade; - } - } -} diff --git a/src/plugins/console/public/styles/components/_index.scss b/src/plugins/console/public/styles/components/_index.scss index be80276b3e182..9dfef202d1254 100644 --- a/src/plugins/console/public/styles/components/_index.scss +++ b/src/plugins/console/public/styles/components/_index.scss @@ -1,3 +1,2 @@ @import 'help'; @import 'history'; -@import 'file_tree';