├── CNAME
├── css
├── includes
│ ├── _variables.scss
│ ├── components
│ │ ├── _inputfile.scss
│ │ ├── _button.scss
│ │ └── _filelist.scss
│ └── _layout.scss
├── styles.scss
└── styles.processed.css
├── js
├── components
│ ├── button.js
│ ├── filelist.js
│ └── inputfile.js
├── vendor
│ ├── react-dom.min.js
│ ├── compatibility.js
│ └── react-with-addons.min.js
└── app.js
├── README.md
├── index.html
└── LICENSE
/CNAME:
--------------------------------------------------------------------------------
1 | react-pdfjs.jjperezaguinaga.com
--------------------------------------------------------------------------------
/css/includes/_variables.scss:
--------------------------------------------------------------------------------
1 | $color-bg: #ffffff;
2 | $color-black: #222;
3 | $color-grey: #444;
4 | $color-yellow: #ffe23f;
--------------------------------------------------------------------------------
/css/styles.scss:
--------------------------------------------------------------------------------
1 | @import "includes/variables";
2 | @import "includes/layout";
3 |
4 | @import "includes/components/button";
5 | @import "includes/components/inputfile";
6 | @import "includes/components/filelist";
--------------------------------------------------------------------------------
/css/includes/components/_inputfile.scss:
--------------------------------------------------------------------------------
1 | .InputFile {
2 | margin: 1em auto;
3 |
4 | &__input {
5 | width: 0.1px;
6 | height: 0.1px;
7 | opacity: 0;
8 | overflow: hidden;
9 | position: absolute;
10 | z-index: -1;
11 | }
12 | &__label {
13 | cursor: pointer;
14 | }
15 | }
--------------------------------------------------------------------------------
/js/components/button.js:
--------------------------------------------------------------------------------
1 | export const Button = ({children, type, onClick, isActive}) => {
2 | const buttonClasses = ['Button']
3 |
4 | type !== null ? buttonClasses.push(`Button--is-${type}`) : buttonClasses.push('Button--is-default')
5 | isActive ? buttonClasses.push('Button--is-active') : buttonClasses.push('Button--is-inactive')
6 |
7 | return (
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PDF Viewer
2 |
3 | 📄 React + PDF.js Experiment. Submission for /r/codingprompts (Week 2). Preview
4 | and load PDF documents to read them in your browser.
5 |
6 | ## Technologies
7 |
8 | * ReactJS for templating/routing 📖
9 | * PDF.js by Mozilla for Rendering PDFs 📕
10 |
11 | Works in Chrome + Firefox + Safari, untested in IE.
12 |
13 | ## Play around
14 |
15 | Feel free to play around with the code at Codepen.io!
16 | https://codepen.io/jjperezaguinaga/project/editor/APYyRa/
17 |
--------------------------------------------------------------------------------
/css/includes/components/_button.scss:
--------------------------------------------------------------------------------
1 | .Button {
2 | padding: 5px 15px;
3 | font-size: 13px;
4 | border-radius: 4px;
5 | border: 1px inset rgba(0,0,0,0.3);
6 | background-color: #4ECE3D;
7 | color: white;
8 | text-align: center;
9 | cursor: pointer;
10 | transition: background-color 180ms ease-in;
11 | &:hover {
12 | background-color: #40B730;
13 | }
14 | &--is-primary {
15 | border: none;
16 | font-size: 16px;
17 | padding: 10px 25px;
18 | }
19 | &--is-active {
20 | background-color: #39A12B;
21 | }
22 | }
--------------------------------------------------------------------------------
/css/includes/components/_filelist.scss:
--------------------------------------------------------------------------------
1 | .FileList {
2 | display: flex;
3 | flex-wrap: wrap;
4 | font-size: 0.8em;
5 |
6 | &__item {
7 | display: flex;
8 | align-items: center;
9 | padding: 0 1.5em;
10 | transition: background 180ms ease-in;
11 | cursor: pointer;
12 | width: 100%;
13 |
14 | &:hover {
15 | background: #dedede;
16 | }
17 |
18 | figure {
19 | margin: 0;
20 | margin-right: 1em;
21 | }
22 |
23 | small {
24 | display: block;
25 | color: #aaa;
26 | }
27 |
28 | h3 {
29 | text-align: left;
30 | text-overflow: ellipsis;
31 | overflow: hidden;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/js/vendor/react-dom.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ReactDOM v15.3.0
3 | *
4 | * Copyright 2013-present, Facebook, Inc.
5 | * All rights reserved.
6 | *
7 | * This source code is licensed under the BSD-style license found in the
8 | * LICENSE file in the root directory of this source tree. An additional grant
9 | * of patent rights can be found in the PATENTS file in the same directory.
10 | *
11 | */
12 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e(require("react"));else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;f="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,f.ReactDOM=e(f.React)}}(function(e){return e.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED});
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | My React Project on CodePen
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/js/components/filelist.js:
--------------------------------------------------------------------------------
1 | export default class FileList extends React.Component {
2 | constructor(props) {
3 | super(props)
4 | }
5 | _printSize(size) {
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB']
7 | let i = 0; while( size > 900 ) { size /= 1024; i++; }
8 | return `${(Math.round(size*100)/100)} ${sizes[i]}`
9 | }
10 | render() {
11 | const { files } = this.props;
12 | return (
13 |
14 | {
15 | files.map( file =>
16 | (
this.props.loadFile(file)}>
17 | 📄
18 |
{file.name}{this._printSize(file.size)}
19 | ))
20 | }
21 |
22 | )
23 | }
24 | }
--------------------------------------------------------------------------------
/js/components/inputfile.js:
--------------------------------------------------------------------------------
1 | import { Button } from './button'
2 |
3 | export default class InputFile extends React.Component {
4 | triggerInput = e => {
5 | ReactDOM.findDOMNode(this._inputFile).click();
6 | }
7 | render() {
8 | return (
9 |
10 |
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jose Aguinaga
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/css/includes/_layout.scss:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0px;
3 | padding: 0px;
4 | width: 100%;
5 | height: 99%;
6 | position: relative;
7 | }
8 |
9 | body {
10 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
11 | background: $color-bg;
12 | background: radial-gradient(ellipse at center, rgba(255,255,255,1) 20%,rgba(210,210,210,1) 100%);
13 | padding: 0px;
14 | }
15 |
16 | h1 {
17 | margin: 0px;
18 | }
19 |
20 | p {
21 | margin: 0px;
22 | }
23 |
24 | canvas {
25 | position: relative;
26 | margin: 0 auto 3em auto;
27 | padding: 0;
28 | overflow: visible;
29 | background-clip: content-box;
30 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.54);
31 | background-color: white;
32 | width: 100%;
33 | }
34 |
35 | .center {
36 | width: 760px;
37 | height: 480px;
38 | position: absolute;
39 | left: 50%;
40 | top: 50%;
41 | margin-left: -360px;
42 | margin-top: -200px;
43 | text-align: center;
44 | display: flex;
45 | flex: 1 auto;
46 | background: black;
47 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25);
48 | }
49 |
50 | .Sidebar {
51 | display: inline-block;
52 | width: 35%;
53 | background: white;
54 | overflow-y: scroll;
55 | overflow-x: hidden;
56 | }
57 |
58 | .Content {
59 | display: inline-block;
60 | width: 65%;
61 | background: #3E3E3E;
62 | padding: 3em;
63 | overflow-y: scroll;
64 | overflow-x: hidden;
65 | }
--------------------------------------------------------------------------------
/css/styles.processed.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0px;
4 | padding: 0px;
5 | width: 100%;
6 | height: 99%;
7 | position: relative;
8 | }
9 |
10 | body {
11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
12 | background: #ffffff;
13 | background: radial-gradient(ellipse at center, white 20%, #d2d2d2 100%);
14 | padding: 0px;
15 | }
16 |
17 | h1 {
18 | margin: 0px;
19 | }
20 |
21 | p {
22 | margin: 0px;
23 | }
24 |
25 | canvas {
26 | position: relative;
27 | margin: 0 auto 3em auto;
28 | padding: 0;
29 | overflow: visible;
30 | background-clip: content-box;
31 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.54);
32 | background-color: white;
33 | width: 100%;
34 | }
35 |
36 | .center {
37 | width: 760px;
38 | height: 480px;
39 | position: absolute;
40 | left: 50%;
41 | top: 50%;
42 | margin-left: -360px;
43 | margin-top: -200px;
44 | text-align: center;
45 | display: -ms-flexbox;
46 | display: flex;
47 | -ms-flex: 1 auto;
48 | flex: 1 auto;
49 | background: black;
50 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25);
51 | }
52 |
53 | .Sidebar {
54 | display: inline-block;
55 | width: 35%;
56 | background: white;
57 | overflow-y: scroll;
58 | overflow-x: hidden;
59 | }
60 |
61 | .Content {
62 | display: inline-block;
63 | width: 65%;
64 | background: #3E3E3E;
65 | padding: 3em;
66 | overflow-y: scroll;
67 | overflow-x: hidden;
68 | }
69 |
70 | .Button {
71 | padding: 5px 15px;
72 | font-size: 13px;
73 | border-radius: 4px;
74 | border: 1px inset rgba(0, 0, 0, 0.3);
75 | background-color: #4ECE3D;
76 | color: white;
77 | text-align: center;
78 | cursor: pointer;
79 | transition: background-color 180ms ease-in;
80 | }
81 |
82 | .Button:hover {
83 | background-color: #40B730;
84 | }
85 |
86 | .Button--is-primary {
87 | border: none;
88 | font-size: 16px;
89 | padding: 10px 25px;
90 | }
91 |
92 | .Button--is-active {
93 | background-color: #39A12B;
94 | }
95 |
96 | .InputFile {
97 | margin: 1em auto;
98 | }
99 |
100 | .InputFile__input {
101 | width: 0.1px;
102 | height: 0.1px;
103 | opacity: 0;
104 | overflow: hidden;
105 | position: absolute;
106 | z-index: -1;
107 | }
108 |
109 | .InputFile__label {
110 | cursor: pointer;
111 | }
112 |
113 | .FileList {
114 | display: -ms-flexbox;
115 | display: flex;
116 | -ms-flex-wrap: wrap;
117 | flex-wrap: wrap;
118 | font-size: 0.8em;
119 | }
120 |
121 | .FileList__item {
122 | display: -ms-flexbox;
123 | display: flex;
124 | -ms-flex-align: center;
125 | align-items: center;
126 | padding: 0 1.5em;
127 | transition: background 180ms ease-in;
128 | cursor: pointer;
129 | width: 100%;
130 | }
131 |
132 | .FileList__item:hover {
133 | background: #dedede;
134 | }
135 |
136 | .FileList__item figure {
137 | margin: 0;
138 | margin-right: 1em;
139 | }
140 |
141 | .FileList__item small {
142 | display: block;
143 | color: #aaa;
144 | }
145 |
146 | .FileList__item h3 {
147 | text-align: left;
148 | text-overflow: ellipsis;
149 | overflow: hidden;
150 | }
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | import { default as InputFile } from './components/inputfile'
2 | import { default as FileList } from './components/filelist'
3 |
4 | class App extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | fileReader: new FileReader(),
9 | files: [],
10 | pages: [],
11 | currentFile: null
12 | }
13 | this.state.fileReader.onload = this.loadFileReader.bind(this)
14 | }
15 |
16 | loadFileReader = e => {
17 | PDFJS.getDocument(new Uint8Array(e.target.result)).then(pdf => {
18 | // Hardcoded to match the current viewport
19 | let scale = 0.72;
20 |
21 | let viewport, canvas, context, renderContext;
22 |
23 | // This is a good example of why handling DOM directly w/React is a bad idea
24 | // Ideally we just use data and grab context from canvas using something like
25 | //