mirror of
https://github.com/jzillmann/pdf-to-markdown.git
synced 2024-11-27 18:23:36 +01:00
PageControls
This commit is contained in:
parent
8b7ae34bbc
commit
45355a9315
@ -12,14 +12,22 @@ export default class Debugger {
|
||||
private context: TransformContext;
|
||||
private transformers: ItemTransformer[];
|
||||
private stageResultCache: StageResult[];
|
||||
pageCount: number;
|
||||
fontMap: Map<string, object>;
|
||||
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)];
|
||||
|
@ -3,30 +3,13 @@ import type Metadata from './Metadata';
|
||||
import type PageViewport from './parse/PageViewport';
|
||||
|
||||
export default class ParseResult {
|
||||
fontMap: Map<string, object>;
|
||||
pdfjsPages: any[];
|
||||
pageViewports: PageViewport[];
|
||||
metadata: Metadata;
|
||||
schema: string[];
|
||||
items: Item[];
|
||||
|
||||
constructor(
|
||||
fontMap: Map<string, object>,
|
||||
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<string, object>,
|
||||
public pageCount: number,
|
||||
public pdfjsPages: any[],
|
||||
public pageViewports: PageViewport[],
|
||||
public metadata: Metadata,
|
||||
public schema: string[],
|
||||
public items: Item[],
|
||||
) {}
|
||||
}
|
||||
|
@ -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<string, object>]) => {
|
||||
.then(([pdfjsDocument, metadata, pages, fontMap]: [any, Metadata, ParsedPage[], Map<string, object>]) => {
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ export default class PdfPipeline {
|
||||
async debug(src: string | Uint8Array | object, progressListener: ProgressListenFunction): Promise<Debugger> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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 () => {
|
||||
|
@ -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<string, object>;
|
||||
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);
|
||||
</script>
|
||||
|
||||
<div class="sticky top-0 pt-2 pb-1 z-20 bg-gray-50">
|
||||
<div class="flex items-center space-x-2">
|
||||
{#if pageIsPinned}
|
||||
<span on:click={() => (pinnedPage = undefined)} transition:slideH={{ duration: 180, easing: linear }}>
|
||||
{#if $pagePinned}
|
||||
<span on:click={() => pageControl.unpinPage()} transition:slideH={{ duration: 180, easing: linear }}>
|
||||
<Icon class="text-xs hover:text-select hover:opacity-25 cursor-pointer opacity-75" icon={pin} />
|
||||
</span>
|
||||
{/if}
|
||||
@ -47,12 +45,7 @@
|
||||
<BookOpen size="1x" class="hover:text-select cursor-pointer {opened && 'text-select'}" />
|
||||
</span>
|
||||
<span slot="content">
|
||||
<PageSelectionPopup
|
||||
{pagesNumbers}
|
||||
{maxPage}
|
||||
{pinnedPage}
|
||||
on:pinPage={(e) => (pinnedPage = e.detail)}
|
||||
on:unpinPage={() => (pinnedPage = undefined)} />
|
||||
<PageSelectionPopup {pageControl} />
|
||||
</span>
|
||||
</Popup>
|
||||
</span>
|
||||
@ -80,10 +73,10 @@
|
||||
<div>|</div>
|
||||
<div>Transformation:</div>
|
||||
<span on:click={() => canPrev && debugStage.update((cur) => cur - 1)}>
|
||||
<ArrowLeft size="1x" class={canPrev ? 'hover:text-select cursor-pointer' : 'opacity-50'} />
|
||||
<ArrowLeft size="1x" class={canPrev ? 'hover:text-select cursor-pointer' : 'opacity-25'} />
|
||||
</span>
|
||||
<span on:click={() => canNext && debugStage.update((cur) => cur + 1)}>
|
||||
<ArrowRight size="1x" class={canNext ? 'hover:text-select cursor-pointer' : 'opacity-50'} />
|
||||
<ArrowRight size="1x" class={canNext ? 'hover:text-select cursor-pointer' : 'opacity-25'} />
|
||||
</span>
|
||||
<span>
|
||||
<Popup>
|
||||
|
@ -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);
|
||||
</script>
|
||||
|
||||
<div class="mx-4">
|
||||
@ -31,13 +32,12 @@
|
||||
<ControlBar
|
||||
{stageNames}
|
||||
stageDescriptions={debug.stageDescriptions}
|
||||
{pageControl}
|
||||
fontMap={debug.fontMap}
|
||||
{stageResult}
|
||||
{supportsGrouping}
|
||||
{supportsRelevanceFiltering}
|
||||
bind:groupingEnabled
|
||||
bind:onlyRelevantItems
|
||||
bind:pinnedPage />
|
||||
bind:onlyRelevantItems />
|
||||
|
||||
<!-- Stage Messages -->
|
||||
<ul class="messages list-disc list-inside mb-2 p-2 bg-blue-50 rounded shadow text-sm">
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
<!-- Items -->
|
||||
{#if visiblePages.find((page) => page.itemGroups.length > 0)}
|
||||
<ItemTable schema={stageResult.schema} pages={visiblePages} {pageIsPinned} changes={stageResult.changes} />
|
||||
<ItemTable schema={stageResult.schema} pages={visiblePages} {pageControl} changes={stageResult.changes} />
|
||||
{:else}
|
||||
<!-- No items visible -->
|
||||
<div class="flex space-x-1 items-center justify-center text-xl">
|
||||
|
@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import { scale, fade } from 'svelte/transition';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { ArrowLeft, ArrowRight } from 'svelte-hero-icons';
|
||||
|
||||
import type ItemGroup from '@core/debug/ItemGroup';
|
||||
import type ChangeIndex from '@core/debug/ChangeIndex';
|
||||
@ -7,15 +9,16 @@
|
||||
|
||||
import ChangeSymbol from './ChangeSymbol.svelte';
|
||||
import { formatValue } from './formatValues';
|
||||
import PageControl from './PageControl';
|
||||
|
||||
export let pageControl: PageControl;
|
||||
export let pageIdx: number;
|
||||
export let itemIdx: number;
|
||||
export let schema: AnnotatedColumn[];
|
||||
export let itemGroup: ItemGroup;
|
||||
export let changes: ChangeIndex;
|
||||
export let maxPage: number;
|
||||
export let pageIsPinned: boolean;
|
||||
|
||||
let { pagePinned, canPrev, canNext } = pageControl;
|
||||
let expandedItemGroup: { pageIndex: number; itemIndex: number };
|
||||
|
||||
$: expanded =
|
||||
@ -36,7 +39,17 @@
|
||||
<!-- Page number in first page item row -->
|
||||
{#if itemIdx === 0}
|
||||
<td id="page" class="page bg-gray-50 align-top">
|
||||
<div>Page {pageIdx} {pageIsPinned ? '' : ' / ' + maxPage}</div>
|
||||
<div>Page {pageIdx + 1} {$pagePinned ? '' : ' / ' + pageControl.totalPages}</div>
|
||||
{#if $pagePinned}
|
||||
<div class="absolute flex ml-1 space-x-2">
|
||||
<span on:click={() => pageControl.prev()}>
|
||||
<ArrowLeft size="1x" class={$canPrev ? 'hover:text-select cursor-pointer' : 'opacity-25'} />
|
||||
</span>
|
||||
<span on:click={() => pageControl.next()}>
|
||||
<ArrowRight size="1x" class={$canNext ? 'hover:text-select cursor-pointer' : 'opacity-25'} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
{:else}
|
||||
<td id="page" />
|
||||
|
@ -7,21 +7,22 @@
|
||||
import ColumnAnnotation from '../../../core/src/debug/ColumnAnnotation';
|
||||
|
||||
import inView from '../actions/inView';
|
||||
import PageControl from './PageControl';
|
||||
import ItemRow from './ItemRow.svelte';
|
||||
|
||||
export let schema: AnnotatedColumn[];
|
||||
export let pages: Page[];
|
||||
export let pageIsPinned: boolean;
|
||||
export let pageControl: PageControl;
|
||||
export let changes: ChangeIndex;
|
||||
|
||||
let { pagePinned } = pageControl;
|
||||
let maxPage = pages[pages.length - 1].index;
|
||||
let maxItemsToRenderInOneLoad = 200;
|
||||
let renderedMaxPage = 0;
|
||||
let expandedItemGroup: { pageIndex: number; itemIndex: number };
|
||||
|
||||
let renderedPages: Page[];
|
||||
$: {
|
||||
if (pageIsPinned) {
|
||||
if ($pagePinned) {
|
||||
renderedPages = pages;
|
||||
renderedMaxPage = 0;
|
||||
} else {
|
||||
@ -44,13 +45,6 @@
|
||||
}
|
||||
// console.log(`Render pages 0 to ${renderedMaxPage} with ${itemCount} items`);
|
||||
}
|
||||
|
||||
const isExpanded = (pageIndex: number, itemIndex: number) => {
|
||||
return expandedItemGroup?.pageIndex === pageIndex && expandedItemGroup?.itemIndex === itemIndex;
|
||||
};
|
||||
const toggleRow = (pageIndex: number, itemIndex: number) => {
|
||||
expandedItemGroup = isExpanded(pageIndex, itemIndex) ? undefined : { pageIndex, itemIndex };
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Item table -->
|
||||
@ -77,13 +71,13 @@
|
||||
|
||||
<!-- Page items -->
|
||||
{#each page.itemGroups as itemGroup, itemIdx}
|
||||
<ItemRow {pageIdx} {itemIdx} {schema} {itemGroup} {changes} {maxPage} {pageIsPinned} />
|
||||
<ItemRow pageIdx={page.index} {itemIdx} {schema} {itemGroup} {changes} {pageControl} />
|
||||
{/each}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if !pageIsPinned}
|
||||
{#if !pagePinned}
|
||||
{#if renderedMaxPage < pages.length}
|
||||
<span use:inView on:intersect={({ detail }) => detail && calculateNextPageToRenderTo()} />
|
||||
<div class="my-6 text-center text-2xl">...</div>
|
||||
|
67
ui/src/debug/PageControl.ts
Normal file
67
ui/src/debug/PageControl.ts
Normal file
@ -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<number | undefined>;
|
||||
pagePinned: Readable<boolean>;
|
||||
canPrev: Readable<boolean>;
|
||||
canNext: Readable<boolean>;
|
||||
pagesWithItems: Set<number> = 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);
|
||||
}
|
||||
}
|
@ -1,37 +1,39 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getContext } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Collection } from 'svelte-hero-icons';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let pagesNumbers: Set<number>;
|
||||
export let maxPage: number;
|
||||
export let pinnedPage: number;
|
||||
import PageControl from './PageControl';
|
||||
|
||||
export let pageControl: PageControl;
|
||||
|
||||
let { pinnedPageIndex, pagePinned } = pageControl;
|
||||
|
||||
const popupOpened: Writable<boolean> = getContext('popupOpened');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function pinPage(index: number) {
|
||||
popupOpened.set(false);
|
||||
dispatch('pinPage', index);
|
||||
pageControl.pinPage(index);
|
||||
}
|
||||
function unpinPage() {
|
||||
popupOpened.set(false);
|
||||
dispatch('unpinPage');
|
||||
pageControl.unpinPage();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="absolute mt-2 p-2 flex bg-gray-200 shadow-lg rounded-sm overflow-auto max-h-96" transition:slide>
|
||||
<span class="mt-1 pr-2" on:click={!!pinnedPage && unpinPage}>
|
||||
<Collection size="1x" class={!!pinnedPage ? 'hover:text-select cursor-pointer' : 'opacity-50'} />
|
||||
<span class="mt-1 pr-2" on:click={$pagePinned && unpinPage}>
|
||||
<Collection size="1x" class={$pagePinned ? 'hover:text-select cursor-pointer' : 'opacity-50'} />
|
||||
</span>
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat({Math.min(20, maxPage + 1)}, minmax(0, 1fr));">
|
||||
{#each new Array(maxPage + 1) as _, idx}
|
||||
<div
|
||||
on:click={() => 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}
|
||||
class="grid gap-3"
|
||||
style="grid-template-columns: repeat({Math.min(20, pageControl.totalPages)}, minmax(0, 1fr));">
|
||||
{#each new Array(pageControl.totalPages) as _, idx}
|
||||
<div
|
||||
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}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user