mirror of
https://github.com/jzillmann/pdf-to-markdown.git
synced 2025-06-26 20:41:27 +02:00
Remove UI package
This commit is contained in:
parent
02c2fd04fe
commit
e56d70c599
4
ui/.gitignore
vendored
4
ui/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.build
|
||||
build
|
||||
web_modules
|
||||
node_modules
|
@ -1,2 +0,0 @@
|
||||
package-lock.json
|
||||
public/build/
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"svelteSortOrder": "scripts-markup-styles",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
16
ui/README.md
16
ui/README.md
@ -1,16 +0,0 @@
|
||||
# PDF-To-Markdown Converter Core
|
||||
|
||||
Javascript UI to parse PDF files and convert them into Markdown format. Online at http://pdf2md.morethan.io!
|
||||
|
||||
## Contribute
|
||||
|
||||
Use the [issue tracker](https://github.com/jzillmann/pdf-to-markdown/issues) and/or open [pull requests](https://github.com/jzillmann/pdf-to-markdown/pulls)!
|
||||
|
||||
## Build
|
||||
|
||||
- `npm install` Download all necessary npm packages
|
||||
- `npm start` Run local development build
|
||||
|
||||
## Release
|
||||
|
||||
//TBD
|
9586
ui/package-lock.json
generated
9586
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,57 +0,0 @@
|
||||
{
|
||||
"name": "pdf-to-markdown",
|
||||
"version": "0.2.0",
|
||||
"description": "A PDF to Markdown Converter",
|
||||
"keywords": [
|
||||
"PDF",
|
||||
"Markdown",
|
||||
"Converter"
|
||||
],
|
||||
"author": "Johannes Zillmann",
|
||||
"license": "AGPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jzillmann/pdf-to-markdown"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "snowpack dev",
|
||||
"build": "snowpack build",
|
||||
"svelte-check": "svelte-check",
|
||||
"test": "web-test-runner \"src/**/*.test.ts\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.2",
|
||||
"pdfjs-dist": "2.6.347",
|
||||
"simple-statistics": "^7.7.0",
|
||||
"string-similarity": "4.0.4",
|
||||
"svelte-file-dropzone": "0.0.15",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@snowpack/plugin-build-script": "^2.1.0",
|
||||
"@snowpack/plugin-dotenv": "^2.0.5",
|
||||
"@snowpack/plugin-svelte": "^3.5.2",
|
||||
"@snowpack/plugin-typescript": "^1.2.1",
|
||||
"@snowpack/web-test-runner-plugin": "^0.2.1",
|
||||
"@testing-library/svelte": "^3.0.3",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/snowpack-env": "^2.3.2",
|
||||
"@web/test-runner": "^0.10.0",
|
||||
"chai": "^4.3.0",
|
||||
"fa-svelte": "^3.1.0",
|
||||
"postcss": "^8.2.6",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"postcss-import": "^14.0.0",
|
||||
"postcss-load-config": "^3.0.1",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-plugin-svelte": "^1.4.2",
|
||||
"rollup-plugin-svelte": "^7.1.0",
|
||||
"snowpack": "^3.0.13",
|
||||
"svelte": "^3.34.0",
|
||||
"svelte-check": "^1.1.36",
|
||||
"svelte-hero-icons": "^1.5.0",
|
||||
"svelte-preprocess": "^4.6.9",
|
||||
"tailwindcss": "^2.0.3",
|
||||
"typescript": "^4.2.2"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require("postcss-import"),
|
||||
require("tailwindcss"),
|
||||
require("autoprefixer"),
|
||||
],
|
||||
};
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 318 B |
@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="%PUBLIC_URL%" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Converts PDF files to Markdown." />
|
||||
<meta name="keywords" content="PDF, Markdown, converter, online" />
|
||||
<title>PDF to Markdown</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<script type="module" src="dist/index.js"></script>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,36 +0,0 @@
|
||||
/** @type {import("snowpack").SnowpackUserConfig } */
|
||||
module.exports = {
|
||||
mount: {
|
||||
public: { url: '/', static: true },
|
||||
src: { url: '/dist' },
|
||||
'../core/src': { url: '/core' },
|
||||
'node_modules/pdfjs-dist/es5/build/': { url: '/worker', static: true },
|
||||
},
|
||||
optimize: {
|
||||
bundle: true,
|
||||
minify: true,
|
||||
target: 'es2020',
|
||||
},
|
||||
plugins: [
|
||||
['@snowpack/plugin-build-script', { cmd: 'postcss', input: ['.css'], output: ['.css'] }],
|
||||
'@snowpack/plugin-svelte',
|
||||
'@snowpack/plugin-dotenv',
|
||||
'@snowpack/plugin-typescript',
|
||||
],
|
||||
packageOptions: {
|
||||
installTypes: true,
|
||||
packageLookupFields: ['svelte', 'module', 'main'],
|
||||
// rollup: { plugins: [require('rollup-plugin-svelte')()] },
|
||||
},
|
||||
devOptions: {
|
||||
port: 3005,
|
||||
},
|
||||
buildOptions: {
|
||||
baseUrl: '/pdf-to-markdown-staging/',
|
||||
metaUrlPath: 'modules',
|
||||
},
|
||||
alias: {
|
||||
'@core': '../core/src/index.js',
|
||||
'@core/*': '../core/src/*',
|
||||
},
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
<script>
|
||||
import Upload from './main/Upload.svelte';
|
||||
import { blur } from 'svelte/transition';
|
||||
|
||||
import { parseResult, debug } from './store';
|
||||
import DebugView from './debug/DebugView.svelte';
|
||||
</script>
|
||||
|
||||
<div in:blur={{ duration: 450 }} class="text-2xl font-semibold font-serif text-center bg-gray-400">
|
||||
PDF to Markdown Converter
|
||||
</div>
|
||||
<main class="mt-2 h-full">
|
||||
<div class="transition-container">
|
||||
{#if $debug}
|
||||
<span in:blur={{ duration: 1200 }} out:blur={{ duration: 900 }}>
|
||||
<DebugView debug={$debug} />
|
||||
</span>
|
||||
{:else}
|
||||
<span in:blur={{ duration: 900 }} out:blur={{ duration: 900 }}>
|
||||
<Upload />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
@apply text-gray-800;
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.transition-container {
|
||||
display: grid;
|
||||
grid-template-rows: 1;
|
||||
grid-template-columns: 1;
|
||||
}
|
||||
|
||||
.transition-container > * {
|
||||
/* autoprefixer: off */
|
||||
grid-row: 1;
|
||||
/* autoprefixer: off */
|
||||
grid-column: 1;
|
||||
}
|
||||
</style>
|
@ -1,11 +0,0 @@
|
||||
import {render} from '@testing-library/svelte';
|
||||
import {expect} from 'chai';
|
||||
import App from './App.svelte';
|
||||
|
||||
describe('<App>', () => {
|
||||
it('renders learn svelte link', () => {
|
||||
const {getByText} = render(App);
|
||||
const linkElement = getByText(/learn svelte/i);
|
||||
expect(document.body.contains(linkElement));
|
||||
});
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
@ -1,23 +0,0 @@
|
||||
export function clickOutside(node: HTMLElement, { enabled: initialEnabled, cb }) {
|
||||
const handleOutsideClick = ({ target }) => {
|
||||
if (!node.contains(target)) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
function update({ enabled }) {
|
||||
if (enabled) {
|
||||
window.addEventListener('click', handleOutsideClick);
|
||||
} else {
|
||||
window.removeEventListener('click', handleOutsideClick);
|
||||
}
|
||||
}
|
||||
|
||||
update({ enabled: initialEnabled });
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
window.removeEventListener('click', handleOutsideClick);
|
||||
},
|
||||
};
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
export default function inView(node: HTMLElement) {
|
||||
const handleIntersect = (e: IntersectionObserverEntry[]) => {
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('intersect', {
|
||||
detail: e[0].isIntersecting,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const root = null;
|
||||
const rootMargin = `0px 0px 300px 0px`;
|
||||
const options = { root, rootMargin };
|
||||
const observer = new IntersectionObserver(handleIntersect, options);
|
||||
observer.observe(node);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
if (observer) observer.disconnect();
|
||||
},
|
||||
};
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<script>
|
||||
export let name: string;
|
||||
export let enabled = true;
|
||||
</script>
|
||||
|
||||
<input id="checkbox-{name}" type="checkbox" class="hidden" bind:checked={enabled} />
|
||||
<label
|
||||
for="checkbox-{name}"
|
||||
class="px-1 border-t-2 border-b-2 border-transparent cursor-pointer select-none whitespace-nowrap">{name}</label>
|
||||
|
||||
<style>
|
||||
input:not(:checked) + label:hover {
|
||||
transform: scale(1.1);
|
||||
@apply text-select;
|
||||
}
|
||||
|
||||
input:checked + label:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
input:checked + label {
|
||||
@apply border-select;
|
||||
}
|
||||
|
||||
input + label:hover:not(:active) {
|
||||
@apply border-dotted;
|
||||
}
|
||||
</style>
|
@ -1,5 +0,0 @@
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
|
||||
export default class ComponentDefinition {
|
||||
constructor(public component: object, public args: object = {}) {}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<script>
|
||||
import { writable } from 'svelte/store';
|
||||
import { setContext } from 'svelte';
|
||||
import { clickOutside } from '../actions/clickOutside';
|
||||
|
||||
let opened = writable(false);
|
||||
setContext('popupOpened', opened);
|
||||
|
||||
function toogle() {
|
||||
opened.update((old) => !old);
|
||||
}
|
||||
</script>
|
||||
|
||||
<span use:clickOutside={{ enabled: opened, cb: () => opened.set(false) }}>
|
||||
<span on:click={toogle}>
|
||||
<slot name="trigger" opened={$opened} />
|
||||
</span>
|
||||
|
||||
{#if $opened}
|
||||
<span class="popupContent">
|
||||
<slot name="content" class="z-20" />
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.popupContent :global(*) {
|
||||
@apply z-30;
|
||||
}
|
||||
</style>
|
@ -1,35 +0,0 @@
|
||||
<script>
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
export let radius: number;
|
||||
export let stroke: number;
|
||||
export let progress: number;
|
||||
|
||||
const normalizedRadius = radius - stroke * 2;
|
||||
const circumference = normalizedRadius * 2 * Math.PI;
|
||||
const progressTweened = tweened(0, {
|
||||
duration: 400,
|
||||
easing: cubicOut,
|
||||
});
|
||||
$: progressTweened.set(progress);
|
||||
$: strokeDashoffset = circumference - ($progressTweened / 100) * circumference;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
height={radius * 2}
|
||||
width={radius * 2}
|
||||
class="text-select stroke-current"
|
||||
style="filter: brightness({$progressTweened / 100 / 2 + 0.5}) sepia({0.5 - $progressTweened / 100 / 2}) blur({0.6 - $progressTweened / 100 / 3}px)">
|
||||
<circle
|
||||
fill="transparent"
|
||||
stroke-width={stroke}
|
||||
stroke-dasharray={circumference + ' ' + circumference}
|
||||
stroke-dashoffset={strokeDashoffset}
|
||||
r={normalizedRadius}
|
||||
cx={radius}
|
||||
cy={radius} />
|
||||
<text x="50%" y="53%" text-anchor="middle" class="text-gray-800 fill-current" stroke-width="1px" dy=".2em">
|
||||
{Math.round($progressTweened)}%
|
||||
</text>
|
||||
</svg>
|
@ -1,22 +0,0 @@
|
||||
import { Writable, writable } from 'svelte/store';
|
||||
import { debugFromParams, debugStageFromParams } from './processParameters';
|
||||
|
||||
export const debugEnabled = storedWritable('debugEnabled', false, debugFromParams);
|
||||
export const debugStage = storedWritable('debugStage', 0, debugStageFromParams);
|
||||
|
||||
function storedWritable<T>(key: string, defaultValue: T, paramLoadFunction: (defaultValue: T) => T): Writable<T> {
|
||||
const value = paramLoadFunction(fromLocalStore(key, defaultValue));
|
||||
const store = writable(value);
|
||||
store.subscribe((value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
});
|
||||
return store;
|
||||
}
|
||||
|
||||
function fromLocalStore<T>(key: string, defaultValue: T): T {
|
||||
const storedValue = localStorage.getItem(key);
|
||||
if (storedValue === null || storedValue === 'null') {
|
||||
return defaultValue;
|
||||
}
|
||||
return JSON.parse(storedValue);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
<script>
|
||||
import type EvaluationIndex from '@core/debug/EvaluationIndex';
|
||||
import type ChangeIndex from '@core/debug/ChangeIndex';
|
||||
import type Item from '@core/Item';
|
||||
import { Addition, Removal, ContentChange, PositionChange, Direction } from '../../../core/src/debug/ChangeIndex';
|
||||
import ComponentDefinition from '../components/ComponentDefinition';
|
||||
|
||||
import {
|
||||
PlusCircle as Plus,
|
||||
ExclamationCircle as Changed,
|
||||
MinusCircle as Minus,
|
||||
ArrowCircleUp as Up,
|
||||
ArrowCircleDown as Down,
|
||||
Eye,
|
||||
} from 'svelte-hero-icons';
|
||||
|
||||
export let evaluations: EvaluationIndex;
|
||||
export let changes: ChangeIndex;
|
||||
export let item: Item;
|
||||
|
||||
$: evaluated = evaluations.evaluated(item);
|
||||
$: hasChanged = changes.hasChanged(item);
|
||||
let changeContent: string;
|
||||
let iconComp: ComponentDefinition;
|
||||
$: {
|
||||
let args = { size: '14' };
|
||||
if (hasChanged) {
|
||||
let change = changes.change(item);
|
||||
switch (change.constructor.name) {
|
||||
case PositionChange.name:
|
||||
const positionChange = change as PositionChange;
|
||||
changeContent = `${positionChange.amount}`;
|
||||
iconComp =
|
||||
positionChange.direction === Direction.UP
|
||||
? new ComponentDefinition(Up, args)
|
||||
: new ComponentDefinition(Down, args);
|
||||
break;
|
||||
case Addition.name:
|
||||
iconComp = new ComponentDefinition(Plus, args);
|
||||
break;
|
||||
case Removal.name:
|
||||
iconComp = new ComponentDefinition(Minus, args);
|
||||
break;
|
||||
case ContentChange.name:
|
||||
iconComp = new ComponentDefinition(Changed, args);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`${change.constructor.name}: ${change}`);
|
||||
}
|
||||
} else if (evaluated) {
|
||||
iconComp = new ComponentDefinition(Eye, args);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if evaluated || hasChanged}
|
||||
<div class="flex space-x-0.5 items-center text-xs">
|
||||
{#if iconComp}
|
||||
<svelte:component this={iconComp.component} {...iconComp.args} />
|
||||
{/if}
|
||||
{#if changeContent}
|
||||
<div>{changeContent}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -1,99 +0,0 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
import slideH from '../svelte/slideH';
|
||||
|
||||
import { BookOpen, ArrowLeft, ArrowRight } from 'svelte-hero-icons';
|
||||
|
||||
import { debugStage } from '../config';
|
||||
|
||||
import PageControl from './PageControl';
|
||||
import Popup from '../components/Popup.svelte';
|
||||
import PageSelectionPopup from './PageSelectionPopup.svelte';
|
||||
import Checkbox from '../components/Checkbox.svelte';
|
||||
import TransformerSelectionPopup from './TransformerSelectionPopup.svelte';
|
||||
import FontEntry from './FontEntry.svelte';
|
||||
|
||||
export let stageNames: string[];
|
||||
export let stageDescriptions: string[];
|
||||
export let pageControl: PageControl;
|
||||
export let fontMap: Map<string, object>;
|
||||
export let supportsGrouping: boolean;
|
||||
export let supportsRelevanceFiltering: boolean;
|
||||
|
||||
export let groupingEnabled = true;
|
||||
export let onlyRelevantItems = true;
|
||||
|
||||
$: canNext = $debugStage + 1 < stageNames.length;
|
||||
$: canPrev = $debugStage > 0;
|
||||
</script>
|
||||
|
||||
<div class="sticky top-0 pt-2 pb-1 z-20 bg-gray-50">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Popup>
|
||||
<span slot="trigger" let:opened>
|
||||
<BookOpen size="1x" class="hover:text-select cursor-pointer {opened && 'text-select'}" />
|
||||
</span>
|
||||
<span slot="content">
|
||||
<PageSelectionPopup {pageControl} />
|
||||
</span>
|
||||
</Popup>
|
||||
<Popup>
|
||||
<span slot="trigger" let:opened>
|
||||
<div
|
||||
class="hover:text-select cursor-pointer {opened && 'text-select'}"
|
||||
style="font-family: AmericanTypewriter, verdana">
|
||||
F
|
||||
</div>
|
||||
</span>
|
||||
<span slot="content">
|
||||
<div class="absolute mt-1 py-2 px-2 bg-gray-200 rounded-br">
|
||||
<div class=" overflow-y-scroll " style="max-height: 65vh" transition:slide={{ duration: 400 }}>
|
||||
{#each [...fontMap.keys()] as fontName}
|
||||
<FontEntry {fontMap} {fontName} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</Popup>
|
||||
|
||||
<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-25'} />
|
||||
</span>
|
||||
<span on:click={() => canNext && debugStage.update((cur) => cur + 1)}>
|
||||
<ArrowRight size="1x" class={canNext ? 'hover:text-select cursor-pointer' : 'opacity-25'} />
|
||||
</span>
|
||||
<span>
|
||||
<Popup>
|
||||
<span slot="trigger">
|
||||
<div class="w-52 cursor-pointer hover:underline whitespace-nowrap italic">
|
||||
{stageNames[$debugStage]}
|
||||
</div>
|
||||
</span>
|
||||
<span slot="content">
|
||||
<TransformerSelectionPopup
|
||||
{stageNames}
|
||||
{stageDescriptions}
|
||||
currentStage={$debugStage}
|
||||
on:selectTransformer={({ detail }) => debugStage.set(detail)} />
|
||||
</span>
|
||||
</Popup>
|
||||
</span>
|
||||
<div class="w-full flex flex-row-reverse space-x-2 space-x-reverse text-sm">
|
||||
{#if supportsGrouping}
|
||||
<span class="inline-flex" transition:slideH={{ duration: 700 }}>
|
||||
<Checkbox name="Grouped" bind:enabled={groupingEnabled} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if supportsRelevanceFiltering}
|
||||
<span class="inline-flex" transition:slideH={{ duration: 700 }}>
|
||||
<Checkbox name="Relevant Items" bind:enabled={onlyRelevantItems} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Little cushion sitting between the control bar and the item header. Relevant is the z-index. Item headers should go over it, item rows under! -->
|
||||
<div class="sticky top-8 h-3 bg-gray-50" style="z-index:1" />
|
@ -1,40 +0,0 @@
|
||||
<script>
|
||||
import slideH from '../svelte/slideH';
|
||||
import { linear } from 'svelte/easing';
|
||||
|
||||
import Icon from 'fa-svelte';
|
||||
import { faMapPin as pin } from '@fortawesome/free-solid-svg-icons/faMapPin';
|
||||
import { ArrowLeft, ArrowRight } from 'svelte-hero-icons';
|
||||
|
||||
import PageControl from './PageControl';
|
||||
|
||||
export let pageControl: PageControl;
|
||||
export let pageIndex: number;
|
||||
|
||||
let { pagePinned, canPrev, canNext, pageMapping } = pageControl;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-1">
|
||||
{#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}
|
||||
<div>
|
||||
Page
|
||||
{$pageMapping.pageLabel(pageIndex)}
|
||||
{#if $pageMapping.shifted()}
|
||||
<span class="font-light"> ({pageIndex + 1}/{pageControl.totalPages}) </span>
|
||||
{:else}/ {pageControl.totalPages + $pageMapping.pageFactor - 1}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $pagePinned}
|
||||
<div class="absolute flex ml-4 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}
|
@ -1,93 +0,0 @@
|
||||
<script>
|
||||
import { slide, blur } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
import type Debugger from '@core/Debugger';
|
||||
import { PAGE_MAPPING } from '../../../core/src/transformer/CacluclateStatistics';
|
||||
|
||||
import { debugStage } from '../config';
|
||||
import ControlBar from './ControlBar.svelte';
|
||||
import ItemTable from './ItemTable.svelte';
|
||||
import PageControl from './PageControl';
|
||||
import CurrentPage from './CurrentPage.svelte';
|
||||
|
||||
export let debug: Debugger;
|
||||
|
||||
const pageControl = new PageControl(debug.pageCount);
|
||||
const stageNames = debug.stageNames;
|
||||
const { pinnedPageIndex, pagePinned } = pageControl;
|
||||
let groupingEnabled = true;
|
||||
let onlyRelevantItems = true;
|
||||
|
||||
$: stageResult = debug.stageResult($debugStage);
|
||||
$: pageControl.updateMapping(stageResult.globals.getOptional(PAGE_MAPPING));
|
||||
$: supportsGrouping = !!stageResult.descriptor?.debug?.itemMerger;
|
||||
$: supportsRelevanceFiltering = !stageResult.descriptor?.debug?.showAll;
|
||||
$: visiblePages = pageControl.selectPages(stageResult, onlyRelevantItems, groupingEnabled, $pinnedPageIndex);
|
||||
</script>
|
||||
|
||||
<div class="mx-4">
|
||||
<!-- <div>Parsed {parseResult.pageCount()} pages with {parseResult.items.length} items</div>
|
||||
<div>Title: {parseResult.metadata.title()}</div>
|
||||
<div>Author: {parseResult.metadata.author()}</div> -->
|
||||
|
||||
<!-- Sticky Controls -->
|
||||
<ControlBar
|
||||
{stageNames}
|
||||
stageDescriptions={debug.stageDescriptions}
|
||||
{pageControl}
|
||||
fontMap={debug.fontMap}
|
||||
{supportsGrouping}
|
||||
{supportsRelevanceFiltering}
|
||||
bind:groupingEnabled
|
||||
bind:onlyRelevantItems />
|
||||
|
||||
<!-- Stage Messages -->
|
||||
<ul class="messages list-disc list-inside mb-2 p-2 bg-blue-50 rounded shadow text-sm">
|
||||
{#each stageResult.messages as message (message)}
|
||||
<li animate:flip in:slide>{message}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<!-- Items -->
|
||||
{#if visiblePages.find((page) => page.itemGroups.length > 0)}
|
||||
<ItemTable
|
||||
schema={stageResult.schema}
|
||||
pages={visiblePages}
|
||||
{pageControl}
|
||||
evaluations={stageResult.evaluations}
|
||||
changes={stageResult.changes} />
|
||||
{:else}
|
||||
<!-- No items visible -->
|
||||
<div class="flex mt-8">
|
||||
{#if $pagePinned}
|
||||
<span class="w-36">
|
||||
<CurrentPage {pageControl} pageIndex={$pinnedPageIndex} />
|
||||
</span>
|
||||
{/if}
|
||||
<div class="flex w-full space-x-1 items-center justify-center text-xl">
|
||||
<div in:blur={{ delay: 500 }}>No visible changes from the transformation.</div>
|
||||
{#if supportsRelevanceFiltering && onlyRelevantItems}
|
||||
<div in:blur={{ delay: 900 }}>Disable the</div>
|
||||
<div
|
||||
in:blur={{ delay: 950 }}
|
||||
class="font-bold cursor-pointer hover:underline"
|
||||
on:click={() => (onlyRelevantItems = false)}>
|
||||
relevance filter
|
||||
</div>
|
||||
<div in:blur={{ delay: 990 }}>?</div>
|
||||
{/if}
|
||||
{#if supportsGrouping && !groupingEnabled}
|
||||
<div in:blur={{ delay: 1000 }}>Enable</div>
|
||||
<div
|
||||
in:blur={{ delay: 1050 }}
|
||||
class="font-bold cursor-pointer hover:underline"
|
||||
on:click={() => (groupingEnabled = true)}>
|
||||
grouping
|
||||
</div>
|
||||
<div in:blur={{ delay: 1090 }}>?</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -1,58 +0,0 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
export let fontName: string;
|
||||
export let fontMap: Map<string, object>;
|
||||
|
||||
let collapsed = true;
|
||||
$: font = fontMap.get(fontName);
|
||||
</script>
|
||||
|
||||
<div class="pb-1 rounded shadow bg-gray-300 min-w-max">
|
||||
<div
|
||||
class="twoColumned header py-1 px-2 text-sm bg-gray-400 rounded-t cursor-pointer"
|
||||
class:opened={!collapsed}
|
||||
on:click={() => (collapsed = !collapsed)}>
|
||||
<div class="font-semibold">{fontName}</div>
|
||||
<div class="">{font['name']}</div>
|
||||
</div>
|
||||
|
||||
{#if !collapsed}
|
||||
<div class="twoColumned px-2 text-sm" transition:slide>
|
||||
<div>Type:</div>
|
||||
<div>{font['type']}</div>
|
||||
<div>MimeType:</div>
|
||||
<div>{font['mimetype']}</div>
|
||||
<div>Ascent:</div>
|
||||
<div>{font['ascent'].toFixed(2)}</div>
|
||||
<div>Descent:</div>
|
||||
<div>{font['descent'].toFixed(2)}</div>
|
||||
<div>BBox:</div>
|
||||
<div>{font['bbox'].join(', ')}</div>
|
||||
<div>Matrix:</div>
|
||||
<div>{font['fontMatrix'].join(', ')}</div>
|
||||
<div>Vertical:</div>
|
||||
<div>{font['vertical']}</div>
|
||||
<div>Monospace:</div>
|
||||
<div>{font['isMonospace']}</div>
|
||||
<div>Setif:</div>
|
||||
<div>{font['isSerifFont']}</div>
|
||||
<div>Type3:</div>
|
||||
<div>{font['isType3Font']}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.twoColumned {
|
||||
@apply grid;
|
||||
@apply gap-x-2;
|
||||
grid-template-columns: 1fr 2.5fr;
|
||||
}
|
||||
.header:hover {
|
||||
@apply bg-select;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
.opened {
|
||||
@apply bg-select;
|
||||
}
|
||||
</style>
|
@ -1,135 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import type ItemGroup from '@core/debug/ItemGroup';
|
||||
import type EvaluationIndex from '@core/debug/EvaluationIndex';
|
||||
import type ChangeIndex from '@core/debug/ChangeIndex';
|
||||
import type AnnotatedColumn from '@core/debug/AnnotatedColumn';
|
||||
|
||||
import ChangeSymbol from './ChangeSymbol.svelte';
|
||||
import { formatValue } from './formatValues';
|
||||
import PageControl from './PageControl';
|
||||
import CurrentPage from './CurrentPage.svelte';
|
||||
|
||||
export let pageControl: PageControl;
|
||||
export let pageIdx: number;
|
||||
export let itemIdx: number;
|
||||
export let schema: AnnotatedColumn[];
|
||||
export let itemGroup: ItemGroup;
|
||||
export let evaluations: EvaluationIndex;
|
||||
export let changes: ChangeIndex;
|
||||
|
||||
let expandedItemGroup: { pageIndex: number; itemIndex: number };
|
||||
|
||||
$: expanded =
|
||||
expandedItemGroup && expandedItemGroup.pageIndex === pageIdx && expandedItemGroup.itemIndex === itemIdx;
|
||||
|
||||
const toggleRow = (pageIndex: number, itemIndex: number) => {
|
||||
expandedItemGroup = expanded ? undefined : { pageIndex, itemIndex };
|
||||
};
|
||||
</script>
|
||||
|
||||
<tr
|
||||
class:expandable={itemGroup.hasMany()}
|
||||
class:expanded
|
||||
class:changePlus={changes.isPlusChange(itemGroup.top)}
|
||||
class:changeNeutral={changes.isNeutralChange(itemGroup.top)}
|
||||
class:changeMinus={changes.isMinusChange(itemGroup.top)}
|
||||
in:fade>
|
||||
<!-- Page number in first page item row -->
|
||||
{#if itemIdx === 0}
|
||||
<td id="page" class="page bg-gray-50 align-top">
|
||||
<CurrentPage pageIndex={pageIdx} {pageControl} />
|
||||
</td>
|
||||
{:else}
|
||||
<td id="page" />
|
||||
{/if}
|
||||
<td class="align-baseline">
|
||||
<ChangeSymbol {evaluations} {changes} item={itemGroup.top} />
|
||||
</td>
|
||||
{#if evaluations.hasScores()}
|
||||
<td class="whitespace-nowrap">{evaluations.evaluationScore(itemGroup.top)}</td>
|
||||
{/if}
|
||||
<span class="contents" on:click={() => itemGroup.hasMany() && toggleRow(pageIdx, itemIdx)}>
|
||||
<!-- ID & change marker column -->
|
||||
<td class="align-top">
|
||||
<div class="flex space-x-0.5 items-center">
|
||||
<div>{itemIdx}{itemGroup.hasMany() ? '…' : ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Row values -->
|
||||
{#each schema as column}
|
||||
<td class="select-all">{formatValue(itemGroup.top.data[column.name])}</td>
|
||||
{/each}
|
||||
</span>
|
||||
</tr>
|
||||
|
||||
<!-- Expanded childs -->
|
||||
{#if expanded}
|
||||
{#each itemGroup.elements as child, childIdx}
|
||||
<tr
|
||||
class="childs"
|
||||
class:changePlus={changes.isPlusChange(child)}
|
||||
class:changeNeutral={changes.isNeutralChange(child)}
|
||||
class:changeMinus={changes.isMinusChange(child)}>
|
||||
<td id="page" />
|
||||
<td class="align-baseline">
|
||||
<ChangeSymbol {evaluations} {changes} item={child} />
|
||||
</td>
|
||||
{#if evaluations.hasScores()}
|
||||
<td class="whitespace-nowrap">{evaluations.evaluationScore(itemGroup.top)}</td>
|
||||
{/if}
|
||||
<td class="whitespace-nowrap">
|
||||
<div class="flex space-x-1">
|
||||
<div class="w-8">{'└ ' + childIdx}</div>
|
||||
</div>
|
||||
</td>
|
||||
{#each schema as column}
|
||||
<td class="select-all">{formatValue(child.data[column.name])}</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.page {
|
||||
@apply font-semibold;
|
||||
@apply pr-4;
|
||||
@apply whitespace-nowrap;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 2.4em;
|
||||
z-index: 2;
|
||||
}
|
||||
td:not(#page) {
|
||||
@apply px-1;
|
||||
@apply border-b;
|
||||
}
|
||||
|
||||
tr:hover td:not(#page) {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
tr.expandable:hover td:not(#page) {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
tr.expanded td:not(#page) {
|
||||
@apply bg-gray-300;
|
||||
}
|
||||
|
||||
tr.childs td:not(#page) {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
tr.changePlus td:not(#page) {
|
||||
@apply text-green-700;
|
||||
}
|
||||
tr.changeMinus td:not(#page) {
|
||||
@apply text-red-700;
|
||||
}
|
||||
tr.changeNeutral td:not(#page) {
|
||||
@apply text-yellow-500;
|
||||
}
|
||||
</style>
|
@ -1,113 +0,0 @@
|
||||
<script>
|
||||
import { scale } from 'svelte/transition';
|
||||
|
||||
import { PresentationChartLine } from 'svelte-hero-icons';
|
||||
|
||||
import type AnnotatedColumn from '@core/debug/AnnotatedColumn';
|
||||
import type EvaluationIndex from '@core/debug/EvaluationIndex';
|
||||
import type ChangeIndex from '@core/debug/ChangeIndex';
|
||||
import type Page from '@core/debug/Page';
|
||||
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 pageControl: PageControl;
|
||||
export let evaluations: EvaluationIndex;
|
||||
export let changes: ChangeIndex;
|
||||
|
||||
let { pagePinned } = pageControl;
|
||||
let maxItemsToRenderInOneLoad = 200;
|
||||
let renderedMaxPage = 0;
|
||||
|
||||
let renderedPages: Page[];
|
||||
$: {
|
||||
if ($pagePinned) {
|
||||
renderedPages = pages;
|
||||
renderedMaxPage = 0;
|
||||
} else {
|
||||
if (renderedMaxPage === 0) {
|
||||
calculateNextPageToRenderTo();
|
||||
}
|
||||
renderedPages = pages.slice(0, renderedMaxPage);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateNextPageToRenderTo() {
|
||||
if (renderedMaxPage >= pages.length) {
|
||||
return;
|
||||
}
|
||||
let itemCount = 0;
|
||||
for (let index = 0; index < pages.length; index++) {
|
||||
renderedMaxPage++;
|
||||
itemCount += pages[index].itemGroups.length;
|
||||
if (itemCount > maxItemsToRenderInOneLoad) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// console.log(`Render pages 0 to ${renderedMaxPage} with ${itemCount} items`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Item table -->
|
||||
<table class="w-full text-left">
|
||||
<!-- Sticky header -->
|
||||
<thead class="">
|
||||
<th />
|
||||
{#if changes.changeCount() > 0}
|
||||
<th class="bg-gray-300 shadow">
|
||||
<PresentationChartLine size="1x" />
|
||||
</th>
|
||||
{:else}
|
||||
<th class="bg-gray-50" />
|
||||
{/if}
|
||||
{#if evaluations.hasScores()}
|
||||
<th class="bg-gray-300 shadow">score</th>
|
||||
{/if}
|
||||
<th class="bg-gray-300 shadow">#</th>
|
||||
{#each schema as column (column.name)}
|
||||
<th
|
||||
transition:scale
|
||||
class="bg-gray-300 shadow {column.annotation === ColumnAnnotation.ADDED ? 'text-green-800' : column.annotation === ColumnAnnotation.REMOVED ? 'text-red-800' : ''} transition-colors duration-300 delay-200">
|
||||
{column.name}
|
||||
</th>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each renderedPages as page, pageIdx}
|
||||
<!-- Separator between pages -->
|
||||
{#if pageIdx > 0}
|
||||
<tr class="h-5" />
|
||||
{/if}
|
||||
|
||||
<!-- Page items -->
|
||||
{#each page.itemGroups as itemGroup, itemIdx}
|
||||
<ItemRow pageIdx={page.index} {itemIdx} {schema} {itemGroup} {evaluations} {changes} {pageControl} />
|
||||
{/each}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if $pagePinned}
|
||||
<div class="mb-8" />
|
||||
{:else}
|
||||
{#if renderedMaxPage < pages.length}
|
||||
<span use:inView on:intersect={({ detail }) => detail && calculateNextPageToRenderTo()} />
|
||||
<div class="my-6 text-center text-2xl">...</div>
|
||||
{:else}
|
||||
<div class="my-6 text-center text-2xl">FIN!</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
th {
|
||||
@apply px-1;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 2.4em;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
@ -1,74 +0,0 @@
|
||||
import StageResult from '@core/debug/StageResult';
|
||||
import PageMapping from '../../../core/src/PageMapping';
|
||||
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();
|
||||
pageMapping: Writable<PageMapping> = writable(new PageMapping());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
updateMapping(pageMapping: PageMapping | undefined) {
|
||||
if (pageMapping) {
|
||||
this.pageMapping.set(pageMapping);
|
||||
}
|
||||
}
|
||||
|
||||
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,55 +0,0 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getContext } from 'svelte';
|
||||
import { Collection } from 'svelte-hero-icons';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
import PageControl from './PageControl';
|
||||
|
||||
export let pageControl: PageControl;
|
||||
|
||||
let { pinnedPageIndex, pagePinned, pageMapping } = pageControl;
|
||||
let showIndex = false;
|
||||
|
||||
const popupOpened: Writable<boolean> = getContext('popupOpened');
|
||||
|
||||
function pinPage(index: number) {
|
||||
popupOpened.set(false);
|
||||
pageControl.pinPage(index);
|
||||
}
|
||||
function unpinPage() {
|
||||
popupOpened.set(false);
|
||||
pageControl.unpinPage();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="absolute my-2 p-2 flex bg-gray-200 shadow-lg rounded-sm overflow-auto max-h-96"
|
||||
style="max-width: 77%;"
|
||||
transition:slide>
|
||||
<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="flex flex-wrap gap-1">
|
||||
{#each new Array(pageControl.totalPages) as _, idx}
|
||||
<div
|
||||
class="flex-grow-0 px-2 w-12 max-w-xs 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'}"
|
||||
on:click={() => pageControl.pageHasItems(idx) && pinPage(idx)}>
|
||||
{#if showIndex}{idx}{:else}{$pageMapping.pageLabel(idx)}{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
</style>
|
@ -1,83 +0,0 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getContext } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let stageNames: string[];
|
||||
export let stageDescriptions: string[];
|
||||
export let currentStage: number;
|
||||
|
||||
const popupOpened: Writable<boolean> = getContext('popupOpened');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function selectTransformer(index: number) {
|
||||
popupOpened.set(false);
|
||||
dispatch('selectTransformer', index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="absolute -mt-6 " transition:slide>
|
||||
<div class="p-2 bg-gray-200 shadow-lg rounded-sm overflow-auto max-h-96">
|
||||
{#each stageNames as stageName, idx}
|
||||
<div
|
||||
on:click={() => selectTransformer(idx)}
|
||||
class="tooltip px-2"
|
||||
class:selected={idx == currentStage}
|
||||
class:selectable={idx != currentStage}
|
||||
data-text={stageDescriptions[idx]}>
|
||||
{stageName}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.selected {
|
||||
@apply cursor-default;
|
||||
@apply bg-gray-300;
|
||||
@apply rounded;
|
||||
}
|
||||
.selectable {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
.selectable:hover {
|
||||
@apply bg-select;
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.tooltip:before {
|
||||
content: attr(data-text); /* here's the magic */
|
||||
position: absolute;
|
||||
|
||||
transform: translateY(-2px);
|
||||
|
||||
/* move to right */
|
||||
left: 100%;
|
||||
|
||||
/* the arrow */
|
||||
border: 10px solid;
|
||||
@apply border-select;
|
||||
@apply border-t-transparent;
|
||||
@apply border-r-transparent;
|
||||
@apply border-b-transparent;
|
||||
|
||||
/* basic styles */
|
||||
@apply ml-2;
|
||||
@apply px-2;
|
||||
@apply py-1;
|
||||
@apply w-72;
|
||||
@apply bg-gray-300;
|
||||
@apply text-gray-800;
|
||||
@apply rounded;
|
||||
@apply shadow-sm;
|
||||
@apply text-center;
|
||||
@apply italic;
|
||||
|
||||
display: none; /* hide by default */
|
||||
}
|
||||
.tooltip:hover:before {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
@ -1,19 +0,0 @@
|
||||
export function formatValue(value: object) {
|
||||
if (typeof value === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return (value as number).toFixed(2);
|
||||
}
|
||||
if (typeof value === 'object' && typeof Array.isArray(value)) {
|
||||
let array = value as Array<object>;
|
||||
if (array.length > 0 && typeof array[0] === 'number') {
|
||||
array = (array.map((element) => ((element as unknown) as number).toFixed(2)) as unknown) as Array<object>;
|
||||
}
|
||||
return '[' + array.join(', ') + ']';
|
||||
}
|
||||
return value;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import App from './App.svelte';
|
||||
import './Tailwind.css';
|
||||
|
||||
var app = new App({
|
||||
target: document.body,
|
||||
intro: true,
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
|
||||
// Learn more: https://www.snowpack.dev/concepts/hot-module-replacement
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept();
|
||||
import.meta.hot.dispose(() => {
|
||||
app.$destroy();
|
||||
});
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
<script>
|
||||
import { blur, slide } from 'svelte/transition';
|
||||
import Dropzone from 'svelte-file-dropzone';
|
||||
import { Download, Check } from 'svelte-hero-icons';
|
||||
import { processUpload, loadExample, loadUrl } from '../store';
|
||||
import type Progress from '@core/Progress';
|
||||
import ProgressRing from '../components/ProgressRing.svelte';
|
||||
import Checkbox from '../components/Checkbox.svelte';
|
||||
import { debugEnabled } from '../config';
|
||||
import { urlFromParams } from '../processParameters';
|
||||
|
||||
let specifiedFileName: string;
|
||||
let dragover = false;
|
||||
let upload: Promise<any>;
|
||||
let rejectionError: string;
|
||||
let parseProgress: Progress;
|
||||
|
||||
const url = urlFromParams();
|
||||
if (url) {
|
||||
dragover = true;
|
||||
specifiedFileName = url;
|
||||
upload = loadUrl(handleProgress, url);
|
||||
}
|
||||
|
||||
function handleUrlLoad() {
|
||||
dragover = true;
|
||||
let answer = prompt('Url of the pdf');
|
||||
specifiedFileName = answer;
|
||||
rejectionError = undefined;
|
||||
parseProgress = undefined;
|
||||
upload = loadUrl(handleProgress, answer);
|
||||
}
|
||||
|
||||
function handleExampleLoad() {
|
||||
dragover = true;
|
||||
specifiedFileName = 'ExamplePdf.pdf';
|
||||
rejectionError = undefined;
|
||||
parseProgress = undefined;
|
||||
upload = loadExample(handleProgress);
|
||||
}
|
||||
|
||||
function handleFilesSelect(e) {
|
||||
specifiedFileName = undefined;
|
||||
rejectionError = undefined;
|
||||
parseProgress = undefined;
|
||||
const { acceptedFiles, fileRejections } = e.detail;
|
||||
if (acceptedFiles.length === 1) {
|
||||
const specifiedFile = acceptedFiles[0];
|
||||
specifiedFileName = specifiedFile.name;
|
||||
upload = processUpload(specifiedFile, handleProgress);
|
||||
}
|
||||
if (fileRejections.length > 1) {
|
||||
const fileNames = fileRejections.map((r) => r.file.name);
|
||||
rejectionError = `Only one file at a time allowed! Rejected ${fileRejections.length} files: '${fileNames}'.`;
|
||||
}
|
||||
}
|
||||
|
||||
function handleProgress(progress: Progress) {
|
||||
parseProgress = progress;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<!-- Options -->
|
||||
<div class="mb-0.5 flex flex-row-reverse space-x-2 space-x-reverse text-sm items-center">
|
||||
<div class="py-0.5 border-2 border-gray-50 hover:underline cursor-pointer" on:click={handleExampleLoad}>
|
||||
Load Example
|
||||
</div>
|
||||
<div class="py-0.5 border-2 border-gray-50 hover:underline cursor-pointer" on:click={handleUrlLoad}>
|
||||
Open Url
|
||||
</div>
|
||||
<Checkbox name="Debug" bind:enabled={$debugEnabled} />
|
||||
</div>
|
||||
|
||||
<!-- Upload Box -->
|
||||
<div class="mb-5 border-2 border-dashed border-gray-400 hover:border-select" class:dragover>
|
||||
<Dropzone
|
||||
on:drop={handleFilesSelect}
|
||||
on:dragenter={() => (dragover = true)}
|
||||
on:dragleave={() => (dragover = false)}
|
||||
multiple={false}
|
||||
noClick={false}
|
||||
disableDefaultStyles={true}>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 justify-items-center">
|
||||
<span class:dragoverItem={dragover}>
|
||||
<Download size="21x" />
|
||||
</span>
|
||||
<div class="px-5 mb-5">
|
||||
<div class="text-5xl font-bold my-4">Drop your PDF file here...</div>
|
||||
<div class="text-2xl font-bold">Or click the box to select one...</div>
|
||||
<div class="mt-14"><strong>Note:</strong> Your data stays locally in your browser.</div>
|
||||
<div class="mt-5 text-sm italic font-serif">
|
||||
This tool converts a PDF file into a Markdown text format! Simply drag & drop your PDF file on
|
||||
the upload area and go from there. Don't expect wonders, there are a lot of variances in
|
||||
generated PDF's from different tools and different ages. No matter how good the parser works for
|
||||
your PDF, you will have to invest a good amount of manuell work to complete it.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dropzone>
|
||||
</div>
|
||||
|
||||
<!-- Progress Info -->
|
||||
<div class="mt-5 text-xl font-bold">
|
||||
<div style="min-width: 70%;">
|
||||
{#if specifiedFileName}
|
||||
<div in:blur class="text-2xl mb-2">Parsing {specifiedFileName} ...</div>
|
||||
{/if}
|
||||
{#if parseProgress}
|
||||
<div in:blur class="flex space-x-4">
|
||||
<ProgressRing radius={50} stroke={7} progress={parseProgress?.totalProgress() * 100} />
|
||||
<div>
|
||||
{#each parseProgress.stages as stage, index}
|
||||
{#if parseProgress.isProgressing(index)}
|
||||
<div class="flex space-x-2 items-center">
|
||||
<div>
|
||||
Parsing
|
||||
{stage}
|
||||
{parseProgress.stageDetails[index] ? parseProgress.stageDetails[index] : ''}
|
||||
</div>
|
||||
</div>
|
||||
{:else if parseProgress.isComplete(index)}
|
||||
<div class="flex space-x-2 items-center ">
|
||||
<div>
|
||||
Parsing
|
||||
{stage}
|
||||
{parseProgress.stageDetails[index] ? parseProgress.stageDetails[index] : ''}
|
||||
</div>
|
||||
<Check size="1.5x" class="text-select" />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if rejectionError}
|
||||
<div in:slide class="text-red-700">{rejectionError}</div>
|
||||
{/if}
|
||||
{#await upload}
|
||||
<!-- -->
|
||||
{:catch error}
|
||||
<div in:blur={{ delay: 200 }} class="text-red-700">
|
||||
Failed to parse '{specifiedFileName}':
|
||||
{error?.message}
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dragover {
|
||||
@apply border-select;
|
||||
}
|
||||
.dragoverItem {
|
||||
@apply text-select;
|
||||
}
|
||||
</style>
|
@ -1,29 +0,0 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
export function debugFromParams(defaultValue: boolean): boolean {
|
||||
const defined = params.has('debug');
|
||||
if (!defined) {
|
||||
return defaultValue;
|
||||
}
|
||||
return params.get('debug') !== 'false';
|
||||
}
|
||||
|
||||
export function debugStageFromParams(defaultValue: number): number {
|
||||
if (!params.has('stage')) {
|
||||
return defaultValue;
|
||||
}
|
||||
const stage = +params.get('stage');
|
||||
if (!Number.isInteger(stage)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return stage;
|
||||
}
|
||||
|
||||
export function urlFromParams(): string {
|
||||
return getParameterByName('url');
|
||||
}
|
||||
|
||||
function getParameterByName(name: string): string {
|
||||
const match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import { createPipeline } from '@core';
|
||||
import type ProgressListenFunction from '@core/ProgressListenFunction';
|
||||
import type ParseResult from '@core/ParseResult';
|
||||
import type Debugger from '@core/Debugger';
|
||||
import * as pdfjs from 'pdfjs-dist/es5/build/pdf';
|
||||
|
||||
import { Writable, writable } from 'svelte/store';
|
||||
|
||||
export let debug: Writable<Debugger> = writable(undefined);
|
||||
export let parseResult: Writable<ParseResult> = writable(undefined);
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `worker/pdf.worker.min.js`;
|
||||
|
||||
const pdfPipeline = createPipeline(pdfjs, {});
|
||||
|
||||
export async function loadExample(progressListener: ProgressListenFunction): Promise<any> {
|
||||
return parsePdf('ExamplePdf.pdf', progressListener);
|
||||
}
|
||||
|
||||
export async function loadUrl(progressListener: ProgressListenFunction, url: String): Promise<any> {
|
||||
return parsePdf('https://pdf-to-markdown-proxy.herokuapp.com/' + url, progressListener);
|
||||
}
|
||||
|
||||
export async function processUpload(file: File, progressListener: ProgressListenFunction): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = reject;
|
||||
reader.onload = () => {
|
||||
resolve(reader.result as ArrayBuffer);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}).then((buffer) => {
|
||||
const data = new Uint8Array(buffer as ArrayBuffer);
|
||||
return parsePdf(data, progressListener);
|
||||
});
|
||||
}
|
||||
|
||||
async function parsePdf(src: string | Uint8Array, progressListener: ProgressListenFunction): Promise<any> {
|
||||
return pdfPipeline.debug(src, progressListener).then((debugInstance) => {
|
||||
debug.set(debugInstance);
|
||||
return debug;
|
||||
});
|
||||
//TODO without debug-flag
|
||||
// return pdfPipeline.execute(src, progressListener).then((result) => {
|
||||
// parseResult.set(result);
|
||||
// return result;
|
||||
// });
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import { backInOut as easing } from 'svelte/easing';
|
||||
|
||||
const defaultOptions = {
|
||||
delay: 0,
|
||||
duration: 400,
|
||||
easing,
|
||||
};
|
||||
|
||||
/**
|
||||
* Slide the element horizontally, across the X-axis.
|
||||
* @param node
|
||||
* @param options
|
||||
*/
|
||||
export default function slideH(node: HTMLElement, options: object) {
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
|
||||
const style = getComputedStyle(node);
|
||||
const opacity = +style.opacity;
|
||||
const width = parseFloat(style.width);
|
||||
const padding_left = parseFloat(style.paddingLeft);
|
||||
const padding_right = parseFloat(style.paddingRight);
|
||||
const margin_left = parseFloat(style.marginLeft);
|
||||
const margin_right = parseFloat(style.marginRight);
|
||||
const border_left_width = parseFloat(style.borderLeftWidth);
|
||||
const border_right_width = parseFloat(style.borderRightWidth);
|
||||
|
||||
return {
|
||||
delay: mergedOptions.delay,
|
||||
duration: mergedOptions.duration,
|
||||
easing: mergedOptions.easing,
|
||||
css: (t: number) =>
|
||||
`overflow: hidden;` +
|
||||
`opacity: ${Math.min(t * 20, 1) * opacity};` +
|
||||
`width: ${t * width}px;` +
|
||||
`padding-left: ${t * padding_left}px;` +
|
||||
`padding-right: ${t * padding_right}px;` +
|
||||
`margin-left: ${t * margin_left}px;` +
|
||||
`margin-right: ${t * margin_right}px;` +
|
||||
`border-left-width: ${t * border_left_width}px;` +
|
||||
`border-right-width: ${t * border_right_width}px;`,
|
||||
};
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
const autoPreprocess = require('svelte-preprocess');
|
||||
|
||||
module.exports = {
|
||||
preprocess: autoPreprocess({
|
||||
defaults: {
|
||||
script: 'typescript',
|
||||
},
|
||||
postcss: true,
|
||||
}),
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
module.exports = {
|
||||
future: {
|
||||
removeDeprecatedGapUtilities: true,
|
||||
purgeLayersByDefault: true,
|
||||
},
|
||||
purge: {
|
||||
content: ['./src/**/*.svelte', './src/**/*.ts'],
|
||||
},
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// gray-400 hue-rotated 180deg
|
||||
select: '#A8A297',
|
||||
},
|
||||
gridTemplateColumns: {
|
||||
15: 'repeat(15, minmax(0, 1fr))',
|
||||
20: 'repeat(20, minmax(0, 1fr))',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
plugin(function ({ addUtilities, theme }) {
|
||||
const themeColors = theme('colors');
|
||||
const individualBorderColors = Object.keys(themeColors).map((colorName) => ({
|
||||
[`.border-b-${colorName}`]: {
|
||||
borderBottomColor: themeColors[colorName],
|
||||
},
|
||||
[`.border-t-${colorName}`]: {
|
||||
borderTopColor: themeColors[colorName],
|
||||
},
|
||||
[`.border-l-${colorName}`]: {
|
||||
borderLeftColor: themeColors[colorName],
|
||||
},
|
||||
[`.border-r-${colorName}`]: {
|
||||
borderRightColor: themeColors[colorName],
|
||||
},
|
||||
}));
|
||||
|
||||
addUtilities(individualBorderColors);
|
||||
}),
|
||||
],
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
{
|
||||
"include": ["src", "types"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "preserve",
|
||||
"baseUrl": "./",
|
||||
/* paths - If you configure Snowpack import aliases, add them here. */
|
||||
"paths": {
|
||||
"@core": ["../core/src"],
|
||||
"@core/*": ["../core/src/*"]
|
||||
},
|
||||
/* noEmit - Snowpack builds (emits) files, not tsc. */
|
||||
"noEmit": true,
|
||||
/* Additional Options */
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
// "noImplicitThis": false,
|
||||
// "alwaysStrict": false,
|
||||
// "strictBindCallApply": false,
|
||||
"strictNullChecks": false,
|
||||
// "strictFunctionTypes": false,
|
||||
// "strictPropertyInitialization": false,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"useDefineForClassFields": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"importsNotUsedAsValues": "remove"
|
||||
}
|
||||
}
|
68
ui/types/static.d.ts
vendored
68
ui/types/static.d.ts
vendored
@ -1,68 +0,0 @@
|
||||
/* Use this file to declare any custom file extensions for importing */
|
||||
/* Use this folder to also add/extend a package d.ts file, if needed. */
|
||||
|
||||
declare module 'svelte-file-dropzone';
|
||||
|
||||
declare namespace svelte.JSX {
|
||||
interface HTMLProps<T> {
|
||||
// inView.ts action
|
||||
onintersect?: (e: CustomEvent) => void;
|
||||
}
|
||||
}
|
||||
|
||||
/* CSS MODULES */
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.scss' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.sass' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.less' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.styl' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
/* CSS */
|
||||
declare module '*.css';
|
||||
declare module '*.scss';
|
||||
declare module '*.sass';
|
||||
declare module '*.less';
|
||||
declare module '*.styl';
|
||||
|
||||
/* IMAGES */
|
||||
declare module '*.svg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.bmp' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.gif' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.jpg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.jpeg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.png' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
|
||||
/* CUSTOM: ADD YOUR OWN HERE */
|
@ -1,6 +0,0 @@
|
||||
// NODE_ENV=test - Needed by "@snowpack/web-test-runner-plugin"
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
module.exports = {
|
||||
plugins: [require('@snowpack/web-test-runner-plugin')()],
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user