From 2d7e3ea1e63f2cd65d6c2904f2e3c292f99bf92c Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Fri, 1 Nov 2024 08:04:29 -0700 Subject: [PATCH 1/9] Upgrade bloom-player to 2.6.7 (BL-14017, BL-13906, BL-13796) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4936962c..08f39ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7216,9 +7216,9 @@ bl@^1.0.0: safe-buffer "^5.1.1" bloom-player@^2.3.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.6.4.tgz#767da393d891268de4fdb4ef2e1640081e1f34c1" - integrity sha512-jtIoRcdqgeSRgHMZ5TfvDEadg/ZfTq/J6BbZTv1HudjGflIGe4DOYGSTsFcQtELrWWmqhx0PYxlKnYXirZdDaw== + version "2.6.7" + resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.6.7.tgz#26817bfb6def139214926bd0be31b81b61e3bfc2" + integrity sha512-jg842Mixray1MW7eyM7/rtZN16gamt5OrpCWfbpCPwOEutmhadTVovl64vbaw6tKGX6SU3HHuQi5U1NEgFEsOg== bluebird@^3.3.5, bluebird@^3.5.5: version "3.7.1" From fff581a244c862ab910cdb073631091232598c1e Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Thu, 31 Oct 2024 10:11:55 -0700 Subject: [PATCH 2/9] Sync up grid export with grid (BL-14047) --- src/components/Grid/GridColumns.tsx | 17 +++++++- src/components/Grid/GridExport.ts | 64 ++++++++++++++--------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/components/Grid/GridColumns.tsx b/src/components/Grid/GridColumns.tsx index 21113ebf..746c7913 100644 --- a/src/components/Grid/GridColumns.tsx +++ b/src/components/Grid/GridColumns.tsx @@ -29,6 +29,11 @@ export interface IGridColumn extends DevExpressColumn { addToFilter?: (filter: IFilter, value: string) => void; sortingEnabled?: boolean; l10nId?: string; // the id to use for localization if it's a common one that we don't want to just fabricate for this context + // Returns a simple string representation of the value in the cell. + // If not provided, the only current caller (csv export) will fall back + // to using getCellValue if that doesn't return an object (component). + // Else it will fallback to b[key].toString(). + getStringValue?: (b: Book) => string; } // For some tags, we want to give them their own column. So we don't want to show them in the tags column. @@ -40,7 +45,6 @@ const kTagsToFilterOutOfTagsList = [ ]; export function getBookGridColumnsDefinitions(): IGridColumn[] { - // Note, the order here is also the default order in the table const definitions: IGridColumn[] = [ { name: "title", @@ -144,6 +148,10 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { getCellValue: (b: Book) => ( ), + getStringValue: (b: Book) => + b.tags.filter((tag) => tag === "system:Incoming").length + ? "true" + : "false", getCustomFilterComponent: (props: TableFilterRow.CellProps) => ( ), @@ -185,6 +193,11 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { {t.replace(/topic:/, "")} )), + getStringValue: (b: Book) => + b.tags + .filter((tag) => tag.startsWith("topic:")) + .map((topic) => topic.replace("topic:", "")) + .join(", "), addToFilter: (filter: IFilter, value: string) => { filter.topic = titleCase(value); }, @@ -265,6 +278,7 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { { name: "Is Rebrand", getCellValue: (b: Book) => , + getStringValue: (b: Book) => (b.rebrand ? "true" : "false"), getCustomFilterComponent: (props: TableFilterRow.CellProps) => ( ), + getStringValue: (b: Book) => b.uploader?.username ?? "", addToFilter: (filter: IFilter, value: string) => { filter.search += ` uploader:${value} `; }, diff --git a/src/components/Grid/GridExport.ts b/src/components/Grid/GridExport.ts index 69b64549..b5df1539 100644 --- a/src/components/Grid/GridExport.ts +++ b/src/components/Grid/GridExport.ts @@ -12,6 +12,7 @@ import { retrieveBookData, retrieveBookStats, } from "../../connection/LibraryQueries"; +import { getBookGridColumnsDefinitions, IGridColumn } from "./GridColumns"; let static_books: Book[] = []; let static_columnsInOrder: string[] = []; @@ -22,6 +23,11 @@ let static_sortingArray: Array<{ }> = []; let static_completeFilter: IFilter; +const static_keyToColumnDefinition: Map = new Map(); +getBookGridColumnsDefinitions().forEach((columnDefinition) => { + static_keyToColumnDefinition.set(columnDefinition.name, columnDefinition); +}); + export function setGridExportFilter( completeFilter: IFilter, //includes the search box gridColumnFilters: GridFilter[] //just the filters from the headers of the columns @@ -75,10 +81,13 @@ function exportData(): string[][] { const iTitle = headerRow.indexOf("title"); headerRow.splice(iTitle + 1, 0, "url"); - all.push(headerRow); + all.push( + headerRow.map( + (key) => static_keyToColumnDefinition.get(key)?.title ?? key + ) + ); static_books.forEach((book) => { - //const valueRow = Object.values(row).map((v) => v ? v.toString() : "") as string[]; const valueRow = headerRow.map((key) => getStringForItem(book, key)); all.push(valueRow); }); @@ -86,37 +95,28 @@ function exportData(): string[][] { } function getStringForItem(book: Book, key: string): string { - switch (key) { - case "url": - // Excel and Google Sheets will interpret =HYPERLINK to make it a link, even in a csv. Supposedly Libre Office will, too. - return `=HYPERLINK("${window.location.origin}/book/${book.id}")`; - case "languages": - return book.languages.map((lang) => lang.name).join(", "); - case "languagecodes": - return book.languages.map((lang) => lang.isoCode).join(", "); - case "uploader": - return book.uploader?.username ?? ""; - case "topic": - return book.tags - .filter((tag) => tag.startsWith("topic:")) - .map((topic) => topic.replace("topic:", "")) - .join(", "); - case "incoming": - return book.tags.filter((tag) => tag === "system:Incoming").length - ? "true" - : "false"; - case "tags": - return book.tags - .filter( - (tag) => - !tag.startsWith("topic:") && tag !== "system:Incoming" - ) - .join(", "); - case "reads": - return book.stats.finishedCount.toString(); - case "downloadsForTranslation": - return book.stats.shellDownloads.toString(); + // url is a special case since it only exists in the export, not in the grid. + if (key === "url") { + // Excel and Google Sheets will interpret =HYPERLINK to make it a link, even in a csv. Supposedly Libre Office will, too. + return `=HYPERLINK("${window.location.origin}/book/${book.id}")`; } + + // Try getStringValue first. If defined, that's what we want; just the string representation. + const columnDefinition = static_keyToColumnDefinition.get(key); + if (columnDefinition?.getStringValue) + return columnDefinition.getStringValue(book); + + // Otherwise, we might just want the same value as the grid shows. + // But not if the result is an object (React component). + if (columnDefinition?.getCellValue) { + const valAsAny = columnDefinition.getCellValue(book, key); + if (valAsAny !== undefined && typeof valAsAny !== "object") { + return valAsAny.toString(); + } + } + + // If there's no getStringValue or getCellValue (or getCellValue returns a component), + // just get the raw value from the book object based on the key. const item = book[key as keyof Book]; return item ? item.toString() : ""; } From 22bb5f0df22e8e03cbea8b3ad643cb33bacf90b3 Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Thu, 31 Oct 2024 10:11:55 -0700 Subject: [PATCH 3/9] Sync up grid export with grid (BL-14047) --- src/components/Grid/GridColumns.tsx | 17 +++++++- src/components/Grid/GridExport.ts | 64 ++++++++++++++--------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/components/Grid/GridColumns.tsx b/src/components/Grid/GridColumns.tsx index 21113ebf..746c7913 100644 --- a/src/components/Grid/GridColumns.tsx +++ b/src/components/Grid/GridColumns.tsx @@ -29,6 +29,11 @@ export interface IGridColumn extends DevExpressColumn { addToFilter?: (filter: IFilter, value: string) => void; sortingEnabled?: boolean; l10nId?: string; // the id to use for localization if it's a common one that we don't want to just fabricate for this context + // Returns a simple string representation of the value in the cell. + // If not provided, the only current caller (csv export) will fall back + // to using getCellValue if that doesn't return an object (component). + // Else it will fallback to b[key].toString(). + getStringValue?: (b: Book) => string; } // For some tags, we want to give them their own column. So we don't want to show them in the tags column. @@ -40,7 +45,6 @@ const kTagsToFilterOutOfTagsList = [ ]; export function getBookGridColumnsDefinitions(): IGridColumn[] { - // Note, the order here is also the default order in the table const definitions: IGridColumn[] = [ { name: "title", @@ -144,6 +148,10 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { getCellValue: (b: Book) => ( ), + getStringValue: (b: Book) => + b.tags.filter((tag) => tag === "system:Incoming").length + ? "true" + : "false", getCustomFilterComponent: (props: TableFilterRow.CellProps) => ( ), @@ -185,6 +193,11 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { {t.replace(/topic:/, "")} )), + getStringValue: (b: Book) => + b.tags + .filter((tag) => tag.startsWith("topic:")) + .map((topic) => topic.replace("topic:", "")) + .join(", "), addToFilter: (filter: IFilter, value: string) => { filter.topic = titleCase(value); }, @@ -265,6 +278,7 @@ export function getBookGridColumnsDefinitions(): IGridColumn[] { { name: "Is Rebrand", getCellValue: (b: Book) => , + getStringValue: (b: Book) => (b.rebrand ? "true" : "false"), getCustomFilterComponent: (props: TableFilterRow.CellProps) => ( ), + getStringValue: (b: Book) => b.uploader?.username ?? "", addToFilter: (filter: IFilter, value: string) => { filter.search += ` uploader:${value} `; }, diff --git a/src/components/Grid/GridExport.ts b/src/components/Grid/GridExport.ts index 69b64549..b5df1539 100644 --- a/src/components/Grid/GridExport.ts +++ b/src/components/Grid/GridExport.ts @@ -12,6 +12,7 @@ import { retrieveBookData, retrieveBookStats, } from "../../connection/LibraryQueries"; +import { getBookGridColumnsDefinitions, IGridColumn } from "./GridColumns"; let static_books: Book[] = []; let static_columnsInOrder: string[] = []; @@ -22,6 +23,11 @@ let static_sortingArray: Array<{ }> = []; let static_completeFilter: IFilter; +const static_keyToColumnDefinition: Map = new Map(); +getBookGridColumnsDefinitions().forEach((columnDefinition) => { + static_keyToColumnDefinition.set(columnDefinition.name, columnDefinition); +}); + export function setGridExportFilter( completeFilter: IFilter, //includes the search box gridColumnFilters: GridFilter[] //just the filters from the headers of the columns @@ -75,10 +81,13 @@ function exportData(): string[][] { const iTitle = headerRow.indexOf("title"); headerRow.splice(iTitle + 1, 0, "url"); - all.push(headerRow); + all.push( + headerRow.map( + (key) => static_keyToColumnDefinition.get(key)?.title ?? key + ) + ); static_books.forEach((book) => { - //const valueRow = Object.values(row).map((v) => v ? v.toString() : "") as string[]; const valueRow = headerRow.map((key) => getStringForItem(book, key)); all.push(valueRow); }); @@ -86,37 +95,28 @@ function exportData(): string[][] { } function getStringForItem(book: Book, key: string): string { - switch (key) { - case "url": - // Excel and Google Sheets will interpret =HYPERLINK to make it a link, even in a csv. Supposedly Libre Office will, too. - return `=HYPERLINK("${window.location.origin}/book/${book.id}")`; - case "languages": - return book.languages.map((lang) => lang.name).join(", "); - case "languagecodes": - return book.languages.map((lang) => lang.isoCode).join(", "); - case "uploader": - return book.uploader?.username ?? ""; - case "topic": - return book.tags - .filter((tag) => tag.startsWith("topic:")) - .map((topic) => topic.replace("topic:", "")) - .join(", "); - case "incoming": - return book.tags.filter((tag) => tag === "system:Incoming").length - ? "true" - : "false"; - case "tags": - return book.tags - .filter( - (tag) => - !tag.startsWith("topic:") && tag !== "system:Incoming" - ) - .join(", "); - case "reads": - return book.stats.finishedCount.toString(); - case "downloadsForTranslation": - return book.stats.shellDownloads.toString(); + // url is a special case since it only exists in the export, not in the grid. + if (key === "url") { + // Excel and Google Sheets will interpret =HYPERLINK to make it a link, even in a csv. Supposedly Libre Office will, too. + return `=HYPERLINK("${window.location.origin}/book/${book.id}")`; } + + // Try getStringValue first. If defined, that's what we want; just the string representation. + const columnDefinition = static_keyToColumnDefinition.get(key); + if (columnDefinition?.getStringValue) + return columnDefinition.getStringValue(book); + + // Otherwise, we might just want the same value as the grid shows. + // But not if the result is an object (React component). + if (columnDefinition?.getCellValue) { + const valAsAny = columnDefinition.getCellValue(book, key); + if (valAsAny !== undefined && typeof valAsAny !== "object") { + return valAsAny.toString(); + } + } + + // If there's no getStringValue or getCellValue (or getCellValue returns a component), + // just get the raw value from the book object based on the key. const item = book[key as keyof Book]; return item ? item.toString() : ""; } From 67b293105c5e15693a27a01fe37b80f43953f217 Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Mon, 11 Nov 2024 16:47:40 -0700 Subject: [PATCH 4/9] Upgrade bloom-player to 2.6.8-alpha.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3b908757..da20139b 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/parser": "^5.38.0", "async-retry": "^1.3.1", - "bloom-player": "^2.3.0", + "bloom-player": "^2.6.8-alpha.1", "concurrently": "^5.1.0", "decompress": "^4.2.1", "download": "^8.0.0", diff --git a/yarn.lock b/yarn.lock index 08f39ea1..6a5e8d02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7215,10 +7215,10 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bloom-player@^2.3.0: - version "2.6.7" - resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.6.7.tgz#26817bfb6def139214926bd0be31b81b61e3bfc2" - integrity sha512-jg842Mixray1MW7eyM7/rtZN16gamt5OrpCWfbpCPwOEutmhadTVovl64vbaw6tKGX6SU3HHuQi5U1NEgFEsOg== +bloom-player@^2.6.8-alpha.1: + version "2.6.8-alpha.1" + resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.6.8-alpha.1.tgz#072f097f335ff771819b5ade470bc45937690f84" + integrity sha512-MYgrSQf3zF+Bo2f8fH6oQmG2m/gdWSTUVeR/eIRpuinqcngqDSVTKcBJKr1OttiLzWoyugsiG1RRKyBlwetOAg== bluebird@^3.3.5, bluebird@^3.5.5: version "3.7.1" From 2d20a1d426d22d9b76e3508aca30cee1db2dd2f0 Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Tue, 12 Nov 2024 10:03:57 -0700 Subject: [PATCH 5/9] Build in GHA --- .github/workflows/build-and-deploy.yml | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/build-and-deploy.yml diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml new file mode 100644 index 00000000..8362d342 --- /dev/null +++ b/.github/workflows/build-and-deploy.yml @@ -0,0 +1,48 @@ +name: Build and Deploy + +on: + push: + branches: + - master + - release + - embed + workflow_dispatch: # Allows manual triggering of the workflow + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install dependencies + run: yarn install --frozen-lockfile --network-timeout 1000000000 + + # Not yet + # - name: Upload translations to Crowdin + # if: github.ref == 'refs/heads/release' + # # Push any new code-based strings to Crowdin + # run: crowdin-dangerous-upload + + - name: Download translations from Crowdin + run: yarn crowdin-download + + - name: Build + run: | + if [ "${{ github.ref }}" == "refs/heads/master" ]; then + yarn build:ci:alpha + else + yarn build:ci + fi + + - name: Run tests + run: yarn test:ci + + # Not yet + # - name: Deploy to S3 + # run: | + # aws s3 cp path/to/build/artifacts s3://your-bucket-name --recursive + # env: + # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From b28761d7f504f447b612b124819bf0b64799bb72 Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Tue, 12 Nov 2024 10:12:46 -0700 Subject: [PATCH 6/9] GHA: Use Crowdin token --- .github/workflows/build-and-deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 8362d342..ed1dfbfb 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -24,9 +24,13 @@ jobs: # if: github.ref == 'refs/heads/release' # # Push any new code-based strings to Crowdin # run: crowdin-dangerous-upload + # env: + # bloomCrowdinApiToken: ${{ secrets.BLOOM_CROWDIN_TOKEN }} - name: Download translations from Crowdin run: yarn crowdin-download + env: + bloomCrowdinApiToken: ${{ secrets.BLOOM_CROWDIN_TOKEN }} - name: Build run: | From 497bb0adc7f895d49b4537e782c94a4cdb6c7131 Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Tue, 12 Nov 2024 10:53:46 -0700 Subject: [PATCH 7/9] GHA: run lighthouse after successful build on master --- .github/workflows/{main.yml => lighthouse.yml} | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) rename .github/workflows/{main.yml => lighthouse.yml} (87%) diff --git a/.github/workflows/main.yml b/.github/workflows/lighthouse.yml similarity index 87% rename from .github/workflows/main.yml rename to .github/workflows/lighthouse.yml index b685a6c7..00a5266a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/lighthouse.yml @@ -1,11 +1,16 @@ name: Lighthouse CI + on: - push: - branches: - - master + workflow_run: + workflows: ["Build and Deploy"] + types: + - completed + workflow_dispatch: # Allows manual triggering of the workflow + jobs: lighthouse-on-live-site: runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'master') }} steps: - uses: actions/checkout@v4 - name: Audit root using Lighthouse From 393dfbf0538c46fad300ed310bb47ebab4b91ccf Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Thu, 14 Nov 2024 17:29:43 -0700 Subject: [PATCH 8/9] Remove extra white space below player iframe and scrollbar (BL-14065) --- src/components/ReadBookPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReadBookPage.tsx b/src/components/ReadBookPage.tsx index 00c26a81..7a4b4ca3 100644 --- a/src/components/ReadBookPage.tsx +++ b/src/components/ReadBookPage.tsx @@ -315,6 +315,7 @@ const ReadBookPage: React.FunctionComponent = (props) => { border: none; width: 100%; height: 100%; + display: block; // Prevent a 4px white bar at the bottom of the iframe. See BL-14065. `} src={iframeSrc} //src={"https://google.com"} From 204eca200ef24e0cd6d6d32deaf2fe3e04a9ea9a Mon Sep 17 00:00:00 2001 From: Andrew Polk Date: Tue, 19 Nov 2024 08:55:19 -0700 Subject: [PATCH 9/9] Upgrade bloom-player to 2.7.1 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6a5e8d02..ce8b4fd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7216,9 +7216,9 @@ bl@^1.0.0: safe-buffer "^5.1.1" bloom-player@^2.6.8-alpha.1: - version "2.6.8-alpha.1" - resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.6.8-alpha.1.tgz#072f097f335ff771819b5ade470bc45937690f84" - integrity sha512-MYgrSQf3zF+Bo2f8fH6oQmG2m/gdWSTUVeR/eIRpuinqcngqDSVTKcBJKr1OttiLzWoyugsiG1RRKyBlwetOAg== + version "2.7.1" + resolved "https://registry.yarnpkg.com/bloom-player/-/bloom-player-2.7.1.tgz#1ae1f14e3f12bdb36d2ecf3ee1486421bcf0fd32" + integrity sha512-2WqiHHeJkmTwuvsSBDDdJVu/m4k6smx1ViHtDq4MwvyOqJu9S7fawtHTgLNflRZZrWJ9Xusu8gm2Dg2FGpw2Fw== bluebird@^3.3.5, bluebird@^3.5.5: version "3.7.1"