Просмотр исходного кода

Merge pull request #5700 from weseek/imprv/88064-add-spinner-to-tag-sidebar

imprv: Add spinner to tag sidebar
Yuki Takei 4 лет назад
Родитель
Сommit
7178602e24

+ 2 - 0
packages/app/src/components/Sidebar/Tag.tsx

@@ -1,5 +1,7 @@
 import React, { FC, useState, useEffect } from 'react';
+
 import { useTranslation } from 'react-i18next';
+
 import TagsList from '../TagsList';
 
 const Tag: FC = () => {

+ 2 - 6
packages/app/src/components/TagCloudBox.tsx

@@ -2,14 +2,10 @@ import React, { FC } from 'react';
 
 import { TagCloud } from 'react-tagcloud';
 
-type Tag = {
-  _id: string,
-  name: string,
-  count: number,
-}
+import { ITagHasCount } from '~/interfaces/tag';
 
 type Props = {
-  tags:Tag[],
+  tags: ITagHasCount[],
   minSize?: number,
   maxSize?: number,
 }

+ 0 - 126
packages/app/src/components/TagsList.jsx

@@ -1,126 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { withTranslation } from 'react-i18next';
-
-import PaginationWrapper from './PaginationWrapper';
-import TagCloudBox from './TagCloudBox';
-import { apiGet } from '../client/util/apiv1-client';
-import { toastError } from '../client/util/apiNotification';
-
-class TagsList extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      tagData: [],
-      activePage: 1,
-      totalTags: 0,
-      pagingLimit: 10,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-    this.getTagList = this.getTagList.bind(this);
-  }
-
-  async componentWillMount() {
-    await this.getTagList(1);
-  }
-
-  async componentDidUpdate() {
-    if (this.props.isOnReload) {
-      await this.getTagList(this.state.activePage);
-    }
-  }
-
-  async handlePage(selectedPage) {
-    await this.getTagList(selectedPage);
-  }
-
-  async getTagList(selectPageNumber) {
-    const limit = this.state.pagingLimit;
-    const offset = (selectPageNumber - 1) * limit;
-    let res;
-
-    try {
-      res = await apiGet('/tags.list', { limit, offset });
-    }
-    catch (error) {
-      toastError(error);
-    }
-
-    const totalTags = res.totalCount;
-    const tagData = res.data;
-    const activePage = selectPageNumber;
-
-    this.setState({
-      tagData,
-      activePage,
-      totalTags,
-    });
-  }
-
-  /**
-   * generate Elements of Tag
-   *
-   * @param {any} pages Array of pages Model Obj
-   *
-   */
-  generateTagList(tagData) {
-    return tagData.map((data) => {
-      return (
-        <a key={data.name} href={`/_search?q=tag:${data.name}`} className="list-group-item">
-          <i className="icon-tag mr-2"></i>{data.name}
-          <span className="ml-4 list-tag-count badge badge-secondary text-muted">{data.count}</span>
-        </a>
-      );
-    });
-  }
-
-  render() {
-    const { t } = this.props;
-    const messageForNoTag = this.state.tagData.length ? null : <h3>{ t('You have no tag, You can set tags on pages') }</h3>;
-
-    return (
-      <>
-        <header className="py-0">
-          <h1 className="title text-center mt-5 mb-3 font-weight-bold">{`${t('Tags')}(${this.state.totalTags})`}</h1>
-        </header>
-        <div className="row text-center">
-          <div className="col-12 mb-5 px-5">
-            <TagCloudBox tags={this.state.tagData} minSize={20} />
-          </div>
-          <div className="col-12 tag-list mb-4">
-            <ul className="list-group text-left">
-              {this.generateTagList(this.state.tagData)}
-            </ul>
-            {messageForNoTag}
-          </div>
-          <div className="col-12 tag-list-pagination">
-            <PaginationWrapper
-              activePage={this.state.activePage}
-              changePage={this.handlePage}
-              totalItemsCount={this.state.totalTags}
-              pagingLimit={this.state.pagingLimit}
-              align="center"
-              size="md"
-            />
-          </div>
-        </div>
-      </>
-    );
-  }
-
-}
-
-TagsList.propTypes = {
-  isOnReload: PropTypes.bool,
-  t: PropTypes.func.isRequired, // i18next
-};
-
-TagsList.defaultProps = {
-  isOnReload: false,
-};
-
-export default withTranslation()(TagsList);

+ 85 - 0
packages/app/src/components/TagsList.tsx

@@ -0,0 +1,85 @@
+import React, { FC, useEffect, useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import { useSWRxTagsList } from '~/stores/tag';
+
+import PaginationWrapper from './PaginationWrapper';
+import TagCloudBox from './TagCloudBox';
+
+
+const PAGING_LIMIT = 10;
+
+type Props = {
+  isOnReload: boolean
+}
+
+const TagsList: FC<Props> = (props: Props) => {
+  const { t } = useTranslation();
+
+  const [activePage, setActivePage] = useState<number>(1);
+  const [pagingOffset, setPagingOffset] = useState<number>(0);
+
+  const { data: tagsList, error, mutate } = useSWRxTagsList(PAGING_LIMIT, pagingOffset);
+
+  const handlePage = (selectedPageNumber: number) => {
+    setActivePage(selectedPageNumber);
+    setPagingOffset((selectedPageNumber - 1) * PAGING_LIMIT);
+  };
+
+  useEffect(() => {
+    if (props.isOnReload) {
+      mutate();
+    }
+  }, [mutate, props.isOnReload]);
+
+  const isLoading = tagsList === undefined && error == null;
+  if (isLoading) {
+    return (
+      <div className="text-muted text-center">
+        <i className="fa fa-2x fa-spinner fa-pulse mt-3"></i>
+      </div>
+    );
+  }
+
+  return (
+    <>
+      <header className="py-0">
+        <h1 className="title text-center mt-5 mb-3 font-weight-bold">{`${t('Tags')}(${tagsList?.totalCount || 0})`}</h1>
+      </header>
+      <div className="row text-center">
+        <div className="col-12 mb-5 px-5">
+          <TagCloudBox tags={tagsList?.data || []} minSize={20} />
+        </div>
+        <div className="col-12 tag-list mb-4">
+          <ul className="list-group text-left">
+            {
+              tagsList?.data != null && tagsList.data.length > 0
+                ? tagsList.data.map((tag) => {
+                  return (
+                    <a key={tag.name} href={`/_search?q=tag:${tag.name}`} className="list-group-item">
+                      <i className="icon-tag mr-2"></i>{tag.name}
+                      <span className="ml-4 list-tag-count badge badge-secondary text-muted">{tag.count}</span>
+                    </a>
+                  );
+                })
+                : <h3>{ t('You have no tag, You can set tags on pages') }</h3>
+            }
+          </ul>
+        </div>
+        <div className="col-12 tag-list-pagination">
+          <PaginationWrapper
+            activePage={activePage}
+            changePage={handlePage}
+            totalItemsCount={tagsList?.totalCount || 0}
+            pagingLimit={PAGING_LIMIT}
+            align="center"
+            size="md"
+          />
+        </div>
+      </div>
+    </>
+  );
+};
+
+export default TagsList;

+ 8 - 0
packages/app/src/interfaces/tag.ts

@@ -3,7 +3,15 @@ export type ITag = {
   createdAt: Date;
 }
 
+export type ITagHasCount = ITag & { count: number }
+
 export type ITagsSearchApiv1Result = {
   ok: boolean,
   tags: string[]
 }
+
+export type ITagsListApiv1Result = {
+  ok: boolean,
+  data: ITagHasCount[],
+  totalCount: number,
+}

+ 14 - 0
packages/app/src/stores/tag.tsx

@@ -0,0 +1,14 @@
+import { SWRResponse } from 'swr';
+import useSWRImmutable from 'swr/immutable';
+
+import { ITagsListApiv1Result } from '~/interfaces/tag';
+
+import { apiGet } from '../client/util/apiv1-client';
+
+
+export const useSWRxTagsList = (limit?: number, offset?: number): SWRResponse<ITagsListApiv1Result, Error> => {
+  return useSWRImmutable(
+    ['/tags.list', limit, offset],
+    (endpoint, limit, offset) => apiGet(endpoint, { limit, offset }).then((result: ITagsListApiv1Result) => result),
+  );
+};