From b72b343316b21c90870b01cc3cc08506bd503cd6 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Wed, 4 Oct 2023 17:04:06 +0200 Subject: [PATCH 1/4] Initial commit --- package-lock.json | 66 +++++++++ package.json | 2 + .../basic-list/basic-list.component.scss | 11 ++ .../basic-list/basic-list.component.tsx | 135 ++++++++++++++++++ .../components/basic-list/makeCheckResults.ts | 40 ++++++ .../paranext-dock-layout.component.tsx | 5 + src/renderer/testing/test-layout.data.ts | 4 + 7 files changed, 263 insertions(+) create mode 100644 src/renderer/components/basic-list/basic-list.component.scss create mode 100644 src/renderer/components/basic-list/basic-list.component.tsx create mode 100644 src/renderer/components/basic-list/makeCheckResults.ts diff --git a/package-lock.json b/package-lock.json index 5bd10ddf15..ff11e1c821 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.1.0", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", "@sillsdev/scripture": "^1.4.0", + "@tanstack/react-table": "^8.10.3", "async-mutex": "^0.4.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", @@ -3384,6 +3386,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz", + "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -7783,6 +7800,37 @@ "node": ">=10" } }, + "node_modules/@tanstack/react-table": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.3.tgz", + "integrity": "sha512-Qya1cJ+91arAlW7IRDWksRDnYw28O446jJ/ljkRSc663EaftJoBCAU10M+VV1K6MpCBLrXq1BD5IQc1zj/ZEjA==", + "dependencies": { + "@tanstack/table-core": "8.10.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.3.tgz", + "integrity": "sha512-hJ55YfJlWbfzRROfcyA/kC1aZr/shsLA8XNAwN8jXylhYWGLnPmiJJISrUfj4dMMWRiFi0xBlnlC7MLH+zSrcw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@teamsupercell/typings-for-css-modules-loader": { "version": "2.5.2", "dev": true, @@ -28237,6 +28285,11 @@ "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true }, + "@faker-js/faker": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz", + "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==" + }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -31113,6 +31166,19 @@ "defer-to-connect": "^2.0.0" } }, + "@tanstack/react-table": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.3.tgz", + "integrity": "sha512-Qya1cJ+91arAlW7IRDWksRDnYw28O446jJ/ljkRSc663EaftJoBCAU10M+VV1K6MpCBLrXq1BD5IQc1zj/ZEjA==", + "requires": { + "@tanstack/table-core": "8.10.3" + } + }, + "@tanstack/table-core": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.3.tgz", + "integrity": "sha512-hJ55YfJlWbfzRROfcyA/kC1aZr/shsLA8XNAwN8jXylhYWGLnPmiJJISrUfj4dMMWRiFi0xBlnlC7MLH+zSrcw==" + }, "@teamsupercell/typings-for-css-modules-loader": { "version": "2.5.2", "dev": true, diff --git a/package.json b/package.json index 5b6354dff4..348e4a7d27 100644 --- a/package.json +++ b/package.json @@ -91,9 +91,11 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.1.0", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", "@sillsdev/scripture": "^1.4.0", + "@tanstack/react-table": "^8.10.3", "async-mutex": "^0.4.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", diff --git a/src/renderer/components/basic-list/basic-list.component.scss b/src/renderer/components/basic-list/basic-list.component.scss new file mode 100644 index 0000000000..904baed9dd --- /dev/null +++ b/src/renderer/components/basic-list/basic-list.component.scss @@ -0,0 +1,11 @@ +.basic-list-table { + overflow: auto; + + .table-header { + text-align: left; + } +} + +.basic-list-expand-button { + cursor: pointer; +} diff --git a/src/renderer/components/basic-list/basic-list.component.tsx b/src/renderer/components/basic-list/basic-list.component.tsx new file mode 100644 index 0000000000..ba869bdac6 --- /dev/null +++ b/src/renderer/components/basic-list/basic-list.component.tsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; +import { SavedTabInfo, TabInfo } from '@shared/data/web-view.model'; +import { + ExpandedState, + useReactTable, + getCoreRowModel, + getExpandedRowModel, + ColumnDef, + flexRender, +} from '@tanstack/react-table'; + +import { makeCheckResults, checkResult } from './makeCheckResults'; +import './basic-list.component.scss'; + +export const TAB_TYPE_BASIC_LIST = 'basic-list'; + +const columns: ColumnDef[] = [ + { + header: 'Scripture reference', + columns: [ + { + accessorKey: 'book', + header: ({ table }) => ( + <> + {' '} + Book + + ), + cell: ({ row, getValue }) => ( +
+ {row.getCanExpand() && ( + + )}{' '} + {getValue()} +
+ ), + }, + { + accessorFn: (row) => row.chapter, + id: 'chapter', + cell: (info) => info.getValue(), + header: () => Chapter, + }, + { + accessorFn: (row) => row.verse, + id: 'verse', + cell: (info) => info.getValue(), + header: () => Verse, + }, + ], + }, + { + header: 'Issue', + columns: [ + { + accessorKey: 'issueDescription', + header: () => 'Description', + footer: (props) => props.column.id, + }, + ], + }, +]; + +export default function BasicList() { + const [expanded, setExpanded] = useState({}); + const [data] = useState(() => makeCheckResults(20, 5)); + const table = useReactTable({ + data, + columns, + state: { + expanded, + }, + onExpandedChange: setExpanded, + getSubRows: (row) => row.subRows, + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + ); + })} + +
+ {header.isPlaceholder ? null : ( +
{flexRender(header.column.columnDef.header, header.getContext())}
+ )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +} + +export function loadBasicListTab(savedTabInfo: SavedTabInfo): TabInfo { + return { + ...savedTabInfo, + tabTitle: 'Basic List', + content: , + }; +} diff --git a/src/renderer/components/basic-list/makeCheckResults.ts b/src/renderer/components/basic-list/makeCheckResults.ts new file mode 100644 index 0000000000..2f7e929b10 --- /dev/null +++ b/src/renderer/components/basic-list/makeCheckResults.ts @@ -0,0 +1,40 @@ +import { faker } from '@faker-js/faker'; + +export type checkResult = { + book: string; + chapter: number; + verse: number; + issueDescription: string; + subRows?: checkResult[]; +}; + +const range = (len: number) => { + const arr = []; + for (let i = 0; i < len; i++) { + arr.push(i); + } + return arr; +}; + +const newCheckResult = (): checkResult => { + return { + book: faker.person.firstName(), + chapter: faker.number.int(40), + verse: faker.number.int(40), + issueDescription: faker.person.jobTitle(), + }; +}; + +export function makeCheckResults(...lens: number[]) { + const makeDataLevel = (depth = 0): checkResult[] => { + const len = lens && lens[depth]; + return range(len).map((): checkResult => { + return { + ...newCheckResult(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }; + }); + }; + + return makeDataLevel(); +} diff --git a/src/renderer/components/docking/paranext-dock-layout.component.tsx b/src/renderer/components/docking/paranext-dock-layout.component.tsx index c02116620a..8e24f05917 100644 --- a/src/renderer/components/docking/paranext-dock-layout.component.tsx +++ b/src/renderer/components/docking/paranext-dock-layout.component.tsx @@ -61,6 +61,10 @@ import { TAB_TYPE_RUN_BASIC_CHECKS, loadRunBasicChecksTab, } from '@renderer/components/run-basic-checks-dialog/run-basic-checks-tab.component'; +import { + TAB_TYPE_BASIC_LIST, + loadBasicListTab, +} from '@renderer/components/basic-list/basic-list.component'; type TabType = string; @@ -98,6 +102,7 @@ const tabLoaderMap = new Map([ [TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG, loadOpenMultipleProjectsTab], [TAB_TYPE_EXTENSION_MANAGER, loadExtensionManagerTab], [TAB_TYPE_RUN_BASIC_CHECKS, loadRunBasicChecksTab], + [TAB_TYPE_BASIC_LIST, loadBasicListTab], ]); /** tab saver functions for each Paranext tab type that wants to override the default */ diff --git a/src/renderer/testing/test-layout.data.ts b/src/renderer/testing/test-layout.data.ts index f4dec65520..bb1a6879ac 100644 --- a/src/renderer/testing/test-layout.data.ts +++ b/src/renderer/testing/test-layout.data.ts @@ -9,6 +9,7 @@ import { TAB_TYPE_TEST } from '@renderer/testing/test-panel.component'; // import { TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG } from '@renderer/components/project-dialogs/open-multiple-projects-tab.component'; // import { TAB_TYPE_EXTENSION_MANAGER } from '@renderer/components/extension-manager/extension-manager-tab.component'; import { TAB_TYPE_RUN_BASIC_CHECKS } from '@renderer/components/run-basic-checks-dialog/run-basic-checks-tab.component'; +import { TAB_TYPE_BASIC_LIST } from '@renderer/components/basic-list/basic-list.component'; export const FIRST_TAB_ID = 'About'; @@ -32,6 +33,9 @@ const testLayout: LayoutBase = { { tabs: [{ id: 'Test Buttons', tabType: TAB_TYPE_BUTTONS }] as SavedTabInfo[], }, + { + tabs: [{ id: 'Basic List', tabType: TAB_TYPE_BASIC_LIST }] as SavedTabInfo[], + }, ], }, floatbox: { From 4636e50c50adfb848ad847447a934ffe3abaad0b Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Fri, 13 Oct 2023 16:17:19 +0200 Subject: [PATCH 2/4] Add mock data --- .../basic-list/basic-list.component.tsx | 43 +++++++++++++++++-- .../components/basic-list/makeCheckResults.ts | 40 ----------------- 2 files changed, 39 insertions(+), 44 deletions(-) delete mode 100644 src/renderer/components/basic-list/makeCheckResults.ts diff --git a/src/renderer/components/basic-list/basic-list.component.tsx b/src/renderer/components/basic-list/basic-list.component.tsx index ba869bdac6..97eb3146c2 100644 --- a/src/renderer/components/basic-list/basic-list.component.tsx +++ b/src/renderer/components/basic-list/basic-list.component.tsx @@ -8,12 +8,47 @@ import { ColumnDef, flexRender, } from '@tanstack/react-table'; +import { Canon } from '@sillsdev/scripture'; +import { faker } from '@faker-js/faker'; -import { makeCheckResults, checkResult } from './makeCheckResults'; import './basic-list.component.scss'; export const TAB_TYPE_BASIC_LIST = 'basic-list'; +type checkResult = { + book: string; + chapter?: number; + verse?: number; + issueDescription: string; + subRows?: checkResult[]; +}; + +const newCheckResult = (bookId: string): checkResult => { + return { + book: Canon.bookIdToEnglishName(bookId), + chapter: faker.number.int(40), + verse: faker.number.int(40), + issueDescription: 'Basic check issue description', + }; +}; + +export function makeMockCheckResults() { + const { allBookIds } = Canon; + return allBookIds.map((bookId) => { + const numberOfIssues: number = faker.number.int({ min: 1, max: 10 }); + const bookResults: checkResult = { + book: Canon.bookIdToEnglishName(bookId), + issueDescription: `${numberOfIssues} issues`, + }; + const subResults: checkResult[] = []; + for (let i = 0; i < numberOfIssues; i++) { + subResults.push(newCheckResult(bookId)); + } + bookResults.subRows = subResults; + return bookResults; + }); +} + const columns: ColumnDef[] = [ { header: 'Scripture reference', @@ -51,13 +86,13 @@ const columns: ColumnDef[] = [ accessorFn: (row) => row.chapter, id: 'chapter', cell: (info) => info.getValue(), - header: () => Chapter, + header: ({ table }) => <> {table.getIsSomeRowsExpanded() && Chapter} , }, { accessorFn: (row) => row.verse, id: 'verse', cell: (info) => info.getValue(), - header: () => Verse, + header: ({ table }) => <> {table.getIsSomeRowsExpanded() && Verse} , }, ], }, @@ -75,7 +110,7 @@ const columns: ColumnDef[] = [ export default function BasicList() { const [expanded, setExpanded] = useState({}); - const [data] = useState(() => makeCheckResults(20, 5)); + const [data] = useState(() => makeMockCheckResults()); const table = useReactTable({ data, columns, diff --git a/src/renderer/components/basic-list/makeCheckResults.ts b/src/renderer/components/basic-list/makeCheckResults.ts deleted file mode 100644 index 2f7e929b10..0000000000 --- a/src/renderer/components/basic-list/makeCheckResults.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { faker } from '@faker-js/faker'; - -export type checkResult = { - book: string; - chapter: number; - verse: number; - issueDescription: string; - subRows?: checkResult[]; -}; - -const range = (len: number) => { - const arr = []; - for (let i = 0; i < len; i++) { - arr.push(i); - } - return arr; -}; - -const newCheckResult = (): checkResult => { - return { - book: faker.person.firstName(), - chapter: faker.number.int(40), - verse: faker.number.int(40), - issueDescription: faker.person.jobTitle(), - }; -}; - -export function makeCheckResults(...lens: number[]) { - const makeDataLevel = (depth = 0): checkResult[] => { - const len = lens && lens[depth]; - return range(len).map((): checkResult => { - return { - ...newCheckResult(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - }; - }); - }; - - return makeDataLevel(); -} From 7fd2248a3b42847d588e67b00b631cd20a84a03b Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Mon, 16 Oct 2023 10:20:26 +0200 Subject: [PATCH 3/4] Processed review comments --- .../basic-list/basic-list.component.tsx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/renderer/components/basic-list/basic-list.component.tsx b/src/renderer/components/basic-list/basic-list.component.tsx index 97eb3146c2..2069d93ec8 100644 --- a/src/renderer/components/basic-list/basic-list.component.tsx +++ b/src/renderer/components/basic-list/basic-list.component.tsx @@ -15,32 +15,29 @@ import './basic-list.component.scss'; export const TAB_TYPE_BASIC_LIST = 'basic-list'; -type checkResult = { +type CheckResult = { book: string; chapter?: number; verse?: number; issueDescription: string; - subRows?: checkResult[]; + subRows?: CheckResult[]; }; -const newCheckResult = (bookId: string): checkResult => { - return { - book: Canon.bookIdToEnglishName(bookId), - chapter: faker.number.int(40), - verse: faker.number.int(40), - issueDescription: 'Basic check issue description', - }; -}; +const newCheckResult = (bookId: string): CheckResult => ({ + book: Canon.bookIdToEnglishName(bookId), + chapter: faker.number.int(40), + verse: faker.number.int(40), + issueDescription: 'Basic check issue description', +}); -export function makeMockCheckResults() { - const { allBookIds } = Canon; - return allBookIds.map((bookId) => { +function makeMockCheckResults() { + return Canon.allBookIds.map((bookId) => { const numberOfIssues: number = faker.number.int({ min: 1, max: 10 }); - const bookResults: checkResult = { + const bookResults: CheckResult = { book: Canon.bookIdToEnglishName(bookId), issueDescription: `${numberOfIssues} issues`, }; - const subResults: checkResult[] = []; + const subResults: CheckResult[] = []; for (let i = 0; i < numberOfIssues; i++) { subResults.push(newCheckResult(bookId)); } @@ -49,7 +46,7 @@ export function makeMockCheckResults() { }); } -const columns: ColumnDef[] = [ +const columns: ColumnDef[] = [ { header: 'Scripture reference', columns: [ From 655369654ee9e632ea18888825b5de5dffe696ea Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Mon, 16 Oct 2023 10:28:36 +0200 Subject: [PATCH 4/4] Use arrow function --- src/renderer/components/basic-list/basic-list.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/basic-list/basic-list.component.tsx b/src/renderer/components/basic-list/basic-list.component.tsx index 2069d93ec8..54deb4483a 100644 --- a/src/renderer/components/basic-list/basic-list.component.tsx +++ b/src/renderer/components/basic-list/basic-list.component.tsx @@ -30,7 +30,7 @@ const newCheckResult = (bookId: string): CheckResult => ({ issueDescription: 'Basic check issue description', }); -function makeMockCheckResults() { +const makeMockCheckResults = () => { return Canon.allBookIds.map((bookId) => { const numberOfIssues: number = faker.number.int({ min: 1, max: 10 }); const bookResults: CheckResult = { @@ -44,7 +44,7 @@ function makeMockCheckResults() { bookResults.subRows = subResults; return bookResults; }); -} +}; const columns: ColumnDef[] = [ {