Răsfoiți Sursa

Merge pull request #6885 from weseek/imprv/sidebar-skelton-pagetree

imprv: sidebar skelton pagetree
Haku Mizuki 3 ani în urmă
părinte
comite
18dbfe9c76

+ 4 - 2
packages/app/src/components/Sidebar.tsx

@@ -17,6 +17,7 @@ import {
 import DrawerToggler from './Navbar/DrawerToggler';
 import DrawerToggler from './Navbar/DrawerToggler';
 import { NavigationResizeHexagon } from './Sidebar/NavigationResizeHexagon';
 import { NavigationResizeHexagon } from './Sidebar/NavigationResizeHexagon';
 import { SidebarNav } from './Sidebar/SidebarNav';
 import { SidebarNav } from './Sidebar/SidebarNav';
+import { SidebarSkelton } from './Sidebar/Skelton/SidebarSkelton';
 import { StickyStretchableScrollerProps } from './StickyStretchableScroller';
 import { StickyStretchableScrollerProps } from './StickyStretchableScroller';
 
 
 import styles from './Sidebar.module.scss';
 import styles from './Sidebar.module.scss';
@@ -57,8 +58,9 @@ const GlobalNavigation = () => {
 
 
 const SidebarContentsWrapper = () => {
 const SidebarContentsWrapper = () => {
   const StickyStretchableScroller = dynamic<StickyStretchableScrollerProps>(() => import('./StickyStretchableScroller')
   const StickyStretchableScroller = dynamic<StickyStretchableScrollerProps>(() => import('./StickyStretchableScroller')
-    .then(mod => mod.StickyStretchableScroller), { ssr: false });
-  const SidebarContents = dynamic(() => import('./Sidebar/SidebarContents').then(mod => mod.SidebarContents), { ssr: false });
+    .then(mod => mod.StickyStretchableScroller), { ssr: false, loading: () => <SidebarSkelton /> });
+  const SidebarContents = dynamic(() => import('./Sidebar/SidebarContents')
+    .then(mod => mod.SidebarContents), { ssr: false, loading: () => <SidebarSkelton /> });
   const { mutate: mutateSidebarScroller } = useSidebarScrollerRef();
   const { mutate: mutateSidebarScroller } = useSidebarScrollerRef();
 
 
   const calcViewHeight = useCallback(() => {
   const calcViewHeight = useCallback(() => {

+ 7 - 7
packages/app/src/components/Sidebar/CustomSidebar.tsx

@@ -1,6 +1,7 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+import Link from 'next/link';
 
 
 import { IRevision } from '~/interfaces/revision';
 import { IRevision } from '~/interfaces/revision';
 import { useSWRxPageByPath } from '~/stores/page';
 import { useSWRxPageByPath } from '~/stores/page';
@@ -9,6 +10,7 @@ import loggerFactory from '~/utils/logger';
 
 
 import RevisionRenderer from '../Page/RevisionRenderer';
 import RevisionRenderer from '../Page/RevisionRenderer';
 
 
+import { SidebarHeaderReloadButton } from './SidebarHeaderReloadButton';
 
 
 import styles from './CustomSidebar.module.scss';
 import styles from './CustomSidebar.module.scss';
 
 
@@ -19,9 +21,9 @@ const logger = loggerFactory('growi:cli:CustomSidebar');
 const SidebarNotFound = () => {
 const SidebarNotFound = () => {
   return (
   return (
     <div className="grw-sidebar-content-header h5 text-center p-3">
     <div className="grw-sidebar-content-header h5 text-center p-3">
-      <a href="/Sidebar#edit">
-        <i className="icon-magic-wand"></i> Create <strong>/Sidebar</strong> page
-      </a>
+      <Link href="/Sidebar#edit">
+        <a><i className="icon-magic-wand"></i> Create <strong>/Sidebar</strong> page</a>
+      </Link>
     </div>
     </div>
   );
   );
 };
 };
@@ -44,11 +46,9 @@ const CustomSidebar: FC = () => {
       <div className="grw-sidebar-content-header p-3 d-flex">
       <div className="grw-sidebar-content-header p-3 d-flex">
         <h3 className="mb-0">
         <h3 className="mb-0">
           {t('CustomSidebar')}
           {t('CustomSidebar')}
-          <a className="h6 ml-2" href="/Sidebar"><i className="icon-pencil"></i></a>
+          <Link href="/Sidebar"><a className="h6 ml-2"><i className="icon-pencil"></i></a></Link>
         </h3>
         </h3>
-        <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={() => mutate()}>
-          <i className="icon icon-reload"></i>
-        </button>
+        <SidebarHeaderReloadButton onClick={() => mutate()} />
       </div>
       </div>
 
 
       {
       {

+ 15 - 13
packages/app/src/components/Sidebar/PageTree.tsx

@@ -10,6 +10,17 @@ import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
 
 
 import ItemsTree from './PageTree/ItemsTree';
 import ItemsTree from './PageTree/ItemsTree';
 import { PrivateLegacyPagesLink } from './PageTree/PrivateLegacyPagesLink';
 import { PrivateLegacyPagesLink } from './PageTree/PrivateLegacyPagesLink';
+import PageTreeContentSkelton from './Skelton/PageTreeContentSkelton';
+
+const PageTreeHeader = () => {
+  const { t } = useTranslation();
+
+  return (
+    <div className="grw-sidebar-content-header p-3">
+      <h3 className="mb-0">{t('Page Tree')}</h3>
+    </div>
+  );
+};
 
 
 const PageTree: FC = memo(() => {
 const PageTree: FC = memo(() => {
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -25,12 +36,8 @@ const PageTree: FC = memo(() => {
   if (migrationStatus == null) {
   if (migrationStatus == null) {
     return (
     return (
       <>
       <>
-        <div className="grw-sidebar-content-header p-3">
-          <h3 className="mb-0">{t('Page Tree')}</h3>
-        </div>
-        <div className="text-muted text-center mt-3">
-          <i className="fa fa-lg fa-spinner fa-pulse mr-1"></i>
-        </div>
+        <PageTreeHeader />
+        <PageTreeContentSkelton />
       </>
       </>
     );
     );
   }
   }
@@ -40,9 +47,7 @@ const PageTree: FC = memo(() => {
     // Story : https://redmine.weseek.co.jp/issues/83755
     // Story : https://redmine.weseek.co.jp/issues/83755
     return (
     return (
       <>
       <>
-        <div className="grw-sidebar-content-header p-3">
-          <h3 className="mb-0">{t('Page Tree')}</h3>
-        </div>
+        <PageTreeHeader />
         <div className="mt-5 mx-2 text-center">
         <div className="mt-5 mx-2 text-center">
           <h3 className="text-gray">{t('v5_page_migration.page_tree_not_avaliable')}</h3>
           <h3 className="text-gray">{t('v5_page_migration.page_tree_not_avaliable')}</h3>
           <a href="/admin">{t('v5_page_migration.go_to_settings')}</a>
           <a href="/admin">{t('v5_page_migration.go_to_settings')}</a>
@@ -62,10 +67,7 @@ const PageTree: FC = memo(() => {
 
 
   return (
   return (
     <>
     <>
-      <div className="grw-sidebar-content-header p-3">
-        <h3 className="mb-0">{t('Page Tree')}</h3>
-      </div>
-
+      <PageTreeHeader />
       <ItemsTree
       <ItemsTree
         isEnableActions={!isGuestUser}
         isEnableActions={!isGuestUser}
         targetPath={path}
         targetPath={path}

+ 13 - 1
packages/app/src/components/Sidebar/PageTree/ItemsTree.module.scss

@@ -2,8 +2,20 @@
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-footer-height: 50px;
 $grw-sidebar-content-footer-height: 50px;
 $grw-pagetree-item-padding-left: 10px;
 $grw-pagetree-item-padding-left: 10px;
+$grw-pagetree-item-container-height: 40px;
 
 
 .grw-pagetree {
 .grw-pagetree {
+
+  .grw-pagetree-item-skelton {
+    width: 100%;
+    height: $grw-pagetree-item-container-height;
+    padding: (($grw-pagetree-item-container-height - 16px) / 2) $grw-pagetree-item-padding-left;
+  }
+
+  .grw-pagetree-item-skelton-children {
+    padding-left: $grw-pagetree-item-padding-left * 2;
+  }
+
   :global {
   :global {
     min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
     min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
 
 
@@ -56,7 +68,7 @@ $grw-pagetree-item-padding-left: 10px;
     .grw-pagetree-item-container {
     .grw-pagetree-item-container {
       .grw-triangle-container {
       .grw-triangle-container {
         min-width: 35px;
         min-width: 35px;
-        height: 40px;
+        height: $grw-pagetree-item-container-height;
       }
       }
     }
     }
   }
   }

+ 3 - 1
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -24,6 +24,8 @@ import { usePageTreeDescCountMap, useSidebarScrollerRef } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import { useGlobalSocket } from '~/stores/websocket';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import PageTreeContentSkelton from '../Skelton/PageTreeContentSkelton';
+
 import Item from './Item';
 import Item from './Item';
 import { ItemNode } from './ItemNode';
 import { ItemNode } from './ItemNode';
 
 
@@ -286,7 +288,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     );
     );
   }
   }
 
 
-  return <></>;
+  return <PageTreeContentSkelton />;
 };
 };
 
 
 export default ItemsTree;
 export default ItemsTree;

+ 10 - 6
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -16,6 +16,7 @@ import loggerFactory from '~/utils/logger';
 import FormattedDistanceDate from '../FormattedDistanceDate';
 import FormattedDistanceDate from '../FormattedDistanceDate';
 
 
 import InfiniteScroll from './InfiniteScroll';
 import InfiniteScroll from './InfiniteScroll';
+import { SidebarHeaderReloadButton } from './SidebarHeaderReloadButton';
 
 
 import TagLabelsStyles from '../Page/TagLabels.module.scss';
 import TagLabelsStyles from '../Page/TagLabels.module.scss';
 import styles from './RecentChanges.module.scss';
 import styles from './RecentChanges.module.scss';
@@ -130,12 +131,17 @@ const SmallPageItem = memo(({ page }: PageItemProps): JSX.Element => {
 SmallPageItem.displayName = 'SmallPageItem';
 SmallPageItem.displayName = 'SmallPageItem';
 
 
 const RecentChanges = (): JSX.Element => {
 const RecentChanges = (): JSX.Element => {
+
   const PER_PAGE = 20;
   const PER_PAGE = 20;
   const { t } = useTranslation();
   const { t } = useTranslation();
   const swr = useSWRInifinitexRecentlyUpdated();
   const swr = useSWRInifinitexRecentlyUpdated();
+  const { data, error, mutate } = swr;
+
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
-  const isEmpty = swr.data?.[0].length === 0;
-  const isReachingEnd = isEmpty || (swr.data && swr.data[swr.data.length - 1]?.length < PER_PAGE);
+  const isEmpty = data?.[0].length === 0;
+  const isLoading = error == null && data === undefined;
+  const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < PER_PAGE);
+
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
       setIsRecentChangesSidebarSmall(true);
       setIsRecentChangesSidebarSmall(true);
@@ -155,10 +161,8 @@ const RecentChanges = (): JSX.Element => {
   return (
   return (
     <div data-testid="grw-recent-changes">
     <div data-testid="grw-recent-changes">
       <div className="grw-sidebar-content-header p-3 d-flex">
       <div className="grw-sidebar-content-header p-3 d-flex">
-        <h3 className="mb-0  text-nowrap">{t('Recent Changes')}</h3>
-        <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={() => swr.mutate()}>
-          <i className="icon icon-reload"></i>
-        </button>
+        <h3 className="mb-0 text-nowrap">{t('Recent Changes')}</h3>
+        <SidebarHeaderReloadButton onClick={() => mutate()}/>
         <div className="d-flex align-items-center">
         <div className="d-flex align-items-center">
           <div className={`grw-recent-changes-resize-button ${styles['grw-recent-changes-resize-button']} custom-control custom-switch ml-1`}>
           <div className={`grw-recent-changes-resize-button ${styles['grw-recent-changes-resize-button']} custom-control custom-switch ml-1`}>
             <input
             <input

+ 14 - 0
packages/app/src/components/Sidebar/SidebarHeaderReloadButton.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+
+type Props = {
+  onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
+};
+
+export const SidebarHeaderReloadButton = ({ onClick }: Props) => {
+
+  return (
+    <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={onClick}>
+      <i className="icon icon-reload"></i>
+    </button>
+  );
+};

+ 18 - 0
packages/app/src/components/Sidebar/Skelton/PageTreeContentSkelton.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+
+import { Skelton } from '~/components/Skelton';
+
+import styles from '../PageTree/ItemsTree.module.scss';
+
+const PageTreeContentSkelton = (): JSX.Element => {
+
+  return (
+    <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group p-3`} >
+      <Skelton additionalClass={styles['grw-pagetree-item-skelton']} />
+      <Skelton additionalClass={`${styles['grw-pagetree-item-skelton']} ${styles['grw-pagetree-item-skelton-children']}`} />
+      <Skelton additionalClass={`${styles['grw-pagetree-item-skelton']} ${styles['grw-pagetree-item-skelton-children']}`} />
+    </ul>
+  );
+};
+
+export default PageTreeContentSkelton;

+ 5 - 0
packages/app/src/components/Sidebar/Skelton/SidebarHeaderSkelton.module.scss

@@ -0,0 +1,5 @@
+.grw-sidebar-content-header-skelton {
+  max-width: 160px;
+  height: 30px;
+  padding: 7px 0;
+}

+ 14 - 0
packages/app/src/components/Sidebar/Skelton/SidebarHeaderSkelton.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+
+import { Skelton } from '~/components/Skelton';
+
+import styles from './SidebarHeaderSkelton.module.scss';
+
+const SidebarHeaderSkelton = (): JSX.Element => {
+  return (
+    <div className="grw-sidebar-content-header p-3">
+      <Skelton additionalClass={styles['grw-sidebar-content-header-skelton']} />
+    </div>
+  );
+};
+export default SidebarHeaderSkelton;

+ 41 - 0
packages/app/src/components/Sidebar/Skelton/SidebarSkelton.tsx

@@ -0,0 +1,41 @@
+import React from 'react';
+
+import { SidebarContentsType } from '~/interfaces/ui';
+import { useCurrentSidebarContents } from '~/stores/ui';
+
+// TODO: implement Skelton of other 3 components
+
+// import CustomSidebarContentSkelton from './CustomSidebarContentSkelton';
+import PageTreeContentSkelton from './PageTreeContentSkelton';
+// import RecentChangesContentSkelton from './RecentChangesContentSkelton';
+// import TagContentSkelton from './TagContentSkelton';
+import SidebarHeaderSkelton from './SidebarHeaderSkelton';
+
+export const SidebarSkelton = (): JSX.Element => {
+
+  const { data: currentSidebarContents } = useCurrentSidebarContents();
+
+  let SidebarContentSkelton;
+  switch (currentSidebarContents) {
+    /*
+    case SidebarContentsType.RECENT:
+      SidebarContentSkelton = RecentChangesContentSkelton;
+      break;
+    case SidebarContentsType.CUSTOM:
+      SidebarContentSkelton = CustomSidebarContentSkelton;
+      break;
+    case SidebarContentsType.TAG:
+      SidebarContentSkelton = TagContentSkelton;
+      break;
+    */
+    default:
+      SidebarContentSkelton = PageTreeContentSkelton;
+  }
+
+  return (
+    <>
+      <SidebarHeaderSkelton />
+      <SidebarContentSkelton />
+    </>
+  );
+};

+ 3 - 7
packages/app/src/components/Sidebar/Tag.tsx

@@ -9,6 +9,8 @@ import { useSWRxTagsList } from '~/stores/tag';
 import TagCloudBox from '../TagCloudBox';
 import TagCloudBox from '../TagCloudBox';
 import TagList from '../TagList';
 import TagList from '../TagList';
 
 
+import { SidebarHeaderReloadButton } from './SidebarHeaderReloadButton';
+
 
 
 const PAGING_LIMIT = 10;
 const PAGING_LIMIT = 10;
 const TAG_CLOUD_LIMIT = 20;
 const TAG_CLOUD_LIMIT = 20;
@@ -44,13 +46,7 @@ const Tag: FC = () => {
     <div className="grw-container-convertible px-4 mb-5 pb-5" data-testid="grw-sidebar-content-tags">
     <div className="grw-container-convertible px-4 mb-5 pb-5" data-testid="grw-sidebar-content-tags">
       <div className="grw-sidebar-content-header py-3 d-flex">
       <div className="grw-sidebar-content-header py-3 d-flex">
         <h3 className="mb-0">{t('Tags')}</h3>
         <h3 className="mb-0">{t('Tags')}</h3>
-        <button
-          type="button"
-          className="btn btn-sm ml-auto grw-btn-reload"
-          onClick={onReload}
-        >
-          <i className="icon icon-reload"></i>
-        </button>
+        <SidebarHeaderReloadButton onClick={() => onReload()}/>
       </div>
       </div>
 
 
       <h3 className="my-3">{t('tag_list')}</h3>
       <h3 className="my-3">{t('tag_list')}</h3>