This is an automated email from the ASF dual-hosted git repository.

porcelli pushed a commit to branch KOGITO-8015-feature-preview
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-tools-temporary-rnd-do-not-use.git

commit 1ec224401674968dfc4495866695614b8554eafb
Author: Fabrizio Antonangeli <[email protected]>
AuthorDate: Fri May 5 13:14:34 2023 +0200

    KOGITO-8890: Refactor workspace files listing according to UX redesign 
(#1606)
    
    Co-authored-by: Guilherme Caponetto 
<[email protected]>
---
 .../serverless-logic-web-tools/src/AppConstants.ts |   2 +
 .../src/editor/EditorToolbar.tsx                   |   4 +-
 .../src/homepage/overView/Overview.tsx             |   3 +-
 .../src/homepage/pageTemplate/OnlineEditorPage.tsx |  27 +-
 .../homepage/recentModels/ConfirmDeleteModal.tsx   | 154 -----------
 .../src/homepage/recentModels/RecentModels.tsx     | 151 ++++++----
 .../src/homepage/recentModels/WorkspacesTable.tsx  |   7 +-
 .../homepage/recentModels/WorkspacesTableRow.tsx   |  17 +-
 .../recentModels/workspaceFiles/WorkspaceFiles.tsx | 304 +++++++++++++++++++++
 .../workspaceFiles/WorkspaceFilesTable.tsx         | 159 +++++++++++
 .../workspaceFiles/WorkspaceFilesTableRow.tsx      | 100 +++++++
 .../src/homepage/routes/HomePageRoutes.tsx         |   4 +
 .../src/homepage/uiNav/HomePageNav.tsx             |  19 +-
 .../src/navigation/Routes.ts                       |   4 +
 .../src/table/ConfirmDeleteModal.tsx               |  96 +++++++
 .../src/table/TablePagination.tsx                  |  12 +-
 .../src/table/TableToolbar.tsx                     |   3 +
 .../static/resources/style.css                     |   5 +
 18 files changed, 825 insertions(+), 246 deletions(-)

diff --git a/packages/serverless-logic-web-tools/src/AppConstants.ts 
b/packages/serverless-logic-web-tools/src/AppConstants.ts
index 4c8316aa70..8124e259f5 100644
--- a/packages/serverless-logic-web-tools/src/AppConstants.ts
+++ b/packages/serverless-logic-web-tools/src/AppConstants.ts
@@ -15,3 +15,5 @@
  */
 
 export const APP_NAME = "Serverless Logic Web Tools";
+export const SERVERLESS_LOGIC_WEBTOOLS_DOCUMENTATION_URL =
+  
"https://kiegroup.github.io/kogito-docs/serverlessworkflow/latest/tooling/serverless-logic-web-tools/serverless-logic-web-tools-overview.html";;
diff --git a/packages/serverless-logic-web-tools/src/editor/EditorToolbar.tsx 
b/packages/serverless-logic-web-tools/src/editor/EditorToolbar.tsx
index 27d5cdebb4..50a4dc4635 100644
--- a/packages/serverless-logic-web-tools/src/editor/EditorToolbar.tsx
+++ b/packages/serverless-logic-web-tools/src/editor/EditorToolbar.tsx
@@ -1313,7 +1313,7 @@ If you are, it means that creating this Gist failed and 
it can safely be deleted
                   <Button
                     className={"kie-tools--masthead-hoverable"}
                     variant={ButtonVariant.plain}
-                    onClick={() => history.push({ pathname: 
routes.recentModels.path({}) })}
+                    onClick={() => history.push({ pathname: 
routes.workspaceWithFiles.path(props.workspaceFile) })}
                   >
                     <AngleLeftIcon />
                   </Button>
@@ -1512,7 +1512,7 @@ If you are, it means that creating this Gist failed and 
it can safely be deleted
                         toggle={
                           <DropdownToggle
                             onToggle={setNewFileDropdownMenuOpen}
-                            isPrimary={true}
+                            toggleVariant="primary"
                             toggleIndicator={CaretDownIcon}
                           >
                             <PlusIcon />
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/overView/Overview.tsx 
b/packages/serverless-logic-web-tools/src/homepage/overView/Overview.tsx
index b49ecb1f3a..ac72570919 100644
--- a/packages/serverless-logic-web-tools/src/homepage/overView/Overview.tsx
+++ b/packages/serverless-logic-web-tools/src/homepage/overView/Overview.tsx
@@ -36,6 +36,7 @@ import { CardHeader, CardHeaderMain, CardTitle } from 
"@patternfly/react-core/di
 import { List, ListItem } from 
"@patternfly/react-core/dist/js/components/List";
 import { QuickStartContext, QuickStartContextValues } from 
"@patternfly/quickstarts";
 import { ExternalLinkAltIcon } from "@patternfly/react-icons/dist/js/icons";
+import { SERVERLESS_LOGIC_WEBTOOLS_DOCUMENTATION_URL } from 
"../../AppConstants";
 
 export function Overview(props: { isNavOpen: boolean }) {
   const routes = useRoutes();
@@ -106,7 +107,7 @@ export function Overview(props: { isNavOpen: boolean }) {
               target="_blank"
               iconPosition="right"
               icon={<ExternalLinkAltIcon />}
-              
href="https://kiegroup.github.io/kogito-docs/serverlessworkflow/latest/index.html";
+              href={SERVERLESS_LOGIC_WEBTOOLS_DOCUMENTATION_URL}
               variant={ButtonVariant.secondary}
               component="a"
             >
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
index 7d481676b1..e37a20feb0 100644
--- 
a/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
+++ 
b/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
@@ -17,7 +17,6 @@
 import * as React from "react";
 import { QuickStartContainer, QuickStartContainerProps } from 
"@patternfly/quickstarts";
 import { Brand } from "@patternfly/react-core/dist/js/components/Brand";
-import { Button } from "@patternfly/react-core/dist/js/components/Button";
 import {
   Masthead,
   MastheadBrand,
@@ -28,7 +27,7 @@ import {
 import { PageSidebar } from 
"@patternfly/react-core/dist/js/components/Page/PageSidebar";
 import { SkipToContent } from 
"@patternfly/react-core/dist/js/components/SkipToContent";
 import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from 
"@patternfly/react-core/dist/js/components/Toolbar";
-import { Page } from "@patternfly/react-core/dist/js/components/Page";
+import { Page, PageToggleButton } from 
"@patternfly/react-core/dist/js/components/Page";
 import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip";
 import { BarsIcon, ExclamationIcon } from 
"@patternfly/react-icons/dist/js/icons";
 import { useMemo, useState } from "react";
@@ -45,6 +44,7 @@ import {
 } from "../../quickstarts-data";
 import { SettingsButton } from "../../settings/SettingsButton";
 import { HomePageNav } from "../uiNav/HomePageNav";
+import { APP_NAME } from "../../AppConstants";
 
 export type OnlineEditorPageProps = {
   children?: React.ReactNode;
@@ -57,9 +57,6 @@ export function OnlineEditorPage(props: 
OnlineEditorPageProps) {
   const history = useHistory();
   const routes = useRoutes();
   const isRouteInSettingsSection = 
useRouteMatch(routes.settings.home.path({}));
-  const navToggle = () => {
-    props.setIsNavOpen(!props.isNavOpen);
-  };
   const [activeQuickStartID, setActiveQuickStartID] = useState("");
   const [allQuickStartStates, setAllQuickStartStates] = useState({});
 
@@ -120,27 +117,17 @@ export function OnlineEditorPage(props: 
OnlineEditorPageProps) {
   const masthead = (
     <Masthead>
       <MastheadToggle>
-        <Button
-          id="nav-toggle"
-          variant="plain"
-          aria-label="Global NAV"
-          onClick={navToggle}
-          aria-expanded={props.isNavOpen}
-          aria-controls=""
-        >
+        <PageToggleButton variant="plain" aria-label="Global NAV">
           <BarsIcon />
-        </Button>
+        </PageToggleButton>
       </MastheadToggle>
       <MastheadMain>
         <MastheadBrand
           onClick={() => history.push({ pathname: routes.home.path({}) })}
           style={{ textDecoration: "none" }}
         >
-          <Brand
-            className="kogito-tools-common--brand"
-            src="images/kogito_log_workbranch.svg"
-            alt="kogito_logo_white.png"
-          ></Brand>
+          <Brand className="kogito-tools-common--brand" src="favicon.svg" 
alt="Kie logo"></Brand>
+          <div className="brand-name">{APP_NAME}</div>
         </MastheadBrand>
       </MastheadMain>
       <MastheadContent>{headerToolbar}</MastheadContent>
@@ -158,7 +145,7 @@ export function OnlineEditorPage(props: 
OnlineEditorPageProps) {
     [location, isRouteInSettingsSection]
   );
 
-  const sidebar = <PageSidebar nav={pageNav} isNavOpen={props.isNavOpen} 
theme="dark" />;
+  const sidebar = <PageSidebar nav={pageNav} theme="dark" />;
   const mainContainerId = "main-content-page-layout-tertiary-nav";
 
   const pageSkipToContent = <SkipToContent href={`#${mainContainerId}`}>Skip 
to content</SkipToContent>;
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/ConfirmDeleteModal.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/ConfirmDeleteModal.tsx
deleted file mode 100644
index 4d260c06af..0000000000
--- 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/ConfirmDeleteModal.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2023 Red Hat, Inc. and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import * as React from "react";
-import { WorkspaceDescriptor } from 
"@kie-tools-core/workspaces-git-fs/dist/worker/api/WorkspaceDescriptor";
-import { Button, Checkbox, Modal, ModalProps, Skeleton } from 
"@patternfly/react-core/dist/js";
-import { useCallback, useEffect, useMemo, useState } from "react";
-import { useWorkspaces } from 
"@kie-tools-core/workspaces-git-fs/dist/context/WorkspacesContext";
-import { splitFiles } from "../../extension";
-
-export function ConfirmDeleteModal(
-  props: {
-    onDelete: () => void;
-    selectedWorkspaceIds: WorkspaceDescriptor["workspaceId"][];
-  } & Pick<ModalProps, "isOpen" | "onClose">
-) {
-  const { selectedWorkspaceIds, isOpen, onClose, onDelete } = props;
-  const [isDeleteCheck, setIsDeleteCheck] = useState(false);
-  const [firstSelectedWorkspaceName, setFirstSelectedWorkspaceName] = 
useState("");
-  const [selectedFoldersCount, setSelectedFoldersCount] = useState(0);
-  const [elementsTypeName, setElementsTypeName] = useState("models");
-  const [dataLoaded, setDataLoaded] = useState(false);
-  const [fetchError, setFetchError] = useState(false);
-  const workspaces = useWorkspaces();
-
-  const isPlural = useMemo(() => selectedWorkspaceIds.length > 1, 
[selectedWorkspaceIds]);
-
-  const isWsFolder = useCallback(
-    async (workspaceId: WorkspaceDescriptor["workspaceId"]) => {
-      const { editableFiles, readonlyFiles } = splitFiles(await 
workspaces.getFiles({ workspaceId }));
-      return editableFiles.length > 1 || readonlyFiles.length > 0;
-    },
-    [workspaces]
-  );
-
-  const getWorkspaceName = useCallback(
-    async (workspaceId: WorkspaceDescriptor["workspaceId"]) => {
-      if (selectedWorkspaceIds.length !== 1) {
-        return "";
-      }
-      const workspaceData = await workspaces.getWorkspace({ workspaceId });
-      return (await isWsFolder(workspaceId))
-        ? workspaceData.name
-        : (await workspaces.getFiles({ workspaceId }))[0].nameWithoutExtension;
-    },
-    [isWsFolder, selectedWorkspaceIds, workspaces]
-  );
-
-  const onDeleteCheckChange = useCallback((checked: boolean) => {
-    setIsDeleteCheck(checked);
-  }, []);
-
-  useEffect(() => {
-    if (!isOpen) {
-      return;
-    }
-
-    const allPromises: Promise<void>[] = [];
-
-    setIsDeleteCheck(false);
-    setDataLoaded(false);
-    setFetchError(false);
-
-    if (selectedWorkspaceIds.length === 1) {
-      
allPromises.push(getWorkspaceName(selectedWorkspaceIds[0]).then(setFirstSelectedWorkspaceName));
-    }
-
-    allPromises.push(
-      Promise.all(selectedWorkspaceIds.map(isWsFolder)).then((results) => {
-        const foldersCount = results.filter((r) => r).length;
-        setSelectedFoldersCount(foldersCount);
-        if (isPlural) {
-          setElementsTypeName(foldersCount ? "workspaces" : "models");
-        } else {
-          setElementsTypeName(foldersCount ? "workspace" : "model");
-        }
-      })
-    );
-
-    Promise.all(allPromises)
-      .then(() => setDataLoaded(true))
-      .catch((error) => {
-        console.error("Error retrieving workspace data:", error);
-        setFetchError(true);
-      });
-  }, [selectedWorkspaceIds, isWsFolder, isOpen, getWorkspaceName, isPlural]);
-
-  return (
-    <>
-      <Modal
-        title={`Delete ${elementsTypeName}`}
-        titleIconVariant={"warning"}
-        isOpen={isOpen && !fetchError}
-        onClose={onClose}
-        aria-describedby="modal-custom-icon-description"
-        actions={[
-          dataLoaded ? (
-            <Button key="confirm" variant="danger" onClick={onDelete} 
isDisabled={!isDeleteCheck} aria-label="Delete">
-              Delete {elementsTypeName}
-            </Button>
-          ) : (
-            <Skeleton width="100px" key="confirm-skeleton" />
-          ),
-          <Button key="cancel" variant="link" onClick={onClose} 
aria-label="Cancel">
-            Cancel
-          </Button>,
-        ]}
-        variant="small"
-      >
-        {dataLoaded ? (
-          <span id="modal-custom-icon-description">
-            Deleting {isPlural ? "these" : "this"}{" "}
-            <b>{isPlural ? selectedWorkspaceIds.length : 
firstSelectedWorkspaceName}</b> {elementsTypeName}
-            {selectedFoldersCount ? ` removes the ${elementsTypeName} and all 
the models inside.` : "."}
-          </span>
-        ) : (
-          <Skeleton width="80%" />
-        )}
-        <br />
-        <br />
-        <Checkbox
-          label="I understand that this action cannot be undone."
-          id="delete-model-check"
-          isChecked={isDeleteCheck}
-          onChange={onDeleteCheckChange}
-          aria-label="Confirm checkbox delete model"
-        />
-      </Modal>
-
-      <Modal
-        title={`Error retrieving data`}
-        titleIconVariant={"danger"}
-        isOpen={isOpen && fetchError}
-        onClose={onClose}
-        aria-describedby="modal-custom-icon-description"
-        variant="small"
-      >
-        <span id="modal-custom-icon-description">An error occurred while 
loading the data!</span>
-      </Modal>
-    </>
-  );
-}
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/RecentModels.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/RecentModels.tsx
index 964fce3616..c184cce16c 100644
--- 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/RecentModels.tsx
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/RecentModels.tsx
@@ -15,12 +15,11 @@
  */
 
 import { PromiseStateWrapper } from 
"@kie-tools-core/react-hooks/dist/PromiseState";
+import { useController } from "@kie-tools-core/react-hooks/dist/useController";
 import { useWorkspaces } from 
"@kie-tools-core/workspaces-git-fs/dist/context/WorkspacesContext";
 import { useWorkspaceDescriptorsPromise } from 
"@kie-tools-core/workspaces-git-fs/dist/hooks/WorkspacesHooks";
 import { WorkspaceDescriptor } from 
"@kie-tools-core/workspaces-git-fs/dist/worker/api/WorkspaceDescriptor";
-import { PerPageOptions } from 
"@patternfly/react-core/dist/js/components/Pagination";
-import { Alert, AlertActionCloseButton, AlertProps } from 
"@patternfly/react-core/dist/js/components/Alert";
-import { AlertGroup } from 
"@patternfly/react-core/dist/js/components/AlertGroup";
+import { Alert, AlertActionCloseButton } from 
"@patternfly/react-core/dist/js/components/Alert";
 import { EmptyState, EmptyStateBody, EmptyStateIcon } from 
"@patternfly/react-core/dist/js/components/EmptyState";
 import { Page, PageSection } from 
"@patternfly/react-core/dist/js/components/Page";
 import { Text, TextContent, TextVariants } from 
"@patternfly/react-core/dist/js/components/Text";
@@ -28,39 +27,76 @@ import { Title } from 
"@patternfly/react-core/dist/js/components/Title";
 import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye";
 import { CubesIcon } from "@patternfly/react-icons/dist/js/icons/cubes-icon";
 import * as React from "react";
-import { useCallback, useState } from "react";
-import { ConfirmDeleteModal } from "./ConfirmDeleteModal";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { Alerts, AlertsController, useAlert } from "../../alerts/Alerts";
+import { splitFiles } from "../../extension";
+import { ConfirmDeleteModal } from "../../table/ConfirmDeleteModal";
+import { defaultPerPageOptions, TablePagination } from 
"../../table/TablePagination";
 import { TableToolbar } from "../../table/TableToolbar";
 import { WorkspacesTable } from "./WorkspacesTable";
-import { TablePagination } from "../../table/TablePagination";
-
-const perPageOptions: PerPageOptions[] = [5, 10, 20, 50, 100].map((n) => ({
-  title: n.toString(),
-  value: n,
-}));
 
 export function RecentModels() {
   const workspaceDescriptorsPromise = useWorkspaceDescriptorsPromise();
   const [selectedWorkspaceIds, setSelectedWorkspaceIds] = 
useState<WorkspaceDescriptor["workspaceId"][]>([]);
   const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] = 
useState(false);
-  const [alerts, setAlerts] = useState<Partial<AlertProps>[]>([]);
   const [searchValue, setSearchValue] = React.useState("");
   const [page, setPage] = React.useState(1);
   const [perPage, setPerPage] = React.useState(5);
   const workspaces = useWorkspaces();
+  const [selectedFoldersCount, setSelectedFoldersCount] = useState(0);
+  const [firstSelectedWorkspaceName, setFirstSelectedWorkspaceName] = 
useState("");
+  const [deleteModalDataLoaded, setDeleteModalDataLoaded] = useState(false);
+  const [deleteModalFetchError, setDeleteModalFetchError] = useState(false);
+  const [alerts, alertsRef] = useController<AlertsController>();
+  const isSelectedWorkspacePlural = useMemo(() => selectedWorkspaceIds.length 
> 1, [selectedWorkspaceIds]);
+
+  const selectedElementTypesName = useMemo(() => {
+    if (selectedWorkspaceIds.length > 1) {
+      return selectedFoldersCount ? "workspaces" : "models";
+    }
+    return selectedFoldersCount ? "workspace" : "model";
+  }, [selectedFoldersCount, selectedWorkspaceIds]);
+
+  const deleteModalMessage = useMemo(
+    () => (
+      <>
+        Deleting {isSelectedWorkspacePlural ? "these" : "this"}{" "}
+        <b>{isSelectedWorkspacePlural ? selectedWorkspaceIds.length : 
firstSelectedWorkspaceName}</b>{" "}
+        {selectedElementTypesName}
+        {selectedFoldersCount ? ` removes the ${selectedElementTypesName} and 
all the models inside.` : "."}
+      </>
+    ),
+    [
+      isSelectedWorkspacePlural,
+      selectedWorkspaceIds,
+      firstSelectedWorkspaceName,
+      selectedElementTypesName,
+      selectedFoldersCount,
+    ]
+  );
 
   const onConfirmDeleteModalClose = useCallback(() => 
setIsConfirmDeleteModalOpen(false), []);
 
-  const addAlert = useCallback(
-    (title: string, variant: AlertProps["variant"], key: React.Key = new 
Date().getTime()) => {
-      setAlerts((prevAlerts) => [...prevAlerts, { title, variant, key }]);
-    },
-    []
+  const deleteSuccessAlert = useAlert<{ modelsWord: string }>(
+    alerts,
+    useCallback(({ close }, { modelsWord }) => {
+      return <Alert variant="success" title={`${capitalizeString(modelsWord)} 
deleted successfully`} />;
+    }, []),
+    { durationInSeconds: 2 }
   );
 
-  const removeAlert = useCallback((key: React.Key) => {
-    setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== 
key)]);
-  }, []);
+  const deleteErrorAlert = useAlert<{ modelsWord: string }>(
+    alerts,
+    useCallback(({ close }, { modelsWord }) => {
+      return (
+        <Alert
+          variant="danger"
+          title={`Oops, something went wrong while trying to delete the 
selected ${modelsWord}. Please refresh the page and try again. If the problem 
persists, you can try deleting site data for this application in your browser's 
settings.`}
+          actionClose={<AlertActionCloseButton onClose={close} />}
+        />
+      );
+    }, [])
+  );
 
   const onConfirmDeleteModalDelete = useCallback(
     async (workspaceDescriptors: WorkspaceDescriptor[]) => {
@@ -73,20 +109,17 @@ export function RecentModels() {
           .map((w) => workspaces.deleteWorkspace(w))
       )
         .then(() => {
-          addAlert(`${modelsWord} deleted successfully`, "success");
+          deleteSuccessAlert.show({ modelsWord });
         })
         .catch((e) => {
           console.error(e);
-          addAlert(
-            `Oops, something went wrong while trying to delete the selected 
${modelsWord}. Please refresh the page and try again. If the problem persists, 
you can try deleting site data for this application in your browser's 
settings.`,
-            "danger"
-          );
+          deleteErrorAlert.show({ modelsWord });
         })
         .finally(() => {
           setSelectedWorkspaceIds([]);
         });
     },
-    [selectedWorkspaceIds, addAlert, workspaces]
+    [selectedWorkspaceIds, workspaces, deleteErrorAlert, deleteSuccessAlert]
   );
 
   const onWsToggle = useCallback((workspaceId: 
WorkspaceDescriptor["workspaceId"], checked: boolean) => {
@@ -104,6 +137,39 @@ export function RecentModels() {
     setSearchValue("");
   }, []);
 
+  const isWsFolder = useCallback(
+    async (workspaceId: WorkspaceDescriptor["workspaceId"]) => {
+      const { editableFiles, readonlyFiles } = splitFiles(await 
workspaces.getFiles({ workspaceId }));
+      return editableFiles.length > 1 || readonlyFiles.length > 0;
+    },
+    [workspaces]
+  );
+
+  const getWorkspaceName = useCallback(
+    async (workspaceId: WorkspaceDescriptor["workspaceId"]) => {
+      if (selectedWorkspaceIds.length !== 1) {
+        return "";
+      }
+      const workspaceData = await workspaces.getWorkspace({ workspaceId });
+      return (await isWsFolder(workspaceId))
+        ? workspaceData.name
+        : (await workspaces.getFiles({ workspaceId }))[0].nameWithoutExtension;
+    },
+    [isWsFolder, selectedWorkspaceIds, workspaces]
+  );
+
+  useEffect(() => {
+    Promise.all([
+      Promise.all(selectedWorkspaceIds.map(isWsFolder)).then((results) => {
+        const foldersCount = results.filter((r) => r).length;
+        setSelectedFoldersCount(foldersCount);
+      }),
+      
getWorkspaceName(selectedWorkspaceIds[0]).then(setFirstSelectedWorkspaceName),
+    ])
+      .then(() => setDeleteModalDataLoaded(true))
+      .catch(() => setDeleteModalFetchError(true));
+  }, [getWorkspaceName, selectedWorkspaceIds, isWsFolder]);
+
   return (
     <PromiseStateWrapper
       promise={workspaceDescriptorsPromise}
@@ -113,27 +179,7 @@ export function RecentModels() {
 
         return (
           <>
-            <AlertGroup isToast isLiveRegion>
-              {alerts.map(
-                ({ key, variant, title }) =>
-                  key && (
-                    <Alert
-                      variant={variant}
-                      title={title}
-                      timeout
-                      onTimeout={() => removeAlert(key)}
-                      actionClose={
-                        <AlertActionCloseButton
-                          title={title as string}
-                          variantLabel={`${variant} alert`}
-                          onClose={() => removeAlert(key)}
-                        />
-                      }
-                      key={key}
-                    />
-                  )
-              )}
-            </AlertGroup>
+            <Alerts ref={alertsRef} width={"500px"} />
             <Page>
               <PageSection variant={"light"}>
                 <TextContent>
@@ -157,7 +203,7 @@ export function RecentModels() {
                         setSearchValue={setSearchValue}
                         page={page}
                         perPage={perPage}
-                        perPageOptions={perPageOptions}
+                        perPageOptions={defaultPerPageOptions}
                         setPage={setPage}
                         setPerPage={setPerPage}
                       />
@@ -174,7 +220,7 @@ export function RecentModels() {
                         itemCount={itemCount}
                         page={page}
                         perPage={perPage}
-                        perPageOptions={perPageOptions}
+                        perPageOptions={defaultPerPageOptions}
                         setPage={setPage}
                         setPerPage={setPerPage}
                         variant="bottom"
@@ -196,10 +242,13 @@ export function RecentModels() {
               </PageSection>
             </Page>
             <ConfirmDeleteModal
-              selectedWorkspaceIds={selectedWorkspaceIds}
               isOpen={isConfirmDeleteModalOpen}
               onClose={onConfirmDeleteModalClose}
               onDelete={() => onConfirmDeleteModalDelete(workspaceDescriptors)}
+              elementsTypeName={selectedElementTypesName}
+              deleteMessage={deleteModalMessage}
+              dataLoaded={deleteModalDataLoaded}
+              fetchError={deleteModalFetchError}
             />
           </>
         );
@@ -207,3 +256,5 @@ export function RecentModels() {
     />
   );
 }
+
+const capitalizeString = (value: string) => value.charAt(0).toUpperCase() + 
value.slice(1);
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTable.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTable.tsx
index 9ae35c5f1a..11fde904d4 100644
--- 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTable.tsx
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTable.tsx
@@ -90,11 +90,12 @@ export function WorkspacesTable(props: 
WorkspacesTableProps) {
       allWorkspacePromises.status !== PromiseStateStatus.RESOLVED
         ? []
         : workspaceDescriptors.map((workspace, index) => {
-            const { editableFiles, readonlyFiles } = 
splitFiles(allWorkspacePromises.data[index] ?? []);
+            const allFiles = allWorkspacePromises.data[index];
+            const { editableFiles, readonlyFiles } = splitFiles(allFiles ?? 
[]);
             const isWsFolder =
               editableFiles.length > 1 || readonlyFiles.length > 0 || 
workspace.origin.kind !== WorkspaceKind.LOCAL;
-            const hasErrors = !editableFiles || !editableFiles[0];
-            const name = getWorkspaceName(workspace, isWsFolder, hasErrors, 
editableFiles);
+            const hasErrors = !allFiles[0];
+            const name = getWorkspaceName(workspace, isWsFolder, hasErrors, 
allFiles);
 
             return {
               createdDateISO: workspace.createdDateISO,
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTableRow.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTableRow.tsx
index 68eb97634b..84a47ae038 100644
--- 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTableRow.tsx
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/WorkspacesTableRow.tsx
@@ -22,7 +22,6 @@ import { EmptyState, EmptyStateBody, EmptyStateIcon } from 
"@patternfly/react-co
 import { Popover } from "@patternfly/react-core/dist/js/components/Popover";
 import { Title } from "@patternfly/react-core/dist/js/components/Title";
 import { Skeleton } from "@patternfly/react-core/dist/js/components/Skeleton";
-import "@patternfly/react-core/dist/styles/base.css";
 import { ExclamationTriangleIcon, OutlinedQuestionCircleIcon, SearchIcon } 
from "@patternfly/react-icons/dist/js/icons";
 import { FolderIcon } from "@patternfly/react-icons/dist/js/icons/folder-icon";
 import { TaskIcon } from "@patternfly/react-icons/dist/js/icons/task-icon";
@@ -56,12 +55,14 @@ export function WorkspacesTableRow(props: 
WorkspacesTableRowProps) {
 
   const linkTo = useMemo(
     () =>
-      routes.workspaceWithFilePath.path({
-        workspaceId: editableFiles[0].workspaceId,
-        fileRelativePath: editableFiles[0].relativePathWithoutExtension,
-        extension: editableFiles[0].extension,
-      }),
-    [editableFiles]
+      !isWsFolder
+        ? routes.workspaceWithFilePath.path({
+            workspaceId,
+            fileRelativePath: editableFiles[0].relativePathWithoutExtension,
+            extension: editableFiles[0].extension,
+          })
+        : routes.workspaceWithFiles.path({ workspaceId }),
+    [editableFiles, isWsFolder, workspaceId]
   );
 
   return (
@@ -77,7 +78,7 @@ export function WorkspacesTableRow(props: 
WorkspacesTableRowProps) {
         {isWsFolder ? (
           <>
             <FolderIcon />
-            &nbsp;&nbsp;&nbsp;{name}
+            &nbsp;&nbsp;&nbsp;<Link to={linkTo}>{name}</Link>
           </>
         ) : (
           <>
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFiles.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFiles.tsx
new file mode 100644
index 0000000000..e97d8b00be
--- /dev/null
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFiles.tsx
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2023 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { PromiseStateWrapper } from 
"@kie-tools-core/react-hooks/dist/PromiseState";
+import { useController } from "@kie-tools-core/react-hooks/dist/useController";
+import { useWorkspaces, WorkspaceFile } from 
"@kie-tools-core/workspaces-git-fs/dist/context/WorkspacesContext";
+import { useWorkspacePromise } from 
"@kie-tools-core/workspaces-git-fs/dist/hooks/WorkspaceHooks";
+import { ActiveWorkspace } from 
"@kie-tools-core/workspaces-git-fs/dist/model/ActiveWorkspace";
+import { Breadcrumb } from "@patternfly/react-core/components/Breadcrumb";
+import { BreadcrumbItem, Checkbox, Dropdown, DropdownToggle, ToolbarItem } 
from "@patternfly/react-core/dist/js";
+import { Alert, AlertActionCloseButton } from 
"@patternfly/react-core/dist/js/components/Alert";
+import { EmptyState, EmptyStateBody, EmptyStateIcon } from 
"@patternfly/react-core/dist/js/components/EmptyState";
+import { Page, PageSection } from 
"@patternfly/react-core/dist/js/components/Page";
+import { Text, TextContent, TextVariants } from 
"@patternfly/react-core/dist/js/components/Text";
+import { Title } from "@patternfly/react-core/dist/js/components/Title";
+import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye";
+import { CaretDownIcon, PlusIcon } from 
"@patternfly/react-icons/dist/js/icons";
+import { CubesIcon } from "@patternfly/react-icons/dist/js/icons/cubes-icon";
+import * as React from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useHistory } from "react-router";
+import { Alerts, AlertsController, useAlert } from "../../../alerts/Alerts";
+import { NewFileDropdownMenu } from "../../../editor/NewFileDropdownMenu";
+import { splitFiles } from "../../../extension";
+import { routes } from "../../../navigation/Routes";
+import { ConfirmDeleteModal } from "../../../table/ConfirmDeleteModal";
+import { defaultPerPageOptions, TablePagination } from 
"../../../table/TablePagination";
+import { TableToolbar } from "../../../table/TableToolbar";
+import { WorkspaceFilesTable } from "./WorkspaceFilesTable";
+
+export interface Props {
+  workspaceId: string;
+}
+
+export function WorkspaceFiles(props: Props) {
+  const { workspaceId } = props;
+  const workspacePromise = useWorkspacePromise(workspaceId);
+  const [selectedWorkspaceFiles, setSelectedWorkspaceFiles] = 
useState<WorkspaceFile[]>([]);
+  const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] = 
useState(false);
+  const [searchValue, setSearchValue] = React.useState("");
+  const [page, setPage] = React.useState(1);
+  const [perPage, setPerPage] = React.useState(5);
+  const [isViewRoFilesChecked, setIsViewRoFilesChecked] = useState(false);
+  const [isNewFileDropdownMenuOpen, setNewFileDropdownMenuOpen] = 
useState(false);
+  const workspaces = useWorkspaces();
+  const history = useHistory();
+  const [alerts, alertsRef] = useController<AlertsController>();
+  const isSelectedWorkspaceFilesPlural = useMemo(() => 
selectedWorkspaceFiles.length > 1, [selectedWorkspaceFiles]);
+  const selectedElementTypesName = useMemo(
+    () => (isSelectedWorkspaceFilesPlural ? "files" : "file"),
+    [isSelectedWorkspaceFilesPlural]
+  );
+
+  const deleteModalMessage = useMemo(
+    () => (
+      <>
+        Deleting {isSelectedWorkspaceFilesPlural ? "these" : "this"}{" "}
+        <b>{isSelectedWorkspaceFilesPlural ? selectedWorkspaceFiles.length : 
selectedWorkspaceFiles[0]?.name}</b>{" "}
+        {selectedElementTypesName}
+      </>
+    ),
+    [isSelectedWorkspaceFilesPlural, selectedWorkspaceFiles, 
selectedElementTypesName]
+  );
+
+  const onConfirmDeleteModalClose = useCallback(() => 
setIsConfirmDeleteModalOpen(false), []);
+
+  const deleteSuccessAlert = useAlert<{ selectedElementTypesName: string }>(
+    alerts,
+    useCallback(({ close }, { selectedElementTypesName }) => {
+      return <Alert variant="success" 
title={`${capitalizeString(selectedElementTypesName)} deleted successfully`} />;
+    }, []),
+    { durationInSeconds: 2 }
+  );
+
+  const deleteErrorAlert = useAlert<{ selectedElementTypesName: string }>(
+    alerts,
+    useCallback(({ close }, { selectedElementTypesName }) => {
+      return (
+        <Alert
+          variant="danger"
+          title={`Oops, something went wrong while trying to delete the 
selected ${selectedElementTypesName}. Please refresh the page and try again. If 
the problem persists, you can try deleting site data for this application in 
your browser's settings.`}
+          actionClose={<AlertActionCloseButton onClose={close} />}
+        />
+      );
+    }, [])
+  );
+
+  const onConfirmDeleteModalDelete = useCallback(
+    async (totalFilesCount: number) => {
+      setIsConfirmDeleteModalOpen(false);
+
+      if (selectedWorkspaceFiles.length === totalFilesCount) {
+        workspaces.deleteWorkspace({ workspaceId });
+        history.push({ pathname: routes.recentModels.path({}) });
+        deleteSuccessAlert.show({ selectedElementTypesName });
+        return;
+      }
+
+      Promise.all(selectedWorkspaceFiles.map((file) => workspaces.deleteFile({ 
file })))
+        .then(() => {
+          deleteSuccessAlert.show({ selectedElementTypesName });
+        })
+        .catch((e) => {
+          console.error(e);
+          deleteErrorAlert.show({ selectedElementTypesName });
+        })
+        .finally(() => {
+          setSelectedWorkspaceFiles([]);
+        });
+    },
+    [
+      selectedWorkspaceFiles,
+      workspaces,
+      history,
+      workspaceId,
+      deleteErrorAlert,
+      deleteSuccessAlert,
+      selectedElementTypesName,
+    ]
+  );
+
+  const onFileToggle = useCallback((workspaceFile: WorkspaceFile, checked: 
boolean) => {
+    setSelectedWorkspaceFiles((prevSelected) => {
+      const otherSelectedFiles = [...prevSelected.filter((f) => f !== 
workspaceFile)];
+      return checked ? [...otherSelectedFiles, workspaceFile] : 
otherSelectedFiles;
+    });
+  }, []);
+
+  const onToggleAllElements = useCallback((checked: boolean, files: 
WorkspaceFile[]) => {
+    setSelectedWorkspaceFiles(checked ? files : []);
+  }, []);
+
+  const handleViewRoCheckboxChange = useCallback((checked: boolean) => {
+    setIsViewRoFilesChecked(checked);
+  }, []);
+
+  useEffect(() => {
+    setSelectedWorkspaceFiles([]);
+  }, [workspacePromise]);
+
+  return (
+    <PromiseStateWrapper
+      promise={workspacePromise}
+      rejected={(e) => <>Error fetching workspaces: {e + ""}</>}
+      resolved={(workspace: ActiveWorkspace) => {
+        const allFiles = splitFiles(workspace.files);
+        const isViewRoFilesDisabled = !allFiles.editableFiles.length || 
!allFiles.readonlyFiles.length;
+        const isViewRoFilesCheckedInternal = isViewRoFilesDisabled ? true : 
isViewRoFilesChecked;
+        const files = [...allFiles.editableFiles, 
...(isViewRoFilesCheckedInternal ? allFiles.readonlyFiles : [])];
+        const filesCount = files.length;
+        const allFilesCount = workspace.files.length;
+
+        return (
+          <>
+            <Alerts ref={alertsRef} width={"500px"} />
+            <Page
+              breadcrumb={
+                <Breadcrumb>
+                  <BreadcrumbItem to={"#" + 
routes.recentModels.path({})}>Recent Models</BreadcrumbItem>
+                  <BreadcrumbItem to="#" isActive>
+                    {workspace.descriptor.name}
+                  </BreadcrumbItem>
+                </Breadcrumb>
+              }
+            >
+              <PageSection variant={"light"}>
+                <TextContent>
+                  <Text component={TextVariants.h1}>Files in 
&lsquo;{workspace.descriptor.name}&rsquo;</Text>
+                  <Text component={TextVariants.p}>
+                    Use your recent models from GitHub Repository, a GitHub 
Gist or saved in your browser.
+                  </Text>
+                </TextContent>
+              </PageSection>
+
+              <PageSection isFilled aria-label="workspaces-table-section">
+                <PageSection variant={"light"} padding={{ default: "noPadding" 
}}>
+                  {filesCount > 0 && (
+                    <>
+                      <TableToolbar
+                        itemCount={filesCount}
+                        onDeleteActionButtonClick={() => 
setIsConfirmDeleteModalOpen(true)}
+                        onToggleAllElements={(checked) => 
onToggleAllElements(checked, files)}
+                        searchValue={searchValue}
+                        selectedElementsCount={selectedWorkspaceFiles.length}
+                        setSearchValue={setSearchValue}
+                        page={page}
+                        perPage={perPage}
+                        perPageOptions={defaultPerPageOptions}
+                        setPage={setPage}
+                        setPerPage={setPerPage}
+                        additionalComponents={
+                          <>
+                            <ToolbarItem>
+                              <Dropdown
+                                position={"right"}
+                                isOpen={isNewFileDropdownMenuOpen}
+                                toggle={
+                                  <DropdownToggle
+                                    onToggle={setNewFileDropdownMenuOpen}
+                                    toggleIndicator={CaretDownIcon}
+                                    toggleVariant="primary"
+                                  >
+                                    <PlusIcon />
+                                    &nbsp;&nbsp;New file
+                                  </DropdownToggle>
+                                }
+                              >
+                                <NewFileDropdownMenu
+                                  alerts={alerts}
+                                  workspaceId={workspaceId}
+                                  destinationDirPath={""}
+                                  onAddFile={async (file) => {
+                                    setNewFileDropdownMenuOpen(false);
+                                    if (!file) {
+                                      return;
+                                    }
+
+                                    history.push({
+                                      pathname: 
routes.workspaceWithFilePath.path({
+                                        workspaceId: file.workspaceId,
+                                        fileRelativePath: 
file.relativePathWithoutExtension,
+                                        extension: file.extension,
+                                      }),
+                                    });
+                                  }}
+                                />
+                              </Dropdown>
+                            </ToolbarItem>
+                            <ToolbarItem>
+                              <Checkbox
+                                id="viewRoFiles"
+                                label="View readonly files"
+                                isChecked={isViewRoFilesCheckedInternal}
+                                isDisabled={isViewRoFilesDisabled}
+                                onChange={handleViewRoCheckboxChange}
+                              ></Checkbox>
+                            </ToolbarItem>
+                          </>
+                        }
+                      />
+
+                      <WorkspaceFilesTable
+                        page={page}
+                        perPage={perPage}
+                        onFileToggle={onFileToggle}
+                        searchValue={searchValue}
+                        selectedWorkspaceFiles={selectedWorkspaceFiles}
+                        totalFilesCount={allFilesCount}
+                        workspaceFiles={files}
+                      />
+
+                      <TablePagination
+                        itemCount={filesCount}
+                        page={page}
+                        perPage={perPage}
+                        perPageOptions={defaultPerPageOptions}
+                        setPage={setPage}
+                        setPerPage={setPerPage}
+                        variant="bottom"
+                      />
+                    </>
+                  )}
+                  {files.length === 0 && (
+                    <Bullseye>
+                      <EmptyState>
+                        <EmptyStateIcon icon={CubesIcon} />
+                        <Title headingLevel="h4" size="lg">
+                          {`Nothing here`}
+                        </Title>
+                        <EmptyStateBody>{`Start by adding a new 
model`}</EmptyStateBody>
+                      </EmptyState>
+                    </Bullseye>
+                  )}
+                </PageSection>
+              </PageSection>
+            </Page>
+            <ConfirmDeleteModal
+              isOpen={isConfirmDeleteModalOpen}
+              onClose={onConfirmDeleteModalClose}
+              onDelete={() => 
onConfirmDeleteModalDelete(workspace.files.length)}
+              elementsTypeName={selectedElementTypesName}
+              deleteMessage={deleteModalMessage}
+            />
+          </>
+        );
+      }}
+    />
+  );
+}
+
+const capitalizeString = (value: string) => value.charAt(0).toUpperCase() + 
value.slice(1);
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTable.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTable.tsx
new file mode 100644
index 0000000000..c21d7a034a
--- /dev/null
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTable.tsx
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "react";
+import { WorkspaceFile } from 
"@kie-tools-core/workspaces-git-fs/dist/context/WorkspacesContext";
+import "@patternfly/react-core/dist/styles/base.css";
+import {
+  TableComposable,
+  Tbody,
+  Th,
+  Thead,
+  ThProps,
+  Tr,
+} from "@patternfly/react-table/dist/js/components/TableComposable";
+import { useCallback, useMemo, useState } from "react";
+import { isEditable } from "../../../extension";
+import { TablePaginationProps } from "../../../table/TablePagination";
+import { WorkspaceFilesTableRow } from "./WorkspaceFilesTableRow";
+
+export const columnNames = {
+  name: "Name",
+  type: "Type",
+  isEditable: "Editable",
+};
+
+export type WorkspaceFilesTableProps = Pick<TablePaginationProps, "page" | 
"perPage"> & {
+  onFileToggle: (workspaceFile: WorkspaceFile, checked: boolean) => void;
+  searchValue: string;
+  selectedWorkspaceFiles: WorkspaceFile[];
+  /**
+   * total files count
+   */
+  totalFilesCount: number;
+  workspaceFiles: WorkspaceFile[];
+};
+
+export type WorkspaceFilesTableRowData = Pick<WorkspaceFile, "extension"> & {
+  fileDescriptor: WorkspaceFile;
+  isEditable: boolean;
+  name: string;
+};
+
+export function WorkspaceFilesTable(props: WorkspaceFilesTableProps) {
+  const { workspaceFiles, selectedWorkspaceFiles, searchValue, page, perPage, 
totalFilesCount } = props;
+  const [activeSortIndex, setActiveSortIndex] = useState<number>(0);
+  const [activeSortDirection, setActiveSortDirection] = useState<"asc" | 
"desc">("desc");
+
+  const tableData = useMemo<WorkspaceFilesTableRowData[]>(
+    () =>
+      workspaceFiles.map((f) => ({
+        extension: f.extension,
+        fileDescriptor: f,
+        isEditable: isEditable(f.relativePath),
+        name: f.nameWithoutExtension,
+        relativePath: f.relativePath,
+        workspaceId: f.workspaceId,
+      })),
+    [workspaceFiles]
+  );
+
+  const filteredTableData = useMemo<WorkspaceFilesTableRowData[]>(() => {
+    const searchRegex = new RegExp(searchValue, "i");
+    return searchValue ? tableData.filter((e) => e.name.search(searchRegex) >= 
0) : tableData;
+  }, [searchValue, tableData]);
+
+  const sortedTableData = useMemo<WorkspaceFilesTableRowData[]>(
+    () =>
+      // slice() here is needed to create a copy of filteredTableData and sort 
the data
+      filteredTableData.slice().sort((a, b) => {
+        const aValue = getSortableRowValues(a)[activeSortIndex];
+        const bValue = getSortableRowValues(b)[activeSortIndex];
+        if (typeof aValue === "number" || typeof aValue === "boolean") {
+          return activeSortDirection === "asc"
+            ? (aValue as number) - (bValue as number)
+            : (bValue as number) - (aValue as number);
+        } else {
+          return activeSortDirection === "asc"
+            ? (aValue as string).localeCompare(bValue as string)
+            : (bValue as string).localeCompare(aValue as string);
+        }
+      }),
+    [filteredTableData, activeSortIndex, activeSortDirection]
+  );
+
+  const visibleTableData = useMemo<WorkspaceFilesTableRowData[]>(
+    () => sortedTableData.slice((page - 1) * perPage, page * perPage),
+    [sortedTableData, page, perPage]
+  );
+
+  const getSortParams = useCallback(
+    (columnIndex: number): ThProps["sort"] => ({
+      sortBy: {
+        index: activeSortIndex,
+        direction: activeSortDirection,
+        defaultDirection: "asc",
+      },
+      onSort: (_event, index, direction) => {
+        setActiveSortIndex(index);
+        setActiveSortDirection(direction);
+      },
+      columnIndex,
+    }),
+    [activeSortIndex, activeSortDirection]
+  );
+
+  const isFileCheckboxChecked = useCallback(
+    (rowData: WorkspaceFilesTableRowData) =>
+      selectedWorkspaceFiles.some(
+        (f) =>
+          f.workspaceId === rowData.fileDescriptor.workspaceId && 
f.relativePath === rowData.fileDescriptor.relativePath
+      ),
+    [selectedWorkspaceFiles]
+  );
+
+  return (
+    <>
+      <TableComposable aria-label="Selectable table">
+        <Thead>
+          <Tr>
+            <Th>&nbsp;</Th>
+            <Th sort={getSortParams(0)}>{columnNames.name}</Th>
+            <Th sort={getSortParams(1)}>{columnNames.type}</Th>
+            <Th sort={getSortParams(2)}>{columnNames.isEditable}</Th>
+            <Th></Th>
+          </Tr>
+        </Thead>
+        <Tbody>
+          {visibleTableData.map((rowData, rowIndex) => (
+            <WorkspaceFilesTableRow
+              totalFilesCount={totalFilesCount}
+              isSelected={isFileCheckboxChecked(rowData)}
+              key={rowIndex}
+              onToggle={(checked) => 
props.onFileToggle(rowData.fileDescriptor, checked)}
+              rowData={rowData}
+              rowIndex={rowIndex}
+            />
+          ))}
+        </Tbody>
+      </TableComposable>
+    </>
+  );
+}
+
+function getSortableRowValues(tableData: WorkspaceFilesTableRowData): (string 
| number | boolean)[] {
+  const { name, extension, isEditable } = tableData;
+  return [name, extension, isEditable];
+}
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTableRow.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTableRow.tsx
new file mode 100644
index 0000000000..0b74f4926c
--- /dev/null
+++ 
b/packages/serverless-logic-web-tools/src/homepage/recentModels/workspaceFiles/WorkspaceFilesTableRow.tsx
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "react";
+import { useWorkspaces } from 
"@kie-tools-core/workspaces-git-fs/dist/context/WorkspacesContext";
+import { BanIcon, CheckCircleIcon } from 
"@patternfly/react-icons/dist/js/icons";
+import { TaskIcon } from "@patternfly/react-icons/dist/js/icons/task-icon";
+import { ActionsColumn, Td, Tr } from "@patternfly/react-table/dist/esm";
+import { TdSelectType } from 
"@patternfly/react-table/dist/esm/components/Table/base";
+import { useCallback, useMemo } from "react";
+import { Link, useHistory } from "react-router-dom";
+import { routes } from "../../../navigation/Routes";
+import "../../../table/Table.css";
+import { FileLabel } from "../../../workspace/components/FileLabel";
+import { columnNames, WorkspaceFilesTableRowData } from 
"./WorkspaceFilesTable";
+
+export const workspacesTableRowErrorContent = "Error obtaining workspace 
information";
+
+export type WorkspaceFilesTableRowProps = {
+  /**
+   * total files count
+   */
+  totalFilesCount: number;
+  isSelected: boolean;
+  /**
+   * event fired when the Checkbox is toggled
+   */
+  onToggle: (selected: boolean) => void;
+  rowIndex: TdSelectType["rowIndex"];
+  rowData: WorkspaceFilesTableRowData;
+};
+
+export function WorkspaceFilesTableRow(props: WorkspaceFilesTableRowProps) {
+  const { isSelected, rowIndex, totalFilesCount } = props;
+  const { name, extension, isEditable, fileDescriptor } = props.rowData;
+  const workspaces = useWorkspaces();
+  const history = useHistory();
+
+  const linkTo = useMemo(
+    () =>
+      routes.workspaceWithFilePath.path({
+        workspaceId: fileDescriptor.workspaceId,
+        fileRelativePath: fileDescriptor.relativePathWithoutExtension,
+        extension: fileDescriptor.extension,
+      }),
+    [fileDescriptor]
+  );
+
+  const onDelete = useCallback(async () => {
+    if (totalFilesCount > 1) {
+      return await workspaces.deleteFile({ file: fileDescriptor });
+    }
+    workspaces.deleteWorkspace({ workspaceId: fileDescriptor.workspaceId });
+    history.push({ pathname: routes.recentModels.path({}) });
+  }, [fileDescriptor, history, workspaces, totalFilesCount]);
+
+  return (
+    <Tr key={name}>
+      <Td
+        select={{
+          rowIndex,
+          onSelect: (_event, checked) => props.onToggle(checked),
+          isSelected,
+        }}
+      />
+      <Td dataLabel={columnNames.name}>
+        <TaskIcon />
+        &nbsp;&nbsp;&nbsp;<Link to={linkTo}>{name}</Link>
+      </Td>
+      <Td dataLabel={columnNames.type}>
+        <FileLabel extension={extension} />
+      </Td>
+      <Td dataLabel={columnNames.isEditable}>
+        {isEditable ? <CheckCircleIcon 
className="success-icon"></CheckCircleIcon> : <BanIcon></BanIcon>}
+      </Td>
+      <Td isActionCell>
+        <ActionsColumn
+          items={[
+            {
+              title: "Delete",
+              onClick: onDelete,
+            },
+          ]}
+        />
+      </Td>
+    </Tr>
+  );
+}
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/routes/HomePageRoutes.tsx 
b/packages/serverless-logic-web-tools/src/homepage/routes/HomePageRoutes.tsx
index 896194c6cc..78aae5a70f 100644
--- a/packages/serverless-logic-web-tools/src/homepage/routes/HomePageRoutes.tsx
+++ b/packages/serverless-logic-web-tools/src/homepage/routes/HomePageRoutes.tsx
@@ -27,6 +27,7 @@ import { NewWorkspaceWithEmptyFilePage } from 
"../../workspace/components/NewWor
 import { EditorPage } from "../../editor/EditorPage";
 import { NoMatchPage } from "../../navigation/NoMatchPage";
 import { Showcase } from "../../home/sample/Showcase";
+import { WorkspaceFiles } from "../recentModels/workspaceFiles/WorkspaceFiles";
 
 export function HomePageRoutes(props: { isNavOpen: boolean }) {
   const routes = useRoutes();
@@ -64,6 +65,9 @@ export function HomePageRoutes(props: { isNavOpen: boolean }) 
{
       <Route path={routes.recentModels.path({})}>
         <RecentModels />
       </Route>
+      <Route path={routes.workspaceWithFiles.path({ workspaceId: 
":workspaceId" })}>
+        {({ match }) => <WorkspaceFiles 
workspaceId={match!.params.workspaceId!} />}
+      </Route>
       <Route path={routes.sampleCatalog.path({})}>
         <Showcase />
       </Route>
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/uiNav/HomePageNav.tsx 
b/packages/serverless-logic-web-tools/src/homepage/uiNav/HomePageNav.tsx
index 209a55f0d7..dca5bfeac4 100644
--- a/packages/serverless-logic-web-tools/src/homepage/uiNav/HomePageNav.tsx
+++ b/packages/serverless-logic-web-tools/src/homepage/uiNav/HomePageNav.tsx
@@ -16,21 +16,29 @@
 
 import * as React from "react";
 import { Nav, NavItem, NavList } from 
"@patternfly/react-core/dist/js/components/Nav";
-import { Link } from "react-router-dom";
+import { Link, matchPath } from "react-router-dom";
 import { ExternalLinkAltIcon } from "@patternfly/react-icons/dist/js/icons";
 import { routes } from "../../navigation/Routes";
+import { SERVERLESS_LOGIC_WEBTOOLS_DOCUMENTATION_URL } from 
"../../AppConstants";
 
 export function HomePageNav(props: { pathname: string }) {
   return (
     <>
-      <div className="chr-c-app-title">Serverless Logic Web Tools</div>
       <Nav aria-label="Global NAV" theme="dark">
         <NavList>
           <NavItem itemId={0} key={"Overview-nav"} isActive={props.pathname 
=== routes.home.path({})}>
             <Link to={routes.home.path({})}>Overview</Link>
           </NavItem>
 
-          <NavItem itemId={1} key={"Recent-models-nav"} 
isActive={props.pathname === routes.recentModels.path({})}>
+          <NavItem
+            itemId={1}
+            key={"Recent-models-nav"}
+            isActive={
+              props.pathname === routes.recentModels.path({}) ||
+              matchPath(props.pathname, { path: 
routes.workspaceWithFiles.path({ workspaceId: ":workspaceId" }) })
+                ?.isExact
+            }
+          >
             <Link to={routes.recentModels.path({})}>Recent Models</Link>
           </NavItem>
 
@@ -39,10 +47,7 @@ export function HomePageNav(props: { pathname: string }) {
           </NavItem>
 
           <NavItem itemId={3} key={"Documentation-nav"} 
className="chr-c-navigation__additional-links">
-            <a
-              
href="https://kiegroup.github.io/kogito-docs/serverlessworkflow/latest/tooling/serverless-logic-web-tools/serverless-logic-web-tools-overview.html";
-              target="_blank"
-            >
+            <a href={SERVERLESS_LOGIC_WEBTOOLS_DOCUMENTATION_URL} 
target="_blank">
               Documentation
               <ExternalLinkAltIcon />
             </a>
diff --git a/packages/serverless-logic-web-tools/src/navigation/Routes.ts 
b/packages/serverless-logic-web-tools/src/navigation/Routes.ts
index 0d2f7000b2..0c36e8c953 100644
--- a/packages/serverless-logic-web-tools/src/navigation/Routes.ts
+++ b/packages/serverless-logic-web-tools/src/navigation/Routes.ts
@@ -132,6 +132,10 @@ export const routes = {
       `/${workspaceId}/file/${fileRelativePath}${extension ? "." + extension : 
""}`
   ),
 
+  workspaceWithFiles: new Route<{
+    pathParams: PathParams.WORKSPACE_ID;
+  }>(({ workspaceId }) => `/${workspaceId}/files`),
+
   recentModels: new Route<{}>(() => `/RecentModels`),
   sampleCatalog: new Route<{}>(() => `/SampleCatalog`),
 
diff --git 
a/packages/serverless-logic-web-tools/src/table/ConfirmDeleteModal.tsx 
b/packages/serverless-logic-web-tools/src/table/ConfirmDeleteModal.tsx
new file mode 100644
index 0000000000..1b29c29f2d
--- /dev/null
+++ b/packages/serverless-logic-web-tools/src/table/ConfirmDeleteModal.tsx
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Button, Checkbox, Modal, ModalProps, Skeleton } from 
"@patternfly/react-core/dist/js";
+import * as React from "react";
+import { useCallback, useEffect, useState } from "react";
+
+export type ConfirmDeleteModalProps = Pick<ModalProps, "isOpen" | "onClose"> & 
{
+  /**
+   * set to false to manage the loading. Default is true.
+   */
+  dataLoaded?: boolean;
+
+  deleteMessage: React.ReactNode;
+
+  elementsTypeName: string;
+
+  /**
+   * set to true if there has been error loading the data.
+   */
+  fetchError?: boolean;
+
+  onDelete: () => void;
+};
+
+export function ConfirmDeleteModal(props: ConfirmDeleteModalProps) {
+  const { isOpen, onClose, onDelete, elementsTypeName, deleteMessage, 
dataLoaded = true, fetchError = false } = props;
+  const [isDeleteCheck, setIsDeleteCheck] = useState(false);
+
+  const onDeleteCheckChange = useCallback((checked: boolean) => {
+    setIsDeleteCheck(checked);
+  }, []);
+
+  useEffect(() => {
+    setIsDeleteCheck(false);
+  }, [isOpen]);
+
+  return (
+    <>
+      <Modal
+        title={`Delete ${elementsTypeName}`}
+        titleIconVariant={"warning"}
+        isOpen={isOpen && !fetchError}
+        onClose={onClose}
+        aria-describedby="modal-custom-icon-description"
+        actions={[
+          dataLoaded ? (
+            <Button key="confirm" variant="danger" onClick={onDelete} 
isDisabled={!isDeleteCheck} aria-label="Delete">
+              Delete {elementsTypeName}
+            </Button>
+          ) : (
+            <Skeleton width="100px" key="confirm-skeleton" />
+          ),
+          <Button key="cancel" variant="link" onClick={onClose} 
aria-label="Cancel">
+            Cancel
+          </Button>,
+        ]}
+        variant="small"
+      >
+        {dataLoaded ? <span 
id="modal-custom-icon-description">{deleteMessage}</span> : <Skeleton 
width="80%" />}
+        <br />
+        <br />
+        <Checkbox
+          label="I understand that this action cannot be undone."
+          id="delete-model-check"
+          isChecked={isDeleteCheck}
+          onChange={onDeleteCheckChange}
+          aria-label="Confirm checkbox delete model"
+        />
+      </Modal>
+
+      <Modal
+        title={`Error retrieving data`}
+        titleIconVariant={"danger"}
+        isOpen={isOpen && fetchError}
+        onClose={onClose}
+        aria-describedby="modal-custom-icon-description"
+        variant="small"
+      >
+        <span id="modal-custom-icon-description">An error occurred while 
loading the data!</span>
+      </Modal>
+    </>
+  );
+}
diff --git a/packages/serverless-logic-web-tools/src/table/TablePagination.tsx 
b/packages/serverless-logic-web-tools/src/table/TablePagination.tsx
index d438703838..fec23e2675 100644
--- a/packages/serverless-logic-web-tools/src/table/TablePagination.tsx
+++ b/packages/serverless-logic-web-tools/src/table/TablePagination.tsx
@@ -14,10 +14,20 @@
  * limitations under the License.
  */
 import * as React from "react";
-import { OnPerPageSelect, Pagination, PaginationProps } from 
"@patternfly/react-core/dist/js/components/Pagination";
+import {
+  OnPerPageSelect,
+  Pagination,
+  PaginationProps,
+  PerPageOptions,
+} from "@patternfly/react-core/dist/js/components/Pagination";
 import "@patternfly/react-core/dist/styles/base.css";
 import { useCallback } from "react";
 
+export const defaultPerPageOptions: PerPageOptions[] = [5, 10, 20, 50, 
100].map((n) => ({
+  title: n.toString(),
+  value: n,
+}));
+
 export type TablePaginationProps = Pick<PaginationProps, "variant" | 
"isCompact" | "perPageOptions"> & {
   itemCount: number;
   page: number;
diff --git a/packages/serverless-logic-web-tools/src/table/TableToolbar.tsx 
b/packages/serverless-logic-web-tools/src/table/TableToolbar.tsx
index 84286b5f64..31cb56d3ee 100644
--- a/packages/serverless-logic-web-tools/src/table/TableToolbar.tsx
+++ b/packages/serverless-logic-web-tools/src/table/TableToolbar.tsx
@@ -33,6 +33,7 @@ export type TableToolbarProps = TablePaginationProps & {
   searchValue: string;
   selectedElementsCount: number;
   setSearchValue: React.Dispatch<React.SetStateAction<string>>;
+  additionalComponents?: React.ReactNode;
 };
 
 export function TableToolbar(props: TableToolbarProps) {
@@ -48,6 +49,7 @@ export function TableToolbar(props: TableToolbarProps) {
     perPageOptions,
     setPage,
     setPerPage,
+    additionalComponents,
   } = props;
   const [isBulkDropDownOpen, setIsBulkDropDownOpen] = useState(false);
   const [isActionDropdownOpen, setIsActionDropdownOpen] = 
React.useState(false);
@@ -143,6 +145,7 @@ export function TableToolbar(props: TableToolbarProps) {
             dropdownItems={actionDropdownItems}
           />
         </ToolbarItem>
+        {additionalComponents}
         <ToolbarItem variant="pagination">
           <TablePagination
             itemCount={itemCount}
diff --git a/packages/serverless-logic-web-tools/static/resources/style.css 
b/packages/serverless-logic-web-tools/static/resources/style.css
index 9d002919ea..482b25a9d2 100644
--- a/packages/serverless-logic-web-tools/static/resources/style.css
+++ b/packages/serverless-logic-web-tools/static/resources/style.css
@@ -659,6 +659,11 @@ section.kie-tools--settings-tab {
 .pf-c-menu__item-description {
   word-break: normal !important;
 }
+.brand-name {
+  color: #fff;
+  padding: 0 var(--pf-global--spacer--sm) 0 var(--pf-global--spacer--sm);
+  font-weight: var(--pf-global--FontWeight--semi-bold);
+}
 .chr-c-app-title {
   color: #fff;
   padding: var(--pf-global--spacer--md) var(--pf-global--spacer--sm) 
var(--pf-global--spacer--md)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to