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

Merge remote-tracking branch 'origin/master' into support/commonize-browser-detection

Yuki Takei 4 лет назад
Родитель
Сommit
d6c1343410

+ 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),
+  );
+};

+ 20 - 7
packages/app/test/cypress/integration/6-home/home.spec.ts

@@ -6,6 +6,8 @@ context('Access Home', () => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    // collapse sidebar
+    cy.collapseSidebar(true);
   });
 
   it('Visit home', () => {
@@ -29,6 +31,8 @@ context('Access User settings', () => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    // collapse sidebar
+    cy.collapseSidebar(true);
   });
 
   it('Update settings', () => {
@@ -41,9 +45,11 @@ context('Access User settings', () => {
     cy.getByTestid('grw-user-settings').should('be.visible');
     cy.screenshot(`${ssPrefix}-user-information-1`);
     cy.getByTestid('grw-besic-info-settings-update-button').click();
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-user-information-2`);
+
     cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
+    cy.get('.toast').should('not.exist');
 
     // Access External account
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(1) a').click();
@@ -55,18 +61,22 @@ context('Access User settings', () => {
     cy.getByTestid('grw-associate-modal').find('.modal-footer button').click(); // click add button in modal form
     cy.screenshot(`${ssPrefix}-external-account-3`);
     cy.getByTestid('grw-associate-modal').find('.close').click();
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-external-account-4`);
+
     cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
+    cy.get('.toast').should('not.exist');
 
     // Access Password setting
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(2) a').click();
     cy.scrollTo('top');
     cy.screenshot(`${ssPrefix}-password-settings-1`);
     cy.getByTestid('grw-password-settings-update-button').click();
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-password-settings-2`);
+
     cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
+    cy.get('.toast').should('not.exist');
 
     // Access API setting
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(3) a').click();
@@ -74,9 +84,11 @@ context('Access User settings', () => {
     cy.screenshot(`${ssPrefix}-api-setting-1`);
     cy.getByTestid('grw-api-settings-update-button').click();
     cy.getByTestid('grw-api-settings-input').should('be.visible');
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-api-setting-2`);
+
     cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
+    cy.get('.toast').should('not.exist');
 
     // Access Editor setting
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(4) a').click();
@@ -84,18 +96,19 @@ context('Access User settings', () => {
     cy.getByTestid('grw-editor-settings').should('be.visible');
     cy.screenshot(`${ssPrefix}-editor-setting-1`);
     cy.getByTestid('grw-editor-settings-update-button').click();
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-editor-setting-2`);
+
     cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
+    cy.get('.toast').should('not.exist');
 
     // Access In-app notification setting
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(5) a').click();
     cy.scrollTo('top');
     cy.screenshot(`${ssPrefix}-in-app-notification-setting-1`);
     cy.getByTestid('grw-in-app-notification-settings-update-button').click();
-    cy.get('.toast').should('be.visible');
+    cy.get('.toast').should('be.visible').invoke('attr', 'style', 'opacity: 1');
     cy.screenshot(`${ssPrefix}-in-app-notification-setting-2`);
-    cy.get('.toast-close-button').click({ multiple: true }); // close toast alert
   });
 
 });