mirror of
https://github.com/jzillmann/pdf-to-markdown.git
synced 2024-11-21 23:33:31 +01:00
Switch from Vue to React
This commit is contained in:
parent
409e9a070f
commit
b9da57ed5b
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
build/
|
||||
npm-debug.log
|
||||
|
25
package.json
25
package.json
@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "pdf-to-markdown",
|
||||
"version": "0.0.1",
|
||||
"description": "A PDF to Markdown converter",
|
||||
"description": "A PDF to Markdown Converter",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"watch": "webpack -d --watch",
|
||||
"build": "webpack"
|
||||
"build": "webpack",
|
||||
"lint": "eslint . --ext .js --ext .jsx --cache"
|
||||
},
|
||||
"keywords": [
|
||||
"PDF",
|
||||
@ -13,15 +14,20 @@
|
||||
"Converter"
|
||||
],
|
||||
"author": "Johannes Zillmann",
|
||||
"license": "ISC",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jzillmann/pdf-to-markdown"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.7",
|
||||
"enumify": "^1.0.4",
|
||||
"pdfjs-dist": "^1.6.317",
|
||||
"vue": "^2.0.5",
|
||||
"vue-material": "^0.3.3"
|
||||
"react": "^15.3.2",
|
||||
"react-bootstrap": "^0.30.3",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-dropzone": "^3.6.0",
|
||||
"react-icons": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.18.2",
|
||||
@ -29,13 +35,18 @@
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-0": "^6.16.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.25.0",
|
||||
"esformatter-jsx": "^7.0.1",
|
||||
"eslint": "^3.7.0",
|
||||
"eslint-plugin-react": "^6.3.0",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"html-webpack-plugin": "^2.24.1",
|
||||
"sass-loader": "^4.0.2",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"vue-loader": "^9.8.1",
|
||||
"webpack": "^1.13.3"
|
||||
}
|
||||
}
|
||||
|
47
src/App.vue
47
src/App.vue
@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<hello v-if="state.uploaded"/>
|
||||
<!--img src="./assets/logo.png"-->
|
||||
<dropzone v-else id="myVueDropzone" url="https://httpbin.org/post" v-on:vdropzone-success="showSuccess" v-on:vdropzone-fileAdded="fileAdded"></dropzone>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from './store.js'
|
||||
import Hello from './components/Hello'
|
||||
import Dropzone from './components/Dropzone'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
Hello,
|
||||
Dropzone,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
state: store.state
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
'fileAdded': function (file) {
|
||||
console.log('A file was added: ')
|
||||
console.log(file)
|
||||
},
|
||||
'showSuccess': function (file) {
|
||||
console.log('A file was successfully uploaded: ')
|
||||
console.log(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@ -1,174 +0,0 @@
|
||||
<template>
|
||||
<div id="wrapper">
|
||||
<div class="dropzone-area" @dragenter="hovering = true" @dragleave="hovering = false" :class="{hovered: hovering}">
|
||||
<div class="dropzone-text">
|
||||
<span class="dropzone-title">Drop image here or click to select</span>
|
||||
</div>
|
||||
<input type="file" @change="onFileChange">
|
||||
</div>
|
||||
<div class="dropzone-preview">
|
||||
<img :src="image" />
|
||||
<button @click="removeImage" v-if="image">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '../store.js'
|
||||
import pdfjs from 'pdfjs-dist';
|
||||
import Page from '../models/Page.js';
|
||||
import TextItem from '../models/TextItem.js';
|
||||
|
||||
export default {
|
||||
props : {
|
||||
multiple : {
|
||||
default : false
|
||||
},
|
||||
path : {
|
||||
default : '/document/upload-unlinked/'
|
||||
},
|
||||
file : {
|
||||
default : 'user_file'
|
||||
},
|
||||
files : {
|
||||
default : function () { return [] }
|
||||
},
|
||||
target : {
|
||||
default : 'dropzone'
|
||||
},
|
||||
clickable : {
|
||||
default : false
|
||||
},
|
||||
previewTemplate : {
|
||||
default : '<div style="display:none"></div>'
|
||||
},
|
||||
createImageThumbnails : {
|
||||
default : false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovering : false,
|
||||
image: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
multipleUploads() {
|
||||
return this.multiple ? true : false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFileChange(e) {
|
||||
console.debug('upload');
|
||||
var files = e.target.files || e.dataTransfer.files;
|
||||
console.debug(files);
|
||||
if (!files.length) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
console.debug("Loaded");
|
||||
const buffer = evt.target.result;
|
||||
PDFJS.getDocument(buffer).then(function (pdfDocument) {
|
||||
console.log('Number of pages: ' + pdfDocument.numPages);
|
||||
// console.debug(pdfDocument);
|
||||
const numPages = pdfDocument.numPages;
|
||||
// const numPages = 3;
|
||||
store.preparePageUpload(numPages);
|
||||
for (var i = 0; i <= numPages; i++) {
|
||||
pdfDocument.getPage(i).then(function(page){
|
||||
page.getTextContent().then(function(textContent) {
|
||||
//console.debug(textContent);
|
||||
const textItems = textContent.items.map(function(item) {
|
||||
const transform = item.transform;
|
||||
return new TextItem({
|
||||
x: transform[4],
|
||||
y: transform[5],
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
text: item.str
|
||||
});
|
||||
});
|
||||
store.uploadPage(page.pageIndex, textItems);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(files[0]);
|
||||
},
|
||||
removeImage: function (e) {
|
||||
this.image = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang='sass' scoped>
|
||||
.dropzone-area {
|
||||
width: 80%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 2px dashed #CBCBCB;
|
||||
&.hovered {
|
||||
border: 2px dashed #2E94C4;
|
||||
.dropzone-title {
|
||||
color: #1975A0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.dropzone-area input {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dropzone-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
text-align: center;
|
||||
transform: translate(0, -50%);
|
||||
width: 100%;
|
||||
span {
|
||||
display: block;
|
||||
font-family: Arial, Helvetica;
|
||||
line-height: 1.9;
|
||||
}
|
||||
}
|
||||
|
||||
.dropzone-title {
|
||||
font-size: 13px;
|
||||
color: #787878;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.dropzone-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropzone-preview {
|
||||
width: 80%;
|
||||
position: relative;
|
||||
&:hover .dropzone-button {
|
||||
display: block;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<div>
|
||||
Uploaded: {{state.uploaded}}
|
||||
</div>
|
||||
<div v-for="line in state.pages">
|
||||
<textarea :value="line" rows="50" cols="150"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '../store.js'
|
||||
export default {
|
||||
name: 'hello',
|
||||
data () {
|
||||
return {
|
||||
msg: 'Welcome to Your!! Vue.js App',
|
||||
state: store.state
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
textarea { font-size: 18px; }
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
@ -1,11 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PDF to Markdown</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>PDF to Markdown</title>
|
||||
<meta name="description" content="Converts PDF files to Markdown." />
|
||||
<meta name="keywords" content="PDF, Markdown, converter">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"</div>
|
||||
</body>
|
||||
</html>
|
46
src/javascript/components/App.jsx
Normal file
46
src/javascript/components/App.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
|
||||
import Grid from 'react-bootstrap/lib/Grid'
|
||||
|
||||
import TopBar from './TopBar.jsx';
|
||||
import { View } from '../models/AppState.jsx';
|
||||
import PdfUploadView from './PdfUploadView.jsx';
|
||||
import LoadingView from './LoadingView.jsx';
|
||||
import PdfView from './PdfView.jsx';
|
||||
|
||||
export default class App extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
appState: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
console.debug(this.props.appState);
|
||||
|
||||
var mainView;
|
||||
switch (this.props.appState.mainView) {
|
||||
case View.UPLOAD:
|
||||
mainView = <PdfUploadView uploadPdfFunction={ this.props.appState.uploadPdf } />
|
||||
break;
|
||||
case View.LOADING:
|
||||
mainView = <LoadingView/>
|
||||
break;
|
||||
case View.PDF_VIEW:
|
||||
mainView = <PdfView pdfPages={ this.props.appState.pdfPages } />
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TopBar/>
|
||||
<Grid>
|
||||
<div>
|
||||
{ mainView }
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
28
src/javascript/components/AppLogo.jsx
Normal file
28
src/javascript/components/AppLogo.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { Component } from 'react';
|
||||
import FaFilePdfO from 'react-icons/lib/fa/file-pdf-o'
|
||||
|
||||
export default class AppLogo extends Component {
|
||||
|
||||
static propTypes = {
|
||||
onClick: React.PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
this.props.onClick(e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a href="" onClick={ this.handleClick }>
|
||||
<FaFilePdfO/> PDF To Markdown Converter</a>
|
||||
);
|
||||
}
|
||||
}
|
22
src/javascript/components/LoadingView.jsx
Normal file
22
src/javascript/components/LoadingView.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import Spinner from './lib/Spinner.jsx';
|
||||
|
||||
export default class LoadingView extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={ { textAlign: 'center' } }>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<Spinner/>
|
||||
<br/>
|
||||
<br/>
|
||||
<div>
|
||||
Uploading and parsing PDF...
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
66
src/javascript/components/PdfPageView.jsx
Normal file
66
src/javascript/components/PdfPageView.jsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import Table from 'react-bootstrap/lib/Table'
|
||||
|
||||
export default class PdfPageView extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
pdfPage: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const header = "Page " + this.props.pdfPage.index;
|
||||
return (
|
||||
<div>
|
||||
<h2>{ header }</h2>
|
||||
<Table responsive>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
#
|
||||
</th>
|
||||
<th>
|
||||
Text
|
||||
</th>
|
||||
<th>
|
||||
X
|
||||
</th>
|
||||
<th>
|
||||
Y
|
||||
</th>
|
||||
<th>
|
||||
Width
|
||||
</th>
|
||||
<th>
|
||||
Height
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ this.props.pdfPage.textItems.map((textItem, i) => <tr key={ i }>
|
||||
<td>
|
||||
{ i }
|
||||
</td>
|
||||
<td>
|
||||
{ textItem.text }
|
||||
</td>
|
||||
<td>
|
||||
{ textItem.x }
|
||||
</td>
|
||||
<td>
|
||||
{ textItem.y }
|
||||
</td>
|
||||
<td>
|
||||
{ textItem.width }
|
||||
</td>
|
||||
<td>
|
||||
{ textItem.height }
|
||||
</td>
|
||||
</tr>
|
||||
) }
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
47
src/javascript/components/PdfUploadView.jsx
Normal file
47
src/javascript/components/PdfUploadView.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import Dropzone from 'react-dropzone'
|
||||
import FaCloudUpload from 'react-icons/lib/fa/cloud-upload'
|
||||
|
||||
export default class PdfUploadView extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
uploadPdfFunction: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
uploadPdfFunction: props.uploadPdfFunction,
|
||||
};
|
||||
}
|
||||
|
||||
onDrop(files) {
|
||||
console.debug(files.length);
|
||||
if (files.length > 1) {
|
||||
alert(`Maximum one file allowed to upload, but not ${files.length}!`)
|
||||
return
|
||||
}
|
||||
const reader = new FileReader();
|
||||
const uploadFunction = this.state.uploadPdfFunction;
|
||||
reader.onload = (evt) => {
|
||||
const fileBuffer = evt.target.result;
|
||||
uploadFunction(fileBuffer);
|
||||
};
|
||||
reader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Dropzone onDrop={ this.onDrop.bind(this) } multiple={ false } style={ { width: 400, height: 500, borderWidth: 2, borderColor: '#666', borderStyle: 'dashed', borderRadius: 5, display: 'table-cell', textAlign: 'center', verticalAlign: 'middle' } }>
|
||||
<div className="container">
|
||||
<h2>Drop your PDF file here!</h2>
|
||||
</div>
|
||||
<h1><FaCloudUpload width={ 100 } height={ 100 } /></h1>
|
||||
</Dropzone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
25
src/javascript/components/PdfView.jsx
Normal file
25
src/javascript/components/PdfView.jsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import PdfPageView from './PdfPageView.jsx';
|
||||
|
||||
// A view which displays the TextItems of multiple PdfPages
|
||||
export default class PdfView extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
pdfPages: React.PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
console.debug(this.props.pdfPages);
|
||||
const header = "Parsed " + this.props.pdfPages.length + " pages!"
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{ header }
|
||||
</div>
|
||||
<hr/>
|
||||
{ this.props.pdfPages.map((page) => <PdfPageView key={ page.index } pdfPage={ page } />) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
49
src/javascript/components/TopBar.jsx
Normal file
49
src/javascript/components/TopBar.jsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import Navbar from 'react-bootstrap/lib/Navbar'
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
import Popover from 'react-bootstrap/lib/Popover'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
|
||||
import AppLogo from './AppLogo.jsx';
|
||||
|
||||
export default class TopBar extends React.Component {
|
||||
|
||||
render() {
|
||||
|
||||
const aboutPopover = (
|
||||
<Popover id="popover-trigger-click-root-close" title={ `About PDF to Markdown Converter - ${ process.env.version }` }>
|
||||
<p>
|
||||
<i>PDF to Markdown Converter</i> will convert your uploaded PDF to Markdown format.
|
||||
</p>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
return (
|
||||
<Navbar inverse>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>
|
||||
<Dropdown id="logo-dropdown">
|
||||
<AppLogo bsRole="toggle" />
|
||||
<Dropdown.Menu>
|
||||
<MenuItem divider />
|
||||
<MenuItem href="http://github.com/jzillmann/pdf-to-markdown" target="_blank"> Github
|
||||
</MenuItem>
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
rootClose
|
||||
placement="bottom"
|
||||
overlay={ aboutPopover }>
|
||||
<MenuItem eventKey="3"> About
|
||||
</MenuItem>
|
||||
</OverlayTrigger>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
209
src/javascript/components/lib/Spinner.jsx
Normal file
209
src/javascript/components/lib/Spinner.jsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React from 'react';
|
||||
|
||||
// Spinner like loading indicator
|
||||
export default class Spinner extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<svg
|
||||
width='120px'
|
||||
height='120px'
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100"
|
||||
height="100"
|
||||
fill="none"></rect>
|
||||
<g transform="translate(50 50)">
|
||||
<g transform="rotate(0) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(45) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.12s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.12s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(90) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.25s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.25s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(135) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.37s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.37s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(180) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.5s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.5s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(225) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.62s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.62s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(270) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.75s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.75s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
<g transform="rotate(315) translate(34 0)">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="8"
|
||||
fill="#000">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
from="1"
|
||||
to="0.1"
|
||||
begin="0.87s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
from="1.5"
|
||||
to="1"
|
||||
begin="0.87s"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"></animateTransform>
|
||||
</circle>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
32
src/javascript/functions/pdfToTextItems.jsx
Normal file
32
src/javascript/functions/pdfToTextItems.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import pdfjs from 'pdfjs-dist';
|
||||
|
||||
import AppState from '../models/AppState.jsx';
|
||||
import TextItem from '../models/TextItem.jsx';
|
||||
|
||||
export function pdfToTextItemsAsync(fileBuffer:ArrayBuffer, appState:AppState) {
|
||||
PDFJS.getDocument(fileBuffer).then(function(pdfDocument) {
|
||||
console.log('Number of pages: ' + pdfDocument.numPages);
|
||||
// console.debug(pdfDocument);
|
||||
const numPages = pdfDocument.numPages;
|
||||
// const numPages = 3;
|
||||
appState.setPageCount(numPages);
|
||||
for (var i = 0; i <= numPages; i++) {
|
||||
pdfDocument.getPage(i).then(function(page) {
|
||||
page.getTextContent().then(function(textContent) {
|
||||
// console.debug(textContent);
|
||||
const textItems = textContent.items.map(function(item) {
|
||||
const transform = item.transform;
|
||||
return new TextItem({
|
||||
x: transform[4],
|
||||
y: transform[5],
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
text: item.str
|
||||
});
|
||||
});
|
||||
appState.setPdfPage(page.pageIndex, textItems);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
17
src/javascript/index.jsx
Normal file
17
src/javascript/index.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
|
||||
import App from './components/App.jsx';
|
||||
import AppState from './models/AppState.jsx';
|
||||
|
||||
function render(appState) {
|
||||
ReactDOM.render(<App appState={ appState } />, document.getElementById('main'));
|
||||
}
|
||||
|
||||
const appState = new AppState({
|
||||
renderFunction: render,
|
||||
});
|
||||
|
||||
appState.render()
|
57
src/javascript/models/AppState.jsx
Normal file
57
src/javascript/models/AppState.jsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { Enum } from 'enumify';
|
||||
|
||||
import { pdfToTextItemsAsync } from '../functions/pdfToTextItems.jsx'
|
||||
import PdfPage from './PdfPage.jsx';
|
||||
|
||||
// Holds the state of the Application
|
||||
export default class AppState {
|
||||
|
||||
constructor(options) {
|
||||
this.renderFunction = options.renderFunction;
|
||||
this.mainView = View.UPLOAD;
|
||||
this.pagesToUpload = 0;
|
||||
this.uploadedPages = 0;
|
||||
this.pdfPages = [];
|
||||
|
||||
//bind functions
|
||||
this.render = this.render.bind(this);
|
||||
this.uploadPdf = this.uploadPdf.bind(this);
|
||||
this.setPageCount = this.setPageCount.bind(this);
|
||||
this.setPdfPage = this.setPdfPage.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderFunction(this)
|
||||
}
|
||||
|
||||
uploadPdf(fileBuffer:ArrayBuffer) {
|
||||
pdfToTextItemsAsync(fileBuffer, this);
|
||||
this.mainView = View.LOADING;
|
||||
this.render()
|
||||
}
|
||||
|
||||
setPageCount(numPages) {
|
||||
this.pagesToUpload = numPages;
|
||||
for (var i = 0; i < numPages; i++) {
|
||||
this.pdfPages.push(new PdfPage({
|
||||
index: i
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
setPdfPage(pageIndex, textItems) {
|
||||
console.debug("Upload " + pageIndex);
|
||||
this.pdfPages[pageIndex].textItems = textItems;
|
||||
this.uploadedPages++;
|
||||
if (this.uploadedPages == this.pagesToUpload) {
|
||||
console.debug("Fin");
|
||||
this.mainView = View.PDF_VIEW;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class View extends Enum {
|
||||
}
|
||||
View.initEnum(['UPLOAD', 'LOADING', 'PDF_VIEW'])
|
@ -1,4 +1,5 @@
|
||||
export default class Page {
|
||||
// A page which holds TextItems displayable via PdfPageView
|
||||
export default class PdfPage {
|
||||
|
||||
constructor(options) {
|
||||
this.index = options.index;
|
11
src/main.js
11
src/main.js
@ -1,11 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
template: '<App/>',
|
||||
components: {
|
||||
App
|
||||
}
|
||||
})
|
@ -1,15 +1,18 @@
|
||||
var path = require('path')
|
||||
var sourceDir = path.resolve(__dirname, 'src');
|
||||
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
var SOURCE_DIR = path.resolve(__dirname, 'src');
|
||||
var BUILD_DIR = path.resolve(__dirname, 'build');
|
||||
var NODEMODULES_DIR = path.resolve(__dirname, 'node_modules');
|
||||
var JAVASCRIPT_DIR = SOURCE_DIR + '/javascript';
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.js',
|
||||
entry: JAVASCRIPT_DIR + '/index.jsx',
|
||||
output: {
|
||||
// To the `dist` folder
|
||||
path: './dist',
|
||||
// With the filename `build.js` so it's dist/build.js
|
||||
filename: 'build.js'
|
||||
path: BUILD_DIR,
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.vue'],
|
||||
@ -29,34 +32,55 @@ module.exports = {
|
||||
loaders: [
|
||||
{
|
||||
// Ask webpack to check: If this file ends with .js, then apply some transforms
|
||||
test: /\.js$/,
|
||||
test: /\.jsx?$/,
|
||||
// Transform it with babel
|
||||
loader: 'babel',
|
||||
// don't transform node_modules folder (which don't need to be compiled)
|
||||
exclude: /node_modules/
|
||||
include: [JAVASCRIPT_DIR],
|
||||
query: {
|
||||
plugins: ['transform-runtime'],
|
||||
presets: ['es2015', 'stage-0', 'react'],
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue'
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
loaders: ["style", "css", "sass"]
|
||||
test: /\.css$/,
|
||||
loader: "style-loader!css-loader"
|
||||
},
|
||||
{
|
||||
test: /\.png$/,
|
||||
loader: "url-loader?limit=100000"
|
||||
},
|
||||
{
|
||||
test: /\.jpg$/,
|
||||
loader: "file-loader"
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'url?limit=10000&mimetype=application/font-woff'
|
||||
},
|
||||
{
|
||||
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'url?limit=10000&mimetype=application/octet-stream'
|
||||
},
|
||||
{
|
||||
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'file'
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'url?limit=10000&mimetype=image/svg+xml'
|
||||
}
|
||||
]
|
||||
},
|
||||
vue: {
|
||||
loaders: {
|
||||
js: 'babel'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: sourceDir + '/index.html'
|
||||
})
|
||||
template: SOURCE_DIR + '/index.html'
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: NODEMODULES_DIR + '/pdfjs-dist/build/pdf.worker.js',
|
||||
to: 'bundle.worker.js'
|
||||
},
|
||||
])
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user