From fe883edf6d711cb9dc282b9df326ab06af4eb674 Mon Sep 17 00:00:00 2001
From: Aigamo <51428094+ycanardeau@users.noreply.github.com>
Date: Sat, 3 Jul 2021 16:19:01 +1000
Subject: [PATCH 1/4] Install mobx
---
VocaDbWeb/package-lock.json | 43 +++++++++++++++++++++++++++++++++++++
VocaDbWeb/package.json | 2 ++
VocaDbWeb/tsconfig.json | 5 ++++-
VocaDbWeb/webpack.mix.js | 1 +
4 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/VocaDbWeb/package-lock.json b/VocaDbWeb/package-lock.json
index 9caf957941..012be95985 100644
--- a/VocaDbWeb/package-lock.json
+++ b/VocaDbWeb/package-lock.json
@@ -19,6 +19,8 @@
"knockout-punches": "^0.5.1",
"lodash": "^4.17.19",
"marked": "^0.3.18",
+ "mobx": "^6.3.2",
+ "mobx-react-lite": "^3.2.0",
"moment": "^2.17.0",
"qtip2": "^3.0.3",
"react": "^17.0.2",
@@ -13392,6 +13394,36 @@
"mkdirp": "bin/cmd.js"
}
},
+ "node_modules/mobx": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.3.2.tgz",
+ "integrity": "sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ }
+ },
+ "node_modules/mobx-react-lite": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz",
+ "integrity": "sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ },
+ "peerDependencies": {
+ "mobx": "^6.1.0",
+ "react": "^16.8.0 || ^17"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/moment": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.17.0.tgz",
@@ -31244,6 +31276,17 @@
"minimist": "^1.2.5"
}
},
+ "mobx": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.3.2.tgz",
+ "integrity": "sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg=="
+ },
+ "mobx-react-lite": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz",
+ "integrity": "sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==",
+ "requires": {}
+ },
"moment": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.17.0.tgz",
diff --git a/VocaDbWeb/package.json b/VocaDbWeb/package.json
index 10b025140b..fcf4a65397 100644
--- a/VocaDbWeb/package.json
+++ b/VocaDbWeb/package.json
@@ -64,6 +64,8 @@
"knockout-punches": "^0.5.1",
"lodash": "^4.17.19",
"marked": "^0.3.18",
+ "mobx": "^6.3.2",
+ "mobx-react-lite": "^3.2.0",
"moment": "^2.17.0",
"qtip2": "^3.0.3",
"react": "^17.0.2",
diff --git a/VocaDbWeb/tsconfig.json b/VocaDbWeb/tsconfig.json
index 9248270f41..c9b6efffac 100644
--- a/VocaDbWeb/tsconfig.json
+++ b/VocaDbWeb/tsconfig.json
@@ -20,9 +20,12 @@
"@Models/*": ["Scripts/Models/*"],
"@Repositories/*": ["Scripts/Repositories/*"],
"@Shared/*": ["Scripts/Shared/*"],
+ "@Stores/*": ["Scripts/Stores/*"],
"@ViewModels/*": ["Scripts/ViewModels/*"]
},
- "resolveJsonModule": true
+ "resolveJsonModule": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
},
"include": ["Scripts/**/*"]
}
diff --git a/VocaDbWeb/webpack.mix.js b/VocaDbWeb/webpack.mix.js
index 27f80a6c28..bf3a038deb 100644
--- a/VocaDbWeb/webpack.mix.js
+++ b/VocaDbWeb/webpack.mix.js
@@ -34,6 +34,7 @@ mix
'@Models': path.join(__dirname, 'Scripts/Models'),
'@Repositories': path.join(__dirname, 'Scripts/Repositories'),
'@Shared': path.join(__dirname, 'Scripts/Shared'),
+ '@Stores': path.join(__dirname, 'Scripts/Stores'),
'@ViewModels': path.join(__dirname, 'Scripts/ViewModels'),
})
.eslint({
From e55d20b563fadb9c390cf8bc943fc6f1c0671965 Mon Sep 17 00:00:00 2001
From: Aigamo <51428094+ycanardeau@users.noreply.github.com>
Date: Tue, 6 Jul 2021 21:06:52 +1000
Subject: [PATCH 2/4] Create ServerSidePaging
---
.../Shared/Partials/EntryCountBox.tsx | 25 ++++++
.../Shared/Partials/Knockout/EntryCount.tsx | 43 ++++++++++
.../Partials/Knockout/ServerSidePaging.tsx | 61 +++++++++++++
.../Scripts/Stores/ServerSidePagingStore.ts | 86 +++++++++++++++++++
4 files changed, 215 insertions(+)
create mode 100644 VocaDbWeb/Scripts/Components/Shared/Partials/EntryCountBox.tsx
create mode 100644 VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/EntryCount.tsx
create mode 100644 VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx
create mode 100644 VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
diff --git a/VocaDbWeb/Scripts/Components/Shared/Partials/EntryCountBox.tsx b/VocaDbWeb/Scripts/Components/Shared/Partials/EntryCountBox.tsx
new file mode 100644
index 0000000000..5f8e319004
--- /dev/null
+++ b/VocaDbWeb/Scripts/Components/Shared/Partials/EntryCountBox.tsx
@@ -0,0 +1,25 @@
+import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
+import { observer } from 'mobx-react-lite';
+import React from 'react';
+
+import EntryCount from './Knockout/EntryCount';
+
+interface EntryCountBoxProps {
+ pagingStore: ServerSidePagingStore;
+ selections?: number[];
+}
+
+const EntryCountBox = observer(
+ ({
+ pagingStore,
+ selections = [10, 20, 40, 100],
+ }: EntryCountBoxProps): React.ReactElement => {
+ return (
+
+
+
+ );
+ },
+);
+
+export default EntryCountBox;
diff --git a/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/EntryCount.tsx b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/EntryCount.tsx
new file mode 100644
index 0000000000..bd4be7ad8b
--- /dev/null
+++ b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/EntryCount.tsx
@@ -0,0 +1,43 @@
+import Dropdown from '@Bootstrap/Dropdown';
+import SafeAnchor from '@Bootstrap/SafeAnchor';
+import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
+import { observer } from 'mobx-react-lite';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface EntryCountProps {
+ pagingStore: ServerSidePagingStore;
+ selections?: number[];
+}
+
+const EntryCount = observer(
+ ({
+ pagingStore,
+ selections = [10, 20, 40, 100],
+ }: EntryCountProps): React.ReactElement => {
+ const { t } = useTranslation(['ViewRes.Search']);
+
+ return (
+
+
+ {t('ViewRes.Search:Index.ShowingItemsOf', {
+ 0: pagingStore.pageSize,
+ 1: pagingStore.totalItems,
+ })}
+
+
+ {selections.map((selection) => (
+ pagingStore.setPageSize(selection)}
+ key={selection}
+ >
+ {t('ViewRes.Search:Index.ItemsPerPage', { 0: selection })}
+
+ ))}
+
+
+ );
+ },
+);
+
+export default EntryCount;
diff --git a/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx
new file mode 100644
index 0000000000..1819fedd2a
--- /dev/null
+++ b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx
@@ -0,0 +1,61 @@
+import Pagination from '@Bootstrap/Pagination';
+import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
+import { observer } from 'mobx-react-lite';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface ServerSidePagingProps {
+ pagingStore: ServerSidePagingStore;
+}
+
+const ServerSidePaging = observer(
+ ({ pagingStore }: ServerSidePagingProps): React.ReactElement => {
+ const { t } = useTranslation(['VocaDb.Web.Resources.Other']);
+
+ return (
+
+
+ «« {t('VocaDb.Web.Resources.Other:PagedList.First')}
+
+
+ « {t('VocaDb.Web.Resources.Other:PagedList.Previous')}
+
+
+ {pagingStore.showMoreBegin && }
+
+ {pagingStore.pages.map((page) => (
+ pagingStore.setPage(page)}
+ key={page}
+ >
+ {page}
+
+ ))}
+
+ {pagingStore.showMoreEnd && }
+
+
+ {t('VocaDb.Web.Resources.Other:PagedList.Next')} »
+
+
+ {t('VocaDb.Web.Resources.Other:PagedList.Last')} »»
+
+
+ );
+ },
+);
+
+export default ServerSidePaging;
diff --git a/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
new file mode 100644
index 0000000000..e9569268fc
--- /dev/null
+++ b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
@@ -0,0 +1,86 @@
+import _ from 'lodash';
+import { action, computed, makeObservable, observable } from 'mobx';
+
+export default class ServerSidePagingStore {
+ @observable public page = 1;
+ @action public setPage = (value: number): void => {
+ this.page = value;
+ };
+
+ @observable public totalItems = 0;
+ @action public setTotalItems = (value: number): void => {
+ this.totalItems = value;
+ };
+
+ @observable public pageSize = 10;
+ @action public setPageSize = (value: number): void => {
+ this.pageSize = value;
+ };
+
+ public constructor(pageSize: number = 10) {
+ makeObservable(this);
+
+ this.pageSize = pageSize;
+ }
+
+ @computed public get firstItem(): number {
+ return (this.page - 1) * this.pageSize;
+ }
+
+ @computed public get totalPages(): number {
+ return Math.ceil(this.totalItems / this.pageSize);
+ }
+
+ @computed public get hasMultiplePages(): boolean {
+ return this.totalPages > 1;
+ }
+
+ @computed public get isFirstPage(): boolean {
+ return this.page <= 1;
+ }
+
+ @computed public get isLastPage(): boolean {
+ return this.page >= this.totalPages;
+ }
+
+ @computed public get pages(): number[] {
+ const start = Math.max(this.page - 4, 1);
+ const end = Math.min(this.page + 4, this.totalPages);
+
+ return _.range(start, end + 1);
+ }
+
+ @computed public get showMoreBegin(): boolean {
+ return this.page > 5;
+ }
+
+ @computed public get showMoreEnd(): boolean {
+ return this.page < this.totalPages - 4;
+ }
+
+ public getPagingProperties = (
+ clearResults: boolean = false,
+ ): { start: number; maxEntries: number; getTotalCount: boolean } => {
+ return {
+ start: this.firstItem,
+ maxEntries: this.pageSize,
+ getTotalCount: clearResults || this.totalItems === 0,
+ };
+ };
+
+ @action public goToFirstPage = (): void => {
+ this.page = 1;
+ };
+
+ @action public goToLastPage = (): void => {
+ this.page = this.totalPages;
+ };
+
+ @action public nextPage = (): void => {
+ if (!this.isLastPage) this.page = this.page + 1;
+ };
+
+ @action public previousPage = (): void => {
+ if (!this.isFirstPage) this.page = this.page - 1;
+ };
+}
From 45c6a561694f2f6fa891b6a4bf1b472a9261532f Mon Sep 17 00:00:00 2001
From: Aigamo <51428094+ycanardeau@users.noreply.github.com>
Date: Fri, 9 Jul 2021 21:31:39 +1000
Subject: [PATCH 3/4] Use SafeAnchor regardless of whether or not PageItem is
active
---
VocaDbWeb/Scripts/Bootstrap/PageItem.tsx | 2 +-
.../Components/Shared/Partials/Knockout/ServerSidePaging.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/VocaDbWeb/Scripts/Bootstrap/PageItem.tsx b/VocaDbWeb/Scripts/Bootstrap/PageItem.tsx
index fc64f93246..6455a4f0d3 100644
--- a/VocaDbWeb/Scripts/Bootstrap/PageItem.tsx
+++ b/VocaDbWeb/Scripts/Bootstrap/PageItem.tsx
@@ -33,7 +33,7 @@ const PageItem: BsPrefixRefForwardingComponent<
}: PageItemProps,
ref,
) => {
- const Component = active || disabled ? 'span' : SafeAnchor;
+ const Component = SafeAnchor;
return (
+
Date: Fri, 9 Jul 2021 21:47:18 +1000
Subject: [PATCH 4/4] Use PagingProperties
---
VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
index e9569268fc..7b78a52680 100644
--- a/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
+++ b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
@@ -1,3 +1,4 @@
+import PagingProperties from '@DataContracts/PagingPropertiesContract';
import _ from 'lodash';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -60,7 +61,7 @@ export default class ServerSidePagingStore {
public getPagingProperties = (
clearResults: boolean = false,
- ): { start: number; maxEntries: number; getTotalCount: boolean } => {
+ ): PagingProperties => {
return {
start: this.firstItem,
maxEntries: this.pageSize,