Merge workflows view
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 27 Sep 2018 14:37:45 +0000 (16:37 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 27 Sep 2018 14:37:45 +0000 (16:37 +0200)
Feature #13863

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

31 files changed:
package.json
src/components/data-explorer/data-explorer.tsx
src/components/rich-text-editor-link/rich-text-editor-link.tsx [new file with mode: 0644]
src/components/text-field/text-field.tsx
src/index.tsx
src/routes/route-change-handlers.ts
src/routes/routes.ts
src/store/navigation/navigation-action.ts
src/store/projects/project-update-actions.ts
src/store/rich-text-editor-dialog/rich-text-editor-dialog-actions.tsx [new file with mode: 0644]
src/store/shared-with-me-panel/shared-with-me-middleware-service.ts
src/store/side-panel-tree/side-panel-tree-actions.ts
src/store/side-panel/side-panel-action.ts
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/store/workflow-panel/workflow-middleware-service.ts [new file with mode: 0644]
src/store/workflow-panel/workflow-panel-actions.ts [new file with mode: 0644]
src/validators/validators.tsx
src/views-components/current-token-dialog/current-token-dialog.tsx
src/views-components/data-explorer/data-explorer.tsx
src/views-components/data-explorer/renderers.tsx
src/views-components/details-panel/project-details.tsx
src/views-components/form-fields/project-form-fields.tsx
src/views-components/main-content-bar/main-content-bar.tsx
src/views-components/rich-text-editor-dialog/rich-text-editor-dialog.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx
src/views/workflow-panel/workflow-description-card.tsx [new file with mode: 0644]
src/views/workflow-panel/workflow-panel-view.tsx [new file with mode: 0644]
src/views/workflow-panel/workflow-panel.tsx [new file with mode: 0644]
typings/global.d.ts
yarn.lock

index a15f41743127a331eee63b154ff0ca23d2c8f369..1b196f1b994bd56be35c9a86f366506e8c91207f 100644 (file)
     "classnames": "2.2.6",
     "js-yaml": "3.12.0",
     "lodash": "4.17.10",
-    "react": "16.4.2",
+    "react": "16.5.2",
     "react-copy-to-clipboard": "5.0.1",
-    "react-dom": "16.4.2",
+    "react-dom": "16.5.2",
     "react-dropzone": "5.0.1",
     "react-redux": "5.0.7",
     "react-router": "4.3.1",
     "react-router-dom": "4.3.1",
     "react-router-redux": "5.0.0-alpha.9",
+    "react-rte": "0.16.1",
     "react-scripts-ts": "2.17.0",
     "react-splitter-layout": "3.0.1",
     "react-transition-group": "2.4.0",
index 59f4dbebb47832ff4ec5a827c44eb1a434db61ac..d7abde7bddd2f81d3a668f4587f5063bab010d60 100644 (file)
@@ -12,7 +12,7 @@ import { DataTableFilterItem } from '../data-table-filters/data-table-filters';
 import { SearchInput } from '../search-input/search-input';
 import { ArvadosTheme } from "~/common/custom-theme";
 
-type CssRules = 'searchBox' | "toolbar";
+type CssRules = 'searchBox' | "toolbar" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     searchBox: {
@@ -21,6 +21,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
         paddingTop: theme.spacing.unit * 2
     },
+    root: {
+        height: '100%'
+    }
 });
 
 interface DataExplorerDataProps<T> {
@@ -66,7 +69,7 @@ export const DataExplorer = withStyles(styles)(
                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                 dataTableDefaultView
             } = this.props;
-            return <Paper>
+            return <Paper className={classes.root}>
                 <Toolbar className={classes.toolbar}>
                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
                         <div className={classes.searchBox}>
diff --git a/src/components/rich-text-editor-link/rich-text-editor-link.tsx b/src/components/rich-text-editor-link/rich-text-editor-link.tsx
new file mode 100644 (file)
index 0000000..2d6a5b4
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { withStyles, StyleRulesCallback, WithStyles, Typography } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { openRichTextEditorDialog } from '~/store/rich-text-editor-dialog/rich-text-editor-dialog-actions';
+
+type CssRules = "root";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        color: theme.palette.primary.main,
+        cursor: 'pointer'
+    }
+});
+
+interface RichTextEditorLinkData {
+    title: string;
+    label: string;
+    content: string;
+}
+
+interface RichTextEditorLinkActions {
+    onClick: (title: string, content: string) => void;
+}
+
+type RichTextEditorLinkProps = RichTextEditorLinkData & RichTextEditorLinkActions & WithStyles<CssRules>;
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+    onClick: (title: string, content: string) => dispatch<any>(openRichTextEditorDialog(title, content))
+});
+
+export const RichTextEditorLink = connect(undefined, mapDispatchToProps)(
+    withStyles(styles)(({ classes, title, content, label, onClick }: RichTextEditorLinkProps) =>
+        <Typography component='span' className={classes.root}
+            onClick={() => onClick(title, content) }>
+            {label}
+        </Typography>
+    ));
\ No newline at end of file
index b5671dbb08c962b8bae3458738fdf634b57bc382..076889eabb4b9af6acb630d243e9db9829a47950 100644 (file)
@@ -6,6 +6,7 @@ import * as React from 'react';
 import { WrappedFieldProps } from 'redux-form';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { TextField as MaterialTextField, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+import RichTextEditor from 'react-rte';
 
 type CssRules = 'textField';
 
@@ -27,3 +28,30 @@ export const TextField = withStyles(styles)((props: WrappedFieldProps & WithStyl
         fullWidth={true}
         {...props.input}
     />);
+
+
+interface RichEditorTextFieldData {
+    label?: string;
+}
+
+type RichEditorTextFieldProps = RichEditorTextFieldData & WrappedFieldProps & WithStyles<CssRules>;
+
+export const RichEditorTextField = withStyles(styles)(
+    class RichEditorTextField extends React.Component<RichEditorTextFieldProps> {
+        state = {
+            value: RichTextEditor.createValueFromString(this.props.input.value, 'html')
+        };
+
+        onChange = (value: any) => {
+            this.setState({ value });
+            this.props.input.onChange(value.toString('html'));
+        }
+
+        render() {
+            return <RichTextEditor 
+                value={this.state.value}
+                onChange={this.onChange}
+                placeholder={this.props.label} />;
+        }
+    }
+);
\ No newline at end of file
index 542ce6cd8dd8201d8db2f1cf2af9ca47c80fb094..d64798bca541bfc3996df94fdea8c37c81ec09d0 100644 (file)
@@ -38,6 +38,7 @@ import { addRouteChangeHandlers } from './routes/route-change-handlers';
 import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
 import { processResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
+import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
 import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set';
 import { ContainerRequestState } from '~/models/container-request';
 import { MountKind } from './models/mount-types';
@@ -80,6 +81,7 @@ fetchConfig()
         store.subscribe(initListener(history, store, services, config));
         store.dispatch(initAuth());
         store.dispatch(setCurrentTokenDialogApiHost(apiHost));
+        store.dispatch(setUuidPrefix(config.uuidPrefix));
 
         const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
         const MainPanelComponent = (props: any) => <MainPanel buildInfo={buildInfo} {...props} />;
index 33e0bef753c6f5535a18a02e94b7e8e3012052ab..97613147cc7087a491f6ba5682008141ff81ce96 100644 (file)
@@ -4,11 +4,10 @@
 
 import { History, Location } from 'history';
 import { RootStore } from '~/store/store';
-import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute } from './routes';
+import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchWorkflowRoute } from './routes';
 import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions';
 import { navigateToRootProject } from '~/store/navigation/navigation-action';
-import { navigateToSharedWithMe } from '../store/navigation/navigation-action';
-import { loadSharedWithMe } from '../store/workbench/workbench-actions';
+import { loadSharedWithMe, loadWorkflow } from '~/store/workbench/workbench-actions';
 
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
@@ -25,6 +24,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const processMatch = matchProcessRoute(pathname);
     const processLogMatch = matchProcessLogRoute(pathname);
     const sharedWithMeMatch = matchSharedWithMeRoute(pathname);
+    const workflowMatch = matchWorkflowRoute(pathname);
 
     if (projectMatch) {
         store.dispatch(loadProject(projectMatch.params.id));
@@ -42,5 +42,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(navigateToRootProject);
     } else if (sharedWithMeMatch) {
         store.dispatch(loadSharedWithMe);
+    } else if (workflowMatch) {
+        store.dispatch(loadWorkflow);
     }
 };
index fb28bd05bee5ff360c17cedd01bf2487d7cf4f22..34b15e1132f18cd35c6ec24b04c299ae16cf8925 100644 (file)
@@ -17,6 +17,7 @@ export const Routes = {
     TRASH: '/trash',
     PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
     SHARED_WITH_ME: '/shared-with-me',
+    WORKFLOWS: '/workflows'
 };
 
 export const getResourceUrl = (uuid: string) => {
@@ -64,3 +65,6 @@ export const matchProcessLogRoute = (route: string) =>
 
 export const matchSharedWithMeRoute = (route: string) =>
     matchPath(route, { path: Routes.SHARED_WITH_ME });
+
+export const matchWorkflowRoute = (route: string) =>
+    matchPath<ResourceRouteParams>(route, { path: Routes.WORKFLOWS });
index 943f38ce98b4eb633b6d369c2bcc2313d85d932f..c8a554c7651ea5d5f955ab11bb91051ee96626b2 100644 (file)
@@ -26,6 +26,8 @@ export const navigateTo = (uuid: string) =>
             dispatch<any>(navigateToFavorites);
         } else if (uuid === SidePanelTreeCategory.SHARED_WITH_ME) {
             dispatch(navigateToSharedWithMe);
+        } else if (uuid === SidePanelTreeCategory.WORKFLOWS) {
+            dispatch(navigateToWorkflows);
         } else if (uuid === SidePanelTreeCategory.TRASH) {
             dispatch(navigateToTrash);
         }
@@ -35,6 +37,8 @@ export const navigateToFavorites = push(Routes.FAVORITES);
 
 export const navigateToTrash = push(Routes.TRASH);
 
+export const navigateToWorkflows = push(Routes.WORKFLOWS);
+
 export const navigateToProject = compose(push, getProjectUrl);
 
 export const navigateToCollection = compose(push, getCollectionUrl);
index afa2e35e8d7c6555055ba053c0bfaeadb6145d84..34ea42f59bd93796bb355da70d3291cc91133906 100644 (file)
@@ -10,6 +10,7 @@ import { getCommonResourceServiceError, CommonResourceServiceError } from "~/ser
 import { ServiceRepository } from "~/services/services";
 import { ProjectResource } from '~/models/project';
 import { ContextMenuResource } from "~/store/context-menu/context-menu-actions";
+import { getResource } from '~/store/resources/resources';
 
 export interface ProjectUpdateFormDialogData {
     uuid: string;
@@ -20,8 +21,9 @@ export interface ProjectUpdateFormDialogData {
 export const PROJECT_UPDATE_FORM_NAME = 'projectUpdateFormName';
 
 export const openProjectUpdateDialog = (resource: ContextMenuResource) =>
-    (dispatch: Dispatch) => {
-        dispatch(initialize(PROJECT_UPDATE_FORM_NAME, resource));
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const project = getResource(resource.uuid)(getState().resources);
+        dispatch(initialize(PROJECT_UPDATE_FORM_NAME, project));
         dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_UPDATE_FORM_NAME, data: {} }));
     };
 
diff --git a/src/store/rich-text-editor-dialog/rich-text-editor-dialog-actions.tsx b/src/store/rich-text-editor-dialog/rich-text-editor-dialog-actions.tsx
new file mode 100644 (file)
index 0000000..f2a1c4b
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+
+export const RICH_TEXT_EDITOR_DIALOG_NAME = 'richTextEditorDialogName';
+export const openRichTextEditorDialog = (title: string, text: string) => 
+    dialogActions.OPEN_DIALOG({ id: RICH_TEXT_EDITOR_DIALOG_NAME, data: { title, text } });
\ No newline at end of file
index 1ebb13ecb05053cfb06336f9a5c5bf9ca65dd687..c26a7a5aa7eb7cb846393bd5a67af79f2b016eb1 100644 (file)
@@ -37,7 +37,6 @@ export class SharedWithMeMiddlewareService extends DataExplorerMiddlewareService
         } catch (e) {
             api.dispatch(couldNotFetchSharedItems());
         }
-
     }
 }
 
index 3fd2d68af6930946c933657a8d59aec8dfdcddc6..22a83dda8c9f71b130e5b8251b3e8d8c321bb714 100644 (file)
@@ -13,7 +13,6 @@ import { getTreePicker, TreePicker } from '../tree-picker/tree-picker';
 import { TreeItemStatus } from "~/components/tree/tree";
 import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
 import { ProjectResource } from '~/models/project';
-import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions';
 import { OrderBuilder } from '../../services/api/order-builder';
 
 export enum SidePanelTreeCategory {
index 2a5fdd0c2d17c1aae6c3455034fd303ddb523a36..fd08ee1319d5b1550bd278591df4997d7d7f247a 100644 (file)
@@ -4,7 +4,7 @@
 
 import { Dispatch } from 'redux';
 import { isSidePanelTreeCategory, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
-import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe } from '../navigation/navigation-action';
+import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe, navigateToWorkflows } from '../navigation/navigation-action';
 import { snackbarActions } from '~/store/snackbar/snackbar-actions';
 
 export const navigateFromSidePanel = (id: string) =>
@@ -24,6 +24,8 @@ const getSidePanelTreeCategoryAction = (id: string) => {
             return navigateToTrash;
         case SidePanelTreeCategory.SHARED_WITH_ME:
             return navigateToSharedWithMe;
+        case SidePanelTreeCategory.WORKFLOWS:
+            return navigateToWorkflows;
         default:
             return sidePanelTreeCategoryNotAvailable(id);
     }
index 012b747425b72e714472a5b2a0cfe89d03dc2546..16d0d055e30d9c100f8985e071ceee1e63768dd8 100644 (file)
@@ -35,6 +35,8 @@ import { processPanelReducer } from '~/store/process-panel/process-panel-reducer
 import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
 import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
 import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
+import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
+import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -60,6 +62,9 @@ export function configureStore(history: History, services: ServiceRepository): R
     const sharedWithMePanelMiddleware = dataExplorerMiddleware(
         new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
     );
+    const workflowPanelMiddleware = dataExplorerMiddleware(
+        new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
+    );
 
     const middlewares: Middleware[] = [
         routerMiddleware(history),
@@ -68,6 +73,7 @@ export function configureStore(history: History, services: ServiceRepository): R
         favoritePanelMiddleware,
         trashPanelMiddleware,
         sharedWithMePanelMiddleware,
+        workflowPanelMiddleware
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
     return createStore(rootReducer, enhancer);
index 94b4b4f5bb7602f811b257edc08ab48289522618..8f034ec0383b4f61cb4e8b5b98ebe688e9da0e4f 100644 (file)
@@ -38,6 +38,8 @@ import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
 import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
 import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
 import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
+import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
 import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer';
 import { ResourceKind, extractUuidKind } from '~/models/resource';
@@ -76,6 +78,7 @@ export const loadWorkbench = () =>
                 dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns }));
                 dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
                 dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
+                dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
                 dispatch<any>(initSidePanelTree());
                 if (router.location) {
                     const match = matchRootRoute(router.location.pathname);
@@ -347,6 +350,11 @@ export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) =
     await dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
 });
 
+export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
+    dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS));
+    await dispatch(loadWorkflowPanel());
+    dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
+});
 const finishLoadingProject = (project: GroupContentsResource | string) =>
     async (dispatch: Dispatch<any>) => {
         const uuid = typeof project === 'string' ? project : project.uuid;
diff --git a/src/store/workflow-panel/workflow-middleware-service.ts b/src/store/workflow-panel/workflow-middleware-service.ts
new file mode 100644 (file)
index 0000000..49d17f4
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ServiceRepository } from '~/services/services';
+import { MiddlewareAPI, Dispatch } from 'redux';
+import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service';
+import { RootState } from '~/store/store';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer';
+import { updateResources } from '~/store/resources/resources-actions';
+import { FilterBuilder } from '~/services/api/filter-builder';
+import { SortDirection } from '~/components/data-table/data-column';
+import { WorkflowPanelColumnNames } from '~/views/workflow-panel/workflow-panel-view';
+import { OrderDirection, OrderBuilder } from '~/services/api/order-builder';
+import { WorkflowResource } from '~/models/workflow';
+import { ListResults } from '~/services/common-service/common-resource-service';
+import { workflowPanelActions } from './workflow-panel-actions';
+
+export class WorkflowMiddlewareService extends DataExplorerMiddlewareService {
+    constructor(private services: ServiceRepository, id: string) {
+        super(id);
+    }
+
+    async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+        const state = api.getState();
+        const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
+        try {
+            const response = await this.services.workflowService.list(getParams(dataExplorer));
+            api.dispatch(updateResources(response.items));
+            api.dispatch(setItems(response));
+        } catch {
+            api.dispatch(couldNotFetchWorkflows());
+        }
+    }
+}
+
+export const getParams = (dataExplorer: DataExplorer) => ({
+    ...dataExplorerToListParams(dataExplorer),
+    order: getOrder(dataExplorer),
+});
+
+export const getOrder = (dataExplorer: DataExplorer) => {
+    const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+    const order = new OrderBuilder<WorkflowResource>();
+    if (sortColumn) {
+        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+            ? OrderDirection.ASC
+            : OrderDirection.DESC;
+        const columnName = sortColumn && sortColumn.name === WorkflowPanelColumnNames.NAME ? "name" : "modifiedAt";
+        return order
+            .addOrder(sortDirection, columnName)
+            .getOrder();
+    } else {
+        return order.getOrder();
+    }
+};
+
+export const setItems = (listResults: ListResults<WorkflowResource>) =>
+    workflowPanelActions.SET_ITEMS({
+        ...listResultsToDataExplorerItemsMeta(listResults),
+        items: listResults.items.map(resource => resource.uuid),
+    });
+
+const couldNotFetchWorkflows = () =>
+    snackbarActions.OPEN_SNACKBAR({
+        message: 'Could not fetch workflows.',
+        kind: SnackbarKind.ERROR
+    });
\ No newline at end of file
diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts
new file mode 100644 (file)
index 0000000..aa79347
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
+import { propertiesActions } from '~/store/properties/properties-actions';
+
+export const WORKFLOW_PANEL_ID = "workflowPanel";
+const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix';
+
+export const workflowPanelActions = bindDataExplorerActions(WORKFLOW_PANEL_ID);
+
+export const loadWorkflowPanel = () =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(workflowPanelActions.REQUEST_ITEMS());
+    };
+
+export const setUuidPrefix = (uuidPrefix: string) =>
+    propertiesActions.SET_PROPERTY({ key: UUID_PREFIX_PROPERTY_NAME, value: uuidPrefix });
+
+export const getUuidPrefix = (state: RootState) =>{
+    return state.properties.uuidPrefix;
+};
\ No newline at end of file
index 755cd7f7b4c7be4b7a1650653c3b7783406c1179..edc472657eb3178036fb0d8b3917dd0600e39ce5 100644 (file)
@@ -9,7 +9,6 @@ export const TAG_KEY_VALIDATION = [require, maxLength(255)];
 export const TAG_VALUE_VALIDATION = [require, maxLength(255)];
 
 export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
-export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
 
 export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
 export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
index ba6c3258f428a3fe882fa47463b623e798f25451..d2acccb78d42be530ed5a86d8a9dd2d07123a280 100644 (file)
@@ -10,7 +10,6 @@ import { WithDialogProps } from '~/store/dialog/with-dialog';
 import { compose } from 'redux';
 import { connect } from 'react-redux';
 import { CurrentTokenDialogData, getCurrentTokenDialogData } from '~/store/current-token-dialog/current-token-dialog-actions';
-import { RootState } from '~/store/store';
 import { DefaultCodeSnippet } from '~/components/default-code-snippet/default-code-snippet';
 
 type CssRules = 'link' | 'paper' | 'button';
index 74c3e64aaacf1139d5a90cff0c5ada69f40b14b2..17f2c77b0a44fdd3b235b1625c9d0ddf39ba430f 100644 (file)
@@ -15,7 +15,7 @@ import { DataColumns } from "~/components/data-table/data-table";
 interface Props {
     id: string;
     onRowClick: (item: any) => void;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
+    onContextMenu?: (event: React.MouseEvent<HTMLElement>, item: any) => void;
     onRowDoubleClick: (item: any) => void;
     extractKey?: (item: any) => React.Key;
 }
index b9cc63c30475edc89797a07de66963806deea154..12e1be7805c61c6f6674e282af0456cd78ce6670 100644 (file)
@@ -3,10 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Grid, Typography, withStyles } from '@material-ui/core';
+import { Grid, Typography, withStyles, Tooltip, IconButton } from '@material-ui/core';
 import { FavoriteStar } from '../favorite-star/favorite-star';
 import { ResourceKind, TrashableResource } from '~/models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, WorkflowIcon, ShareIcon } from '~/components/icon/icon';
 import { formatDate, formatFileSize } from '~/common/formatters';
 import { resourceLabel } from '~/common/labels';
 import { connect } from 'react-redux';
@@ -16,6 +16,9 @@ import { GroupContentsResource } from '~/services/groups-service/groups-service'
 import { getProcess, Process, getProcessStatus, getProcessStatusColor } from '~/store/processes/process';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { compose } from 'redux';
+import { WorkflowResource } from '~/models/workflow';
+import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view';
+import { getUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
 
 export const renderName = (item: { name: string; uuid: string, kind: string }) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
@@ -48,6 +51,8 @@ export const renderIcon = (item: { kind: string }) => {
             return <CollectionIcon />;
         case ResourceKind.PROCESS:
             return <ProcessIcon />;
+        case ResourceKind.WORKFLOW:
+            return <WorkflowIcon />;
         default:
             return <DefaultIcon />;
     }
@@ -57,6 +62,68 @@ export const renderDate = (date?: string) => {
     return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
 };
 
+export const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ownerUuid: string }) =>
+    <Grid container alignItems="center" wrap="nowrap" spacing={16}>
+        <Grid item>
+            {renderIcon(item)}
+        </Grid>
+        <Grid item>
+            <Typography color="primary" style={{ width: '100px' }}>
+                {item.name}
+            </Typography>
+        </Grid>
+    </Grid>;
+
+export const RosurceWorkflowName = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+        return resource || { name: '', uuid: '', kind: '', ownerUuid: '' };
+    })(renderWorkflowName);
+
+const getPublicUuid = (uuidPrefix: string) => {
+    return `${uuidPrefix}-tpzed-anonymouspublic`;
+};
+
+// do share onClick
+export const resourceShare = (uuidPrefix: string, ownerUuid?: string) => {
+    return <Tooltip title="Share">
+        <IconButton onClick={() => undefined}>
+            {ownerUuid === getPublicUuid(uuidPrefix) ? <ShareIcon /> : null}
+        </IconButton>
+    </Tooltip>;
+};
+
+export const ResourceShare = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+        const uuidPrefix = getUuidPrefix(state);
+        return {
+            ownerUuid: resource ? resource.ownerUuid : '',
+            uuidPrefix
+        };
+    })((props: { ownerUuid?: string, uuidPrefix: string }) => resourceShare(props.uuidPrefix, props.ownerUuid));
+
+export const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
+    if (ownerUuid === getPublicUuid(uuidPrefix)) {
+        return renderStatus(ResourceStatus.PUBLIC);
+    } else {
+        return renderStatus(ResourceStatus.PRIVATE);
+    }
+};
+
+const renderStatus = (status: string) =>
+    <Typography noWrap style={{ width: '60px' }}>{status}</Typography>;
+
+export const ResourceWorkflowStatus = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+        const uuidPrefix = getUuidPrefix(state);
+        return {
+            ownerUuid: resource ? resource.ownerUuid : '',
+            uuidPrefix
+        };
+    })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
+
 export const ResourceLastModifiedDate = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
index 1e65ec834bf8e48f75230bea8505d843eda82a63..331952f259e7ff357cd48a2e088007064e5a2a8d 100644 (file)
@@ -10,6 +10,7 @@ import { ResourceKind } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
 import { DetailsData } from "./details-data";
 import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
+import { RichTextEditorLink } from '~/components/rich-text-editor-link/rich-text-editor-link';
 
 export class ProjectDetails extends DetailsData<ProjectResource> {
 
@@ -27,7 +28,15 @@ export class ProjectDetails extends DetailsData<ProjectResource> {
             <DetailsAttribute label='Created at' value={formatDate(this.item.createdAt)} />
             {/* Missing attr */}
             <DetailsAttribute label='File size' value='1.4 GB' />
-            <DetailsAttribute label='Description' value={this.item.description} />
+            <DetailsAttribute label='Description'>
+                {this.item.description ? 
+                    <RichTextEditorLink
+                        title={`Description of ${this.item.name}`}
+                        content={this.item.description} 
+                        label='Show full description' />
+                    : '---'
+                }
+            </DetailsAttribute>
         </div>;
     }
 }
index 6446c763a81300850c026ed3889ed4d86872837a..8243cfe364d45cee3d05fde0b3637a7b13a3838f 100644 (file)
@@ -4,8 +4,8 @@
 
 import * as React from "react";
 import { Field } from "redux-form";
-import { TextField } from "~/components/text-field/text-field";
-import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from "~/validators/validators";
+import { TextField, RichEditorTextField } from "~/components/text-field/text-field";
+import { PROJECT_NAME_VALIDATION } from "~/validators/validators";
 
 export const ProjectNameField = () =>
     <Field
@@ -18,6 +18,5 @@ export const ProjectNameField = () =>
 export const ProjectDescriptionField = () =>
     <Field
         name='description'
-        component={TextField}
-        validate={PROJECT_DESCRIPTION_VALIDATION}
+        component={RichEditorTextField}
         label="Description - optional" />;
index 071b986ab57352050ef411e5cd6db7ecc3bd61ea..6fb419e36710aa187e0f28a79b46fba82ed5f0d7 100644 (file)
@@ -8,25 +8,36 @@ import { DetailsIcon } from "~/components/icon/icon";
 import { Breadcrumbs } from "~/views-components/breadcrumbs/breadcrumbs";
 import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
 import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { matchWorkflowRoute } from '~/routes/routes';
 
 interface MainContentBarProps {
     onDetailsPanelToggle: () => void;
+    buttonVisible: boolean;
 }
 
-export const MainContentBar = connect(undefined, {
-    onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
-})((props: MainContentBarProps) =>
-    <Toolbar>
-        <Grid container>
-            <Grid container item xs alignItems="center">
-                <Breadcrumbs />
-            </Grid>
-            <Grid item>
-                <Tooltip title="Additional Info">
-                    <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
-                        <DetailsIcon />
-                    </IconButton>
-                </Tooltip>
+const isWorkflowPath = ({ router }: RootState) => {
+    const pathname = router.location ? router.location.pathname : '';
+    const match = matchWorkflowRoute(pathname);
+    return !!match;
+};
+
+export const MainContentBar = connect((state: RootState) => ({
+    buttonVisible: !isWorkflowPath(state)
+}), {
+        onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
+    })((props: MainContentBarProps) =>
+        <Toolbar>
+            <Grid container>
+                <Grid container item xs alignItems="center">
+                    <Breadcrumbs />
+                </Grid>
+                <Grid item>
+                    {props.buttonVisible ? <Tooltip title="Additional Info">
+                        <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
+                            <DetailsIcon />
+                        </IconButton>
+                    </Tooltip> : null}
+                </Grid>
             </Grid>
-        </Grid>
-    </Toolbar>);
+        </Toolbar>);
diff --git a/src/views-components/rich-text-editor-dialog/rich-text-editor-dialog.tsx b/src/views-components/rich-text-editor-dialog/rich-text-editor-dialog.tsx
new file mode 100644 (file)
index 0000000..a997355
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, DialogContentText } from "@material-ui/core";
+import { WithDialogProps } from "../../store/dialog/with-dialog";
+import { withDialog } from '~/store/dialog/with-dialog';
+import { RICH_TEXT_EDITOR_DIALOG_NAME } from "~/store/rich-text-editor-dialog/rich-text-editor-dialog-actions";
+import RichTextEditor from 'react-rte';
+
+export interface RichTextEditorDialogDataProps {
+    title: string;
+    text: string;
+}
+
+export const RichTextEditorDialog = withDialog(RICH_TEXT_EDITOR_DIALOG_NAME)(
+    (props: WithDialogProps<RichTextEditorDialogDataProps>) =>
+        <Dialog open={props.open}
+            onClose={props.closeDialog}
+            fullWidth
+            maxWidth='sm'>
+            <DialogTitle>{props.data.title}</DialogTitle>
+            <DialogContent>
+                <RichTextEditor 
+                    value={RichTextEditor.createValueFromString(props.data.text, 'html')}
+                    readOnly={true} />
+            </DialogContent>
+            <DialogActions>
+                <Button
+                    variant='flat'
+                    color='primary'
+                    onClick={props.closeDialog}>
+                    Close
+                </Button>
+            </DialogActions>
+        </Dialog>
+);
\ No newline at end of file
index 78918be0c65c687d5230fa622c215c9e04ca847b..776850ceb8950ffe89b2fa7f1e7fc58414dce685 100644 (file)
@@ -11,6 +11,7 @@ import { ArvadosTheme } from '~/common/custom-theme';
 import { ContextMenu } from "~/views-components/context-menu/context-menu";
 import { FavoritePanel } from "../favorite-panel/favorite-panel";
 import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
+import { RichTextEditorDialog } from '~/views-components/rich-text-editor-dialog/rich-text-editor-dialog';
 import { Snackbar } from '~/views-components/snackbar/snackbar';
 import { CollectionPanel } from '../collection-panel/collection-panel';
 import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
@@ -38,6 +39,7 @@ import { Grid } from '@material-ui/core';
 import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
 import SplitterLayout from 'react-splitter-layout';
 import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
+import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -93,6 +95,7 @@ export const WorkbenchPanel =
                                 <Route path={Routes.TRASH} component={TrashPanel} />
                                 <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
                                 <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
+                                <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
                             </Switch>
                         </Grid>
                     </Grid>
@@ -117,6 +120,7 @@ export const WorkbenchPanel =
             <PartialCopyCollectionDialog />
             <ProcessCommandDialog />
             <RenameFileDialog />
+            <RichTextEditorDialog />
             <Snackbar />
             <UpdateCollectionDialog />
             <UpdateProcessDialog />
diff --git a/src/views/workflow-panel/workflow-description-card.tsx b/src/views/workflow-panel/workflow-description-card.tsx
new file mode 100644 (file)
index 0000000..60e17b6
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Paper } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { WorkflowIcon } from '~/components/icon/icon';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { WorkflowResource } from '~/models/workflow';
+
+export type CssRules = 'root' | 'tab';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        height: '100%',
+    },
+    tab: {
+        minWidth: '50%'
+    }
+});
+
+interface WorkflowDetailsCardDataProps {
+    workflow?: WorkflowResource;
+}
+
+type WorkflowDetailsCardProps = WorkflowDetailsCardDataProps & WithStyles<CssRules>;
+
+export const WorkflowDetailsCard = withStyles(styles)(
+    class extends React.Component<WorkflowDetailsCardProps> {
+        state = {
+            value: 0,
+        };
+
+        handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+            this.setState({ value });
+        }
+
+        render() {
+            const { classes } = this.props;
+            const { value } = this.state;
+            return <Paper className={classes.root}>
+                <Tabs value={value} onChange={this.handleChange} centered={true}>
+                    <Tab className={classes.tab} label="Description" />
+                    <Tab className={classes.tab} label="Inputs" />
+                </Tabs>
+                {value === 0 && <CardContent>
+                    Description
+                    <DataTableDefaultView
+                        icon={WorkflowIcon}
+                        messages={['Please select a workflow to see its description.']} />
+                </CardContent>}
+                {value === 1 && <CardContent>
+                    Inputs
+                </CardContent>}
+            </Paper>;
+        }
+    });
\ No newline at end of file
diff --git a/src/views/workflow-panel/workflow-panel-view.tsx b/src/views/workflow-panel/workflow-panel-view.tsx
new file mode 100644 (file)
index 0000000..8a29cb7
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+import { WorkflowIcon } from '~/components/icon/icon';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions';
+import {
+    ResourceLastModifiedDate,
+    RosurceWorkflowName,
+    ResourceWorkflowStatus,
+    ResourceShare
+} from "~/views-components/data-explorer/renderers";
+import { SortDirection } from '~/components/data-table/data-column';
+import { DataColumns } from '~/components/data-table/data-table';
+import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
+import { Grid } from '@material-ui/core';
+import { WorkflowDetailsCard } from './workflow-description-card';
+
+export enum WorkflowPanelColumnNames {
+    NAME = "Name",
+    AUTHORISATION = "Authorisation",
+    LAST_MODIFIED = "Last modified",
+    SHARE = 'Share'
+}
+
+export interface WorkflowPanelFilter extends DataTableFilterItem {
+    type: ResourceStatus;
+}
+
+interface WorkflowPanelDataProps {
+    handleRowDoubleClick: any;
+    handleRowClick: any;
+}
+
+export enum ResourceStatus {
+    PUBLIC = "Public",
+    PRIVATE = "Private",
+    SHARED = "Shared"
+}
+
+const resourceStatus = (type: string) => {
+    switch (type) {
+        case ResourceStatus.PUBLIC:
+            return "Public";
+        case ResourceStatus.PRIVATE:
+            return "Private";
+        case ResourceStatus.SHARED:
+            return "Shared";
+        default:
+            return "Unknown";
+    }
+};
+
+export const workflowPanelColumns: DataColumns<string, WorkflowPanelFilter> = [
+    {
+        name: WorkflowPanelColumnNames.NAME,
+        selected: true,
+        configurable: true,
+        sortDirection: SortDirection.ASC,
+        filters: [],
+        render: (uuid: string) => <RosurceWorkflowName uuid={uuid} />
+    },
+    {
+        name: WorkflowPanelColumnNames.AUTHORISATION,
+        selected: true,
+        configurable: true,
+        filters: [
+            {
+                name: resourceStatus(ResourceStatus.PUBLIC),
+                selected: true,
+                type: ResourceStatus.PUBLIC
+            },
+            {
+                name: resourceStatus(ResourceStatus.PRIVATE),
+                selected: true,
+                type: ResourceStatus.PRIVATE
+            },
+            {
+                name: resourceStatus(ResourceStatus.SHARED),
+                selected: true,
+                type: ResourceStatus.SHARED
+            }
+        ],
+        render: (uuid: string) => <ResourceWorkflowStatus uuid={uuid} />,
+    },
+    {
+        name: WorkflowPanelColumnNames.LAST_MODIFIED,
+        selected: true,
+        configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: [],
+        render: (uuid: string) => <ResourceLastModifiedDate uuid={uuid} />
+    },
+    {
+        name: '',
+        selected: true,
+        configurable: false,
+        filters: [],
+        render: (uuid: string) => <ResourceShare uuid={uuid} />
+    }
+];
+
+export const WorkflowPanelView = ({...props}) => {
+    return <Grid container spacing={16}>
+        <Grid item xs={6}>
+            <DataExplorer
+                id={WORKFLOW_PANEL_ID}
+                onRowClick={props.handleRowClick}
+                onRowDoubleClick={props.handleRowDoubleClick}
+                contextMenuColumn={false}
+                dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
+        </Grid>
+        <Grid item xs={6}>
+            <WorkflowDetailsCard />
+        </Grid>
+    </Grid>;
+};
\ No newline at end of file
diff --git a/src/views/workflow-panel/workflow-panel.tsx b/src/views/workflow-panel/workflow-panel.tsx
new file mode 100644 (file)
index 0000000..279097d
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dispatch } from "redux";
+import { connect } from "react-redux";
+import { navigateTo } from '~/store/navigation/navigation-action';
+import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { WorkflowPanelView } from '~/views/workflow-panel/workflow-panel-view';
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+
+    handleRowDoubleClick: (uuid: string) => {
+        dispatch<any>(navigateTo(uuid));
+    },
+    
+    handleRowClick: (uuid: string) => {
+        dispatch(loadDetailsPanel(uuid));
+    }
+});
+
+export const WorkflowPanel= connect(undefined, mapDispatchToProps)(
+    (props) => <WorkflowPanelView {...props}/>);
\ No newline at end of file
index da8e4415d1f6c2d863eb47cb8586ab9214a5ba32..b9f1cc627770e4061f08cccbf10db29e40fefbac 100644 (file)
@@ -12,4 +12,5 @@ declare interface System {
 }
 declare var System: System;
 
-declare module 'react-splitter-layout';
\ No newline at end of file
+declare module 'react-splitter-layout';
+declare module 'react-rte';
\ No newline at end of file
index 230e9f9185bf7c77e574286860cec509624cc314..07c442e11e1584706759fdf334396c3ac1e78238 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -1127,7 +1127,7 @@ babel-register@^6.26.0:
     mkdirp "^0.5.1"
     source-map-support "^0.4.15"
 
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
+babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
@@ -1616,6 +1616,10 @@ clap@^1.0.9:
   dependencies:
     chalk "^1.1.3"
 
+class-autobind@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/class-autobind/-/class-autobind-0.1.4.tgz#34516c49167cf8d3f639ddc186bcfa2268afff34"
+
 class-utils@^0.3.5:
   version "0.3.6"
   resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -2377,6 +2381,50 @@ dotenv@4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
 
+draft-js-export-html@>=0.6.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/draft-js-export-html/-/draft-js-export-html-1.2.0.tgz#1cbe2b78e1fed74fc29c7cdcbfd7540468eca209"
+  dependencies:
+    draft-js-utils "^1.2.0"
+
+draft-js-export-markdown@>=0.3.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/draft-js-export-markdown/-/draft-js-export-markdown-1.2.0.tgz#c423d67389e1c70ddd13e956afe82fd1b72feea5"
+  dependencies:
+    draft-js-utils "^1.2.0"
+
+draft-js-import-element@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/draft-js-import-element/-/draft-js-import-element-1.2.1.tgz#9a6a56d74690d48d35d8d089564e6d710b4926eb"
+  dependencies:
+    draft-js-utils "^1.2.0"
+    synthetic-dom "^1.2.0"
+
+draft-js-import-html@>=0.4.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/draft-js-import-html/-/draft-js-import-html-1.2.1.tgz#88adb8ce5dbe1a5a777663b1893cee6a35239eaa"
+  dependencies:
+    draft-js-import-element "^1.2.1"
+
+draft-js-import-markdown@>=0.3.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-1.2.1.tgz#ec18eb15008bab13d9878d65db50e181dd64a5ce"
+  dependencies:
+    draft-js-import-element "^1.2.1"
+    synthetic-dom "^1.2.0"
+
+draft-js-utils@>=0.2.0, draft-js-utils@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/draft-js-utils/-/draft-js-utils-1.2.0.tgz#f5cb23eb167325ffed3d79882fdc317721d2fd12"
+
+draft-js@>=0.10.0:
+  version "0.10.5"
+  resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742"
+  dependencies:
+    fbjs "^0.8.15"
+    immutable "~3.7.4"
+    object-assign "^4.1.0"
+
 duplexer3@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -2866,7 +2914,7 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
-fbjs@^0.8.1, fbjs@^0.8.16:
+fbjs@^0.8.1, fbjs@^0.8.15, fbjs@^0.8.16:
   version "0.8.17"
   resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
   dependencies:
@@ -3553,6 +3601,14 @@ ignore-walk@^3.0.1:
   dependencies:
     minimatch "^3.0.4"
 
+immutable@^3.8.1:
+  version "3.8.2"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
+
+immutable@~3.7.4:
+  version "3.7.6"
+  resolved "http://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
+
 import-lazy@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -6127,14 +6183,14 @@ react-dev-utils@^5.0.1:
     strip-ansi "3.0.1"
     text-table "0.2.0"
 
-react-dom@16.4.2:
-  version "16.4.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4"
+react-dom@16.5.2:
+  version "16.5.2"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7"
   dependencies:
-    fbjs "^0.8.16"
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    prop-types "^15.6.0"
+    prop-types "^15.6.2"
+    schedule "^0.5.0"
 
 react-dropzone@5.0.1:
   version "5.0.1"
@@ -6224,6 +6280,21 @@ react-router@4.3.1, react-router@^4.2.0, react-router@^4.3.1:
     prop-types "^15.6.1"
     warning "^4.0.1"
 
+react-rte@0.16.1:
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/react-rte/-/react-rte-0.16.1.tgz#e345664c87e992d15ec053b406f51ffac6e86622"
+  dependencies:
+    babel-runtime "^6.23.0"
+    class-autobind "^0.1.4"
+    classnames "^2.2.5"
+    draft-js ">=0.10.0"
+    draft-js-export-html ">=0.6.0"
+    draft-js-export-markdown ">=0.3.0"
+    draft-js-import-html ">=0.4.0"
+    draft-js-import-markdown ">=0.3.0"
+    draft-js-utils ">=0.2.0"
+    immutable "^3.8.1"
+
 react-scripts-ts@2.17.0:
   version "2.17.0"
   resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.17.0.tgz#398bae19a30c9b39b3dfe0720ebb40c60c2f6574"
@@ -6290,14 +6361,14 @@ react-transition-group@2.4.0, react-transition-group@^2.2.1:
     prop-types "^15.6.2"
     react-lifecycles-compat "^3.0.4"
 
-react@16.4.2:
-  version "16.4.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f"
+react@16.5.2:
+  version "16.5.2"
+  resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42"
   dependencies:
-    fbjs "^0.8.16"
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    prop-types "^15.6.0"
+    prop-types "^15.6.2"
+    schedule "^0.5.0"
 
 read-pkg-up@^1.0.1:
   version "1.0.1"
@@ -6745,6 +6816,12 @@ sax@^1.2.4, sax@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
 
+schedule@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.5.0.tgz#c128fffa0b402488b08b55ae74bb9df55cc29cc8"
+  dependencies:
+    object-assign "^4.1.1"
+
 schema-utils@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
@@ -7289,6 +7366,10 @@ symbol-tree@^3.2.2:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
 
+synthetic-dom@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/synthetic-dom/-/synthetic-dom-1.2.0.tgz#f3589aafe2b5e299f337bb32973a9be42dd5625e"
+
 tapable@^0.2.7:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"