From 45355a9315e03fcc6b8548691bed2f6f035bd577 Mon Sep 17 00:00:00 2001 From: Johannes Zillmann Date: Tue, 9 Mar 2021 08:17:50 +0100 Subject: [PATCH] PageControls --- core/src/Debugger.ts | 10 +++- core/src/ParseResult.ts | 33 +++---------- core/src/PdfParser.ts | 22 +++++++-- core/src/PdfPipeline.ts | 2 +- core/src/debug/StageResult.ts | 7 +-- core/test/Debugger.test.ts | 8 +-- core/test/PdfParser.test.ts | 2 +- core/test/debug/StageResults.test.ts | 33 ------------- ui/src/debug/ControlBar.svelte | 25 ++++------ ui/src/debug/DebugView.svelte | 14 +++--- ui/src/debug/ItemRow.svelte | 21 ++++++-- ui/src/debug/ItemTable.svelte | 18 +++---- ui/src/debug/PageControl.ts | 67 ++++++++++++++++++++++++++ ui/src/debug/PageSelectionPopup.svelte | 30 ++++++------ 14 files changed, 164 insertions(+), 128 deletions(-) create mode 100644 ui/src/debug/PageControl.ts diff --git a/core/src/Debugger.ts b/core/src/Debugger.ts index 7cb35fa..e7140db 100644 --- a/core/src/Debugger.ts +++ b/core/src/Debugger.ts @@ -12,14 +12,22 @@ export default class Debugger { private context: TransformContext; private transformers: ItemTransformer[]; private stageResultCache: StageResult[]; + pageCount: number; fontMap: Map; stageNames: string[]; stageDescriptions: string[]; - constructor(inputSchema: string[], inputItems: Item[], context: TransformContext, transformers: ItemTransformer[]) { + constructor( + pageCount: number, + inputSchema: string[], + inputItems: Item[], + context: TransformContext, + transformers: ItemTransformer[], + ) { this.transformers = transformers; this.context = context; this.fontMap = context.fontMap; + this.pageCount = pageCount; this.stageNames = ['Parse Result', ...transformers.map((t) => t.name)]; this.stageDescriptions = ['Initial items as parsed by PDFjs', ...transformers.map((t) => t.description)]; this.stageResultCache = [initialStage(inputSchema, inputItems)]; diff --git a/core/src/ParseResult.ts b/core/src/ParseResult.ts index 161bdfc..8a2601c 100644 --- a/core/src/ParseResult.ts +++ b/core/src/ParseResult.ts @@ -3,30 +3,13 @@ import type Metadata from './Metadata'; import type PageViewport from './parse/PageViewport'; export default class ParseResult { - fontMap: Map; - pdfjsPages: any[]; - pageViewports: PageViewport[]; - metadata: Metadata; - schema: string[]; - items: Item[]; - constructor( - fontMap: Map, - pdfjsPages: any[], - pageViewports: PageViewport[], - metadata: Metadata, - schema: string[], - items: Item[], - ) { - this.fontMap = fontMap; - this.pdfjsPages = pdfjsPages; - this.pageViewports = pageViewports; - this.metadata = metadata; - this.schema = schema; - this.items = items; - } - - pageCount(): number { - return this.pdfjsPages.length; - } + public fontMap: Map, + public pageCount: number, + public pdfjsPages: any[], + public pageViewports: PageViewport[], + public metadata: Metadata, + public schema: string[], + public items: Item[], + ) {} } diff --git a/core/src/PdfParser.ts b/core/src/PdfParser.ts index 96f7610..0f68783 100644 --- a/core/src/PdfParser.ts +++ b/core/src/PdfParser.ts @@ -23,6 +23,7 @@ export default class PdfParser { .promise.then((pdfjsDocument: any) => { reporter.parsedDocumentHeader(pdfjsDocument.numPages); return Promise.all([ + pdfjsDocument, pdfjsDocument.getMetadata().then((pdfjsMetadata: any) => { reporter.parsedMetadata(); return new Metadata(pdfjsMetadata); @@ -30,10 +31,15 @@ export default class PdfParser { this.extractPagesSequentially(pdfjsDocument, reporter), ]); }) - .then(([metadata, pages]: [Metadata, ParsedPage[]]) => { - return Promise.all([metadata, pages, this.gatherFontObjects(pages).finally(() => reporter.parsedFonts())]); + .then(([pdfjsDocument, metadata, pages]: [any, Metadata, ParsedPage[]]) => { + return Promise.all([ + pdfjsDocument, + metadata, + pages, + this.gatherFontObjects(pages).finally(() => reporter.parsedFonts()), + ]); }) - .then(([metadata, pages, fontMap]: [Metadata, ParsedPage[], Map]) => { + .then(([pdfjsDocument, metadata, pages, fontMap]: [any, Metadata, ParsedPage[], Map]) => { const pdfjsPages = pages.map((page: any) => page.pdfjsPage); const items = pages.reduce((allItems: any[], page: any) => allItems.concat(page.items), []); const pageViewports = pdfjsPages.map((page: any) => { @@ -43,7 +49,15 @@ export default class PdfParser { this.pdfjs.Util.transform(viewPort.transform, itemTransform), }; }); - return new ParseResult(fontMap, pdfjsPages, pageViewports, metadata, this.schema, items); + return new ParseResult( + fontMap, + pdfjsDocument.numPages, + pdfjsPages, + pageViewports, + metadata, + this.schema, + items, + ); }); } diff --git a/core/src/PdfPipeline.ts b/core/src/PdfPipeline.ts index 18b2d60..140f314 100644 --- a/core/src/PdfPipeline.ts +++ b/core/src/PdfPipeline.ts @@ -39,7 +39,7 @@ export default class PdfPipeline { async debug(src: string | Uint8Array | object, progressListener: ProgressListenFunction): Promise { const parseResult = await this.parse(src, progressListener); const context = { fontMap: parseResult.fontMap, pageViewports: parseResult.pageViewports }; - return new Debugger(parseResult.schema, parseResult.items, context, this.transformers); + return new Debugger(parseResult.pageCount, parseResult.schema, parseResult.items, context, this.transformers); } /** diff --git a/core/src/debug/StageResult.ts b/core/src/debug/StageResult.ts index f0e02ab..28f3ecb 100644 --- a/core/src/debug/StageResult.ts +++ b/core/src/debug/StageResult.ts @@ -22,7 +22,7 @@ export default class StageResult { }, []); } - selectPages(relevantChangesOnly: boolean, groupItems: boolean, pinnedPage?: number): Page[] { + selectPages(relevantChangesOnly: boolean, groupItems: boolean): Page[] { let result: Page[]; // Ungroup pages @@ -32,11 +32,6 @@ export default class StageResult { result = this.pages; } - // Filter to pinned page - if (Number.isInteger(pinnedPage)) { - result = result.filter((page) => page.index === pinnedPage); - } - // Filter out item (groups) with no changes if (relevantChangesOnly && !this.descriptor.debug?.showAll === true) { result = result.map( diff --git a/core/test/Debugger.test.ts b/core/test/Debugger.test.ts index a9aa9fc..c8d1673 100644 --- a/core/test/Debugger.test.ts +++ b/core/test/Debugger.test.ts @@ -36,7 +36,7 @@ describe('Transform Items', () => { const trans1Items = parsedItems.map((item) => item.withData({ C: `c=${item.value('A')}+${item.value('B')}` })); const transformers = [new TestTransformer('Trans1', trans1Desc, trans1Schema, trans1Items)]; - const debug = new Debugger(parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); + const debug = new Debugger(1, parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); expect(debug.stageNames).toEqual(['Parse Result', 'Trans1']); expect(debug.stageResults(0).schema).toEqual(parsedSchema.map((column) => ({ name: column }))); @@ -62,7 +62,7 @@ describe('Transform Items', () => { const trans1Items = parsedItems.map((item) => item.withData({ line: item.data['y'] })); const transformers = [new TestTransformer('Trans1', trans1Desc, trans1Schema, trans1Items)]; - const debug = new Debugger(parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); + const debug = new Debugger(1, parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); expect(debug.stageNames).toEqual(['Parse Result', 'Trans1']); expect(debug.stageResults(0).schema).toEqual([{ name: 'id' }, { name: 'y' }]); @@ -100,7 +100,7 @@ test('Change inside of Line', async () => { const trans1Items = swapElements([...parsedItems], 0, 1); const transformers = [new TestTransformer('Trans1', trans1Desc, trans1Schema, trans1Items)]; - const debug = new Debugger(parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); + const debug = new Debugger(1, parsedSchema, parsedItems, { fontMap: new Map(), pageViewports: [] }, transformers); expect(debug.stageNames).toEqual(['Parse Result', 'Trans1']); expect(debug.stageResults(0).schema).toEqual([{ name: 'id' }, { name: 'line' }]); @@ -134,7 +134,7 @@ describe('build schemas', () => { function calculateSchema(inputSchema: string[], outputSchema: string[]): AnnotatedColumn[] { const transformers = [new TestTransformer('Trans1', {}, outputSchema, items)]; - const debug = new Debugger(inputSchema, items, { fontMap: new Map(), pageViewports: [] }, transformers); + const debug = new Debugger(1, inputSchema, items, { fontMap: new Map(), pageViewports: [] }, transformers); return debug.stageResults(1).schema; } diff --git a/core/test/PdfParser.test.ts b/core/test/PdfParser.test.ts index 08de437..2fb3c06 100644 --- a/core/test/PdfParser.test.ts +++ b/core/test/PdfParser.test.ts @@ -21,7 +21,7 @@ test('basic example PDF parse', async () => { const expectedPages = 7; expect(result.metadata.title()).toEqual('ExamplePdf'); expect(result.metadata.author()).toEqual('Johannes Zillmann'); - expect(result.pageCount()).toBe(expectedPages); + expect(result.pageCount).toBe(expectedPages); result.pdfjsPages.forEach((pdfPage, i) => { expect(pdfPage._pageIndex).toBe(i); }); diff --git a/core/test/debug/StageResults.test.ts b/core/test/debug/StageResults.test.ts index ddafd9b..2d1d89b 100644 --- a/core/test/debug/StageResults.test.ts +++ b/core/test/debug/StageResults.test.ts @@ -5,7 +5,6 @@ import AnnotatedColumn from 'src/debug/AnnotatedColumn'; import Page, { asPages } from 'src/debug/Page'; import { items } from '../testItems'; import LineItemMerger from 'src/debug/LineItemMerger'; -import ItemGroup from 'src/debug/ItemGroup'; test('itemsUnpacked', async () => { const tracker = new ChangeTracker(); @@ -77,22 +76,6 @@ describe('select pages', () => { expect(groupElements(allUnpacked[0], 'idx')).toEqual([[0], [1], [2]]); expect(groupElements(allUnpacked[1], 'idx')).toEqual([[3]]); expect(groupElements(allUnpacked[2], 'idx')).toEqual([[4], [5]]); - - const allGroupedWithPin = result.selectPages(false, true, 0); - expect(allGroupedWithPin.map((page) => page.index)).toEqual([0]); - expect(groupElements(allGroupedWithPin[0], 'idx')).toEqual([[0, 1], [2]]); - - const relevantGroupedWithPin = result.selectPages(true, true, 0); - expect(relevantGroupedWithPin.map((page) => page.index)).toEqual([0]); - expect(groupElements(relevantGroupedWithPin[0], 'idx')).toEqual([[0, 1]]); - - const relevantUnpackedWithPin = result.selectPages(true, false, 0); - expect(relevantUnpackedWithPin.map((page) => page.index)).toEqual([0]); - expect(groupElements(relevantUnpackedWithPin[0], 'idx')).toEqual([]); - - const allUnpackedWithPin = result.selectPages(false, false, 0); - expect(allUnpackedWithPin.map((page) => page.index)).toEqual([0]); - expect(groupElements(allUnpackedWithPin[0], 'idx')).toEqual([[0], [1], [2]]); }); test('Changes on element level', async () => { @@ -141,22 +124,6 @@ describe('select pages', () => { expect(groupElements(allUnpacked[0], 'idx')).toEqual([[0], [1], [2]]); expect(groupElements(allUnpacked[1], 'idx')).toEqual([[3]]); expect(groupElements(allUnpacked[2], 'idx')).toEqual([[4], [5], [6]]); - - const allGroupedWithPin = result.selectPages(false, true, 2); - expect(allGroupedWithPin.map((page) => page.index)).toEqual([2]); - expect(groupElements(allGroupedWithPin[0], 'idx')).toEqual([[4, 5], [6]]); - - const relevantGroupedWithPin = result.selectPages(true, true, 2); - expect(relevantGroupedWithPin.map((page) => page.index)).toEqual([2]); - expect(groupElements(relevantGroupedWithPin[0], 'idx')).toEqual([[4, 5]]); - - const relevantUnpackedWithPin = result.selectPages(true, false, 2); - expect(relevantUnpackedWithPin.map((page) => page.index)).toEqual([2]); - expect(groupElements(relevantUnpackedWithPin[0], 'idx')).toEqual([[5]]); - - const allUnpackedWithPin = result.selectPages(false, false, 2); - expect(allUnpackedWithPin.map((page) => page.index)).toEqual([2]); - expect(groupElements(allUnpackedWithPin[0], 'idx')).toEqual([[4], [5], [6]]); }); test('showAll - grouped', async () => { diff --git a/ui/src/debug/ControlBar.svelte b/ui/src/debug/ControlBar.svelte index c617fa2..5c0632b 100644 --- a/ui/src/debug/ControlBar.svelte +++ b/ui/src/debug/ControlBar.svelte @@ -8,8 +8,8 @@ import { BookOpen, ArrowLeft, ArrowRight } from 'svelte-hero-icons'; import { debugStage } from '../config'; - import type StageResult from '@core/debug/StageResult'; + import PageControl from './PageControl'; import Popup from '../components/Popup.svelte'; import PageSelectionPopup from './PageSelectionPopup.svelte'; import Checkbox from '../components/Checkbox.svelte'; @@ -18,26 +18,24 @@ export let stageNames: string[]; export let stageDescriptions: string[]; + export let pageControl: PageControl; export let fontMap: Map; - export let stageResult: StageResult; export let supportsGrouping: boolean; export let supportsRelevanceFiltering: boolean; - export let pinnedPage: number; export let groupingEnabled = true; export let onlyRelevantItems = true; + let { pinnedPageIndex, pagePinned } = pageControl; + $: canNext = $debugStage + 1 < stageNames.length; $: canPrev = $debugStage > 0; - $: pagesNumbers = new Set(stageResult.pages.map((page) => page.index)); - $: maxPage = Math.max(...pagesNumbers); - $: pageIsPinned = !isNaN(pinnedPage);
- {#if pageIsPinned} - (pinnedPage = undefined)} transition:slideH={{ duration: 180, easing: linear }}> + {#if $pagePinned} + pageControl.unpinPage()} transition:slideH={{ duration: 180, easing: linear }}> {/if} @@ -47,12 +45,7 @@ - (pinnedPage = e.detail)} - on:unpinPage={() => (pinnedPage = undefined)} /> + @@ -80,10 +73,10 @@
|
Transformation:
canPrev && debugStage.update((cur) => cur - 1)}> - + canNext && debugStage.update((cur) => cur + 1)}> - + diff --git a/ui/src/debug/DebugView.svelte b/ui/src/debug/DebugView.svelte index bf8a859..8249792 100644 --- a/ui/src/debug/DebugView.svelte +++ b/ui/src/debug/DebugView.svelte @@ -7,19 +7,20 @@ import { debugStage } from '../config'; import ControlBar from './ControlBar.svelte'; import ItemTable from './ItemTable.svelte'; + import PageControl from './PageControl'; export let debug: Debugger; + const pageControl = new PageControl(debug.pageCount); const stageNames = debug.stageNames; - let pinnedPage: number; + const { pinnedPageIndex } = pageControl; let groupingEnabled = true; let onlyRelevantItems = true; $: stageResult = debug.stageResults($debugStage); $: supportsGrouping = !!stageResult.descriptor?.debug?.itemMerger; $: supportsRelevanceFiltering = !stageResult.descriptor?.debug?.showAll; - $: pageIsPinned = !isNaN(pinnedPage); - $: visiblePages = stageResult.selectPages(onlyRelevantItems, groupingEnabled, pinnedPage); + $: visiblePages = pageControl.selectPages(stageResult, onlyRelevantItems, groupingEnabled, $pinnedPageIndex);
@@ -31,13 +32,12 @@ + bind:onlyRelevantItems />
    @@ -48,7 +48,7 @@ {#if visiblePages.find((page) => page.itemGroups.length > 0)} - + {:else}
    diff --git a/ui/src/debug/ItemRow.svelte b/ui/src/debug/ItemRow.svelte index edf812a..b5ef136 100644 --- a/ui/src/debug/ItemRow.svelte +++ b/ui/src/debug/ItemRow.svelte @@ -1,5 +1,7 @@ @@ -77,13 +71,13 @@ {#each page.itemGroups as itemGroup, itemIdx} - + {/each} {/each} -{#if !pageIsPinned} +{#if !pagePinned} {#if renderedMaxPage < pages.length} detail && calculateNextPageToRenderTo()} />
    ...
    diff --git a/ui/src/debug/PageControl.ts b/ui/src/debug/PageControl.ts new file mode 100644 index 0000000..059875a --- /dev/null +++ b/ui/src/debug/PageControl.ts @@ -0,0 +1,67 @@ +import Page from '@core/debug/Page'; +import StageResult from '@core/debug/StageResult'; +import { Writable, writable, get, Readable, derived } from 'svelte/store'; + +export default class PageControl { + pinnedPageIndex: Writable; + pagePinned: Readable; + canPrev: Readable; + canNext: Readable; + pagesWithItems: Set = new Set(); + + constructor(public totalPages: number) { + this.pinnedPageIndex = writable(undefined); + this.pagePinned = derived(this.pinnedPageIndex, (page) => !isNaN(page)); + this.canPrev = derived([this.pinnedPageIndex, this.pagePinned], ([page, pinned]) => pinned && page > 0); + this.canNext = derived( + [this.pinnedPageIndex, this.pagePinned], + ([page, pinned]) => pinned && page < totalPages - 1 + ); + this.next.bind(this); + this.prev.bind(this); + this.unpinPage.bind(this); + } + + selectPages(stageResult: StageResult, relevantChangesOnly: boolean, groupItems: boolean, pinnedPage?: number) { + const allRelevantPages = stageResult.selectPages(relevantChangesOnly, groupItems); + this.pagesWithItems = new Set( + allRelevantPages.filter((page) => page.itemGroups.length > 0).map((page) => page.index) + ); + if (Number.isInteger(pinnedPage)) { + return allRelevantPages.filter((page) => page.index === pinnedPage); + } + return allRelevantPages; + } + + pageHasItems(pageIdx: number) { + return this.pagesWithItems.has(pageIdx); + } + + next() { + if (get(this.canNext)) { + this.pinnedPageIndex.update((page) => page + 1); + } + } + + prev() { + if (get(this.canPrev)) { + this.pinnedPageIndex.update((page) => page - 1); + } + } + + pinPage(pageIdx: number) { + this.pinnedPageIndex.set(pageIdx); + } + + unpinPage() { + this.pinnedPageIndex.set(undefined); + } + + pinnedPage() { + return get(this.pinnedPageIndex); + } + + pageIsPinned() { + return get(this.pagePinned); + } +} diff --git a/ui/src/debug/PageSelectionPopup.svelte b/ui/src/debug/PageSelectionPopup.svelte index d8a6f8c..36dcbfc 100644 --- a/ui/src/debug/PageSelectionPopup.svelte +++ b/ui/src/debug/PageSelectionPopup.svelte @@ -1,37 +1,39 @@
    - - + + -
    - {#each new Array(maxPage + 1) as _, idx} +
    + {#each new Array(pageControl.totalPages) as _, idx}
    pagesNumbers.has(idx) && pinPage(idx)} - class="px-2 border border-gray-300 rounded-full text-center {pagesNumbers.has(idx) ? (pinnedPage === idx ? 'bg-select' : 'hover:text-select hover:border-select cursor-pointer') : 'opacity-50'}"> - {idx} + on:click={() => pageControl.pageHasItems(idx) && pinPage(idx)} + class="px-2 border border-gray-300 rounded-full text-center {pageControl.pageHasItems(idx) ? ($pinnedPageIndex === idx ? 'bg-select' : 'hover:text-select hover:border-select cursor-pointer') : 'opacity-50'}"> + {idx + 1}
    {/each}