Compare commits
1058 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7c70c83b4 | ||
|
|
74079512d0 | ||
|
|
f3697a18e3 | ||
|
|
734c871666 | ||
|
|
5dc73ab773 | ||
|
|
152daf60fb | ||
|
|
1a7e148a78 | ||
|
|
227f46456b | ||
|
|
c2bcda925a | ||
|
|
e806ee8015 | ||
|
|
51e35ee0ee | ||
|
|
f7f6d40eee | ||
|
|
d449ccf624 | ||
|
|
40ed0f68db | ||
|
|
4056d87335 | ||
|
|
335a879e81 | ||
|
|
a928e7a917 | ||
|
|
a884268d97 | ||
|
|
28862d70ca | ||
|
|
187b7b3e78 | ||
|
|
fc3e2adc82 | ||
|
|
c19bc8d07a | ||
|
|
09a3d83dc5 | ||
|
|
9d540d6aa2 | ||
|
|
331360098f | ||
|
|
96d6f56e85 | ||
|
|
527688db90 | ||
|
|
ecb12e66b6 | ||
|
|
fae524f8da | ||
|
|
dbadfe50e6 | ||
|
|
edd4e0c39a | ||
|
|
771e66100f | ||
|
|
d0dbb7e708 | ||
|
|
3eca56f8c6 | ||
|
|
a484a804ad | ||
|
|
6f4bb8242b | ||
|
|
2420e59cd8 | ||
|
|
df10eacf92 | ||
|
|
b1bb8e0f02 | ||
|
|
efd4bdc493 | ||
|
|
e72f3d5525 | ||
|
|
f587797ddb | ||
|
|
c723a5c77d | ||
|
|
a91dd0e502 | ||
|
|
534a7776cf | ||
|
|
9cb499c93f | ||
|
|
52fae8e41b | ||
|
|
113f87aa3e | ||
|
|
6eed4a3238 | ||
|
|
2fc152e96f | ||
|
|
13bfa0b0d0 | ||
|
|
20f027bb5a | ||
|
|
644e0ceae9 | ||
|
|
244fb36195 | ||
|
|
aadc5b5cda | ||
|
|
fe12a2722c | ||
|
|
da32769463 | ||
|
|
e7b4c93481 | ||
|
|
829127c3f8 | ||
|
|
50ab159abe | ||
|
|
fbc921a077 | ||
|
|
9a721f8658 | ||
|
|
81861ac4c4 | ||
|
|
bcb8ff5843 | ||
|
|
c07e247a2a | ||
|
|
9c1588d150 | ||
|
|
5fd505d4f4 | ||
|
|
836922b856 | ||
|
|
8a5711cd86 | ||
|
|
768ab4def1 | ||
|
|
0bdda72caa | ||
|
|
0411a36b56 | ||
|
|
b2fdb7ec15 | ||
|
|
f6e67e04da | ||
|
|
f87fb32913 | ||
|
|
31ae0110ed | ||
|
|
51ca8ac7fc | ||
|
|
8d9c24b09b | ||
|
|
0df64d5e7d | ||
|
|
a0cf84d16c | ||
|
|
c9c03b206c | ||
|
|
762fa850f1 | ||
|
|
45d5f2c533 | ||
|
|
abc0f8cb8a | ||
|
|
3e10972efa | ||
|
|
efd8b48a3f | ||
|
|
993d44b9b8 | ||
|
|
ba2588ea24 | ||
|
|
09aa3a8bc9 | ||
|
|
26dcc4843e | ||
|
|
58c08821d2 | ||
|
|
c45e6c43f2 | ||
|
|
b9b7f0d5ea | ||
|
|
3fb8c6c6eb | ||
|
|
1d89a4c081 | ||
|
|
162a2baa44 | ||
|
|
498f287d57 | ||
|
|
90f0ee0bc5 | ||
|
|
6d81fa1a9e | ||
|
|
a02169048d | ||
|
|
a5060f0fd3 | ||
|
|
42904788bf | ||
|
|
d73a61ae3d | ||
|
|
7df48d43d6 | ||
|
|
a7545b017f | ||
|
|
facf1553ec | ||
|
|
7503d70377 | ||
|
|
698c8f6215 | ||
|
|
9bca4848f3 | ||
|
|
c060b2a4e0 | ||
|
|
8a63ebe6cf | ||
|
|
432342415e | ||
|
|
3496a5d658 | ||
|
|
26cb467156 | ||
|
|
a93c575b05 | ||
|
|
4c8a85726d | ||
|
|
b4251b56fe | ||
|
|
fa36d5d296 | ||
|
|
544a080db4 | ||
|
|
53bf6ef4bc | ||
|
|
57db105b7e | ||
|
|
921a8c431c | ||
|
|
abf9278b90 | ||
|
|
8f1f64f6c0 | ||
|
|
ae99ba0fab | ||
|
|
373c5e4c26 | ||
|
|
576b0e02f6 | ||
|
|
667583984f | ||
|
|
57415bea85 | ||
|
|
5f23491189 | ||
|
|
ed4c7d9400 | ||
|
|
997f0f349d | ||
|
|
063acc6bbf | ||
|
|
85d61fddf0 | ||
|
|
d7feec32dd | ||
|
|
7bf100daff | ||
|
|
02957f7757 | ||
|
|
01bc1b0303 | ||
|
|
35e2668f07 | ||
|
|
bbcbc8be50 | ||
|
|
9956367384 | ||
|
|
b3c73247d3 | ||
|
|
185cd1c0a7 | ||
|
|
89e8c3b0ba | ||
|
|
77274e6117 | ||
|
|
4a832942f2 | ||
|
|
eab9607f8a | ||
|
|
e625a415fd | ||
|
|
d2a7ef921b | ||
|
|
5ae8cb77ac | ||
|
|
57142381ca | ||
|
|
c7dd18695d | ||
|
|
8018947353 | ||
|
|
0d915fcc33 | ||
|
|
3fad22c4fe | ||
|
|
1b0a881ad6 | ||
|
|
fd6b0740fd | ||
|
|
2cb2116940 | ||
|
|
458e9ed75b | ||
|
|
7b702734f3 | ||
|
|
55f55afed2 | ||
|
|
97e4eab7bb | ||
|
|
0f35c77074 | ||
|
|
0a43660e55 | ||
|
|
f80297fbcc | ||
|
|
55bab60e89 | ||
|
|
48f0c24886 | ||
|
|
608b1cbe4d | ||
|
|
47e0092378 | ||
|
|
7186c6c3e0 | ||
|
|
de2c1fd3e2 | ||
|
|
c404754e26 | ||
|
|
0d2d1726f8 | ||
|
|
35c5a457b7 | ||
|
|
98a19e56a4 | ||
|
|
fcf53c3923 | ||
|
|
5a2d8e9f07 | ||
|
|
f73c570bd3 | ||
|
|
f87a921ad1 | ||
|
|
261cbe64f2 | ||
|
|
3006478e9c | ||
|
|
cac8f856fa | ||
|
|
a006937e6e | ||
|
|
877386cdd3 | ||
|
|
f7e5fd3aef | ||
|
|
e25c3a4110 | ||
|
|
ad62137a28 | ||
|
|
88550e13dd | ||
|
|
cfbfbf34d7 | ||
|
|
013a3f7e1b | ||
|
|
dd14b3a773 | ||
|
|
4f500f2257 | ||
|
|
29a8107ae0 | ||
|
|
99ca6b6a26 | ||
|
|
29b633d7b4 | ||
|
|
6d505715e0 | ||
|
|
13c4f664c2 | ||
|
|
4e8d8e3d53 | ||
|
|
02f98fb378 | ||
|
|
19f4f7febb | ||
|
|
989a5e02b9 | ||
|
|
f78a64d545 | ||
|
|
50a1bd8082 | ||
|
|
9eb3ff4bb3 | ||
|
|
1588ae4948 | ||
|
|
5579892aa7 | ||
|
|
8d032a2cf2 | ||
|
|
0320d76f05 | ||
|
|
368d742d41 | ||
|
|
4fe725f7b5 | ||
|
|
27842a43d2 | ||
|
|
31b746eb4b | ||
|
|
db1c558a2b | ||
|
|
19589c6468 | ||
|
|
9ab4d9f585 | ||
|
|
256a2b61fb | ||
|
|
7f68a580f0 | ||
|
|
2dfb90d0e5 | ||
|
|
f6b4479200 | ||
|
|
26cdb1d04f | ||
|
|
b5ba2b25cd | ||
|
|
6248aa6a32 | ||
|
|
f402a36042 | ||
|
|
2bc618d66b | ||
|
|
0bf8c520aa | ||
|
|
3fd8833457 | ||
|
|
8193d5044a | ||
|
|
be336f09de | ||
|
|
5203fc3605 | ||
|
|
c60de02e14 | ||
|
|
21e0385a31 | ||
|
|
fddbec2408 | ||
|
|
8e8c4596bf | ||
|
|
96bac91fa1 | ||
|
|
76e37354e1 | ||
|
|
542d74b9cc | ||
|
|
3be12c8988 | ||
|
|
89345c8d60 | ||
|
|
49f1f4e7c7 | ||
|
|
d0ce7db9ee | ||
|
|
53a0291cc2 | ||
|
|
9a3bc839dd | ||
|
|
e519840bd6 | ||
|
|
ed32a3ca33 | ||
|
|
369ac99a16 | ||
|
|
39ac823b05 | ||
|
|
548ae4dba3 | ||
|
|
f69d593649 | ||
|
|
423af5f077 | ||
|
|
76e5e1ad00 | ||
|
|
420caa4d8d | ||
|
|
fdeeb68a6d | ||
|
|
9fe803a0b1 | ||
|
|
dd9ba90a03 | ||
|
|
2bc739316a | ||
|
|
74da8c340d | ||
|
|
766cb4410b | ||
|
|
78fe6d6ea8 | ||
|
|
6b99decb56 | ||
|
|
323745e61f | ||
|
|
22c19670e9 | ||
|
|
a1b7aaddb8 | ||
|
|
cfba9681c4 | ||
|
|
a019b8b5ca | ||
|
|
bd9b267562 | ||
|
|
b0f8f56650 | ||
|
|
a71e813e82 | ||
|
|
75e7665d6e | ||
|
|
12293f5297 | ||
|
|
4899ee0bee | ||
|
|
563c17c84e | ||
|
|
d94eca4ee7 | ||
|
|
f4a01884bd | ||
|
|
105d7f12ac | ||
|
|
30c115a7de | ||
|
|
88a90f22a3 | ||
|
|
c72c712c1b | ||
|
|
7205801e76 | ||
|
|
64f4f54b9d | ||
|
|
0287d88895 | ||
|
|
cdc075b27c | ||
|
|
604d9827c5 | ||
|
|
bdeb6bf188 | ||
|
|
19e122be99 | ||
|
|
f3ddf18a23 | ||
|
|
51f863e1e4 | ||
|
|
0a9381d538 | ||
|
|
a9514b54eb | ||
|
|
d647cb196f | ||
|
|
b69973f614 | ||
|
|
fb9c42f4a1 | ||
|
|
b873e3cdf8 | ||
|
|
e9fc024332 | ||
|
|
d4956fad8c | ||
|
|
9d1dfe742e | ||
|
|
e32c092af8 | ||
|
|
18a2664b54 | ||
|
|
954e46c5ec | ||
|
|
e0f306d3f7 | ||
|
|
09db6618d6 | ||
|
|
c5ea254945 | ||
|
|
1fc1ecbaa6 | ||
|
|
bc4640c3f0 | ||
|
|
1e2eb9b07a | ||
|
|
ece00956d9 | ||
|
|
af5bbd8838 | ||
|
|
3a8f2495ea | ||
|
|
993f5e5097 | ||
|
|
1f99c26e78 | ||
|
|
05ebf3a6b4 | ||
|
|
1be3046d26 | ||
|
|
5b3858ba29 | ||
|
|
a1f388e524 | ||
|
|
cf14ff1540 | ||
|
|
a0ac2bc02a | ||
|
|
ed82c492ab | ||
|
|
fc2d71d120 | ||
|
|
e5a7a0631b | ||
|
|
315cba07f0 | ||
|
|
cd5bd92a41 | ||
|
|
10126ce979 | ||
|
|
f6c4f08254 | ||
|
|
fc4feb2096 | ||
|
|
82b641458f | ||
|
|
2c0fb33548 | ||
|
|
24e665bfd5 | ||
|
|
1404e33386 | ||
|
|
0b1fd61188 | ||
|
|
80b4c67e35 | ||
|
|
1f4aae9249 | ||
|
|
145e8006f0 | ||
|
|
872f562aad | ||
|
|
db70d67180 | ||
|
|
b1fd798a5c | ||
|
|
42907ade21 | ||
|
|
b3bc0b4e5a | ||
|
|
12e24f3ec1 | ||
|
|
fefa8347da | ||
|
|
cd7ca09a3f | ||
|
|
3960b2d6f9 | ||
|
|
264969e80c | ||
|
|
4e2911f648 | ||
|
|
42610b2645 | ||
|
|
7b07a706e1 | ||
|
|
8fef9dc8a8 | ||
|
|
83936bf4c8 | ||
|
|
65f89e283e | ||
|
|
1aa65bd3d1 | ||
|
|
0547ec3c49 | ||
|
|
7e1cbe572d | ||
|
|
7a98f30d05 | ||
|
|
c565bd4400 | ||
|
|
d039c8e62e | ||
|
|
da7f0561cb | ||
|
|
00210b75c7 | ||
|
|
3fe8973ae6 | ||
|
|
87c4b42683 | ||
|
|
0901eaac45 | ||
|
|
c7c81a7243 | ||
|
|
ac019ac196 | ||
|
|
b12c1305ea | ||
|
|
1876c2afae | ||
|
|
4575375ea9 | ||
|
|
aac7836dce | ||
|
|
df5de2d60d | ||
|
|
b026283ef2 | ||
|
|
548e5d108c | ||
|
|
4d31152a02 | ||
|
|
fd08513212 | ||
|
|
3175ac16d1 | ||
|
|
0cb5a6c7ad | ||
|
|
0bb2df135b | ||
|
|
adadf7428c | ||
|
|
146dd3c00b | ||
|
|
78bfa84afd | ||
|
|
07512c7e2c | ||
|
|
9fe96bec40 | ||
|
|
18172aa33a | ||
|
|
9ece6dacbd | ||
|
|
ef07963d79 | ||
|
|
862086eae5 | ||
|
|
277aa0c7e1 | ||
|
|
fc52741435 | ||
|
|
a7cd6bfd2e | ||
|
|
648df7b3d3 | ||
|
|
01f7f1f59c | ||
|
|
ff7f089f69 | ||
|
|
4e06e8c0c0 | ||
|
|
ca5ce905c7 | ||
|
|
0fc29de02c | ||
|
|
8509a16d6e | ||
|
|
0ed021357b | ||
|
|
58ad7a1e8a | ||
|
|
622ee29dd8 | ||
|
|
5cbb4c6223 | ||
|
|
5299859385 | ||
|
|
c57b308909 | ||
|
|
6409274f83 | ||
|
|
bc534c12a5 | ||
|
|
b58fd2022a | ||
|
|
d850d026ed | ||
|
|
83627686d4 | ||
|
|
4e7d01c72c | ||
|
|
0f8ab20db7 | ||
|
|
88cc90786d | ||
|
|
fb66717b43 | ||
|
|
1d60433fcf | ||
|
|
0f3df6e92b | ||
|
|
ca7c63c7d7 | ||
|
|
135f9611df | ||
|
|
cfaaeebd4a | ||
|
|
09a0779180 | ||
|
|
7f7d09bc85 | ||
|
|
0c454a08dc | ||
|
|
d749b63549 | ||
|
|
2053a6950d | ||
|
|
41bd801e0d | ||
|
|
cd0e1a3962 | ||
|
|
7c2f482b3b | ||
|
|
7741d60afd | ||
|
|
8aac0c0327 | ||
|
|
7c26c56210 | ||
|
|
e88a780efe | ||
|
|
363fb5dc02 | ||
|
|
39a187b6da | ||
|
|
b4cc34a522 | ||
|
|
af94ef3d49 | ||
|
|
505855a53c | ||
|
|
87ac245341 | ||
|
|
1670a09d04 | ||
|
|
620b954336 | ||
|
|
cfd51e9b84 | ||
|
|
6c797f8216 | ||
|
|
40e208152a | ||
|
|
cf7bfa62ef | ||
|
|
a1086b9a04 | ||
|
|
9bc4bbd2c8 | ||
|
|
73156012e9 | ||
|
|
91cc3d77d4 | ||
|
|
3fc55a9e9f | ||
|
|
b666aa3f26 | ||
|
|
53e7dbe12f | ||
|
|
9d8ff6856b | ||
|
|
3fb38376b0 | ||
|
|
15c73d9dd3 | ||
|
|
86f71ffb93 | ||
|
|
eb928d3369 | ||
|
|
d7307665b3 | ||
|
|
2836f0ab5a | ||
|
|
91b7f3980c | ||
|
|
5053432c2d | ||
|
|
563c612395 | ||
|
|
95dced6455 | ||
|
|
989f0bbbfb | ||
|
|
e5eec28bfd | ||
|
|
bfc402f307 | ||
|
|
35a998b934 | ||
|
|
cadc8e499d | ||
|
|
7f7ea6da9f | ||
|
|
ab9a22d8e7 | ||
|
|
cd2728105e | ||
|
|
d75e84bdff | ||
|
|
e791fee38b | ||
|
|
ad5f057733 | ||
|
|
6f325b5fdb | ||
|
|
b73aeee18d | ||
|
|
8a54035a9f | ||
|
|
af28e30e4c | ||
|
|
492513306c | ||
|
|
bc554ff4a7 | ||
|
|
b7a0d1ece8 | ||
|
|
ca384218dc | ||
|
|
e9550fd6b2 | ||
|
|
232a305d51 | ||
|
|
a6ab448eeb | ||
|
|
f9aa157c6c | ||
|
|
b7df24acaa | ||
|
|
ad4ca1b2d7 | ||
|
|
c562d197e7 | ||
|
|
83ba1899b7 | ||
|
|
3420adc7c9 | ||
|
|
fd39f28e46 | ||
|
|
710125852a | ||
|
|
e8ec208390 | ||
|
|
4584562607 | ||
|
|
2c1412a088 | ||
|
|
0a65382979 | ||
|
|
4f404f66e5 | ||
|
|
89505ada00 | ||
|
|
374a30ac5a | ||
|
|
cee4ee4128 | ||
|
|
58fc5e2ffa | ||
|
|
951ea43f8b | ||
|
|
ca16ecef24 | ||
|
|
bb025dc2a1 | ||
|
|
d797169bd0 | ||
|
|
891f9e2252 | ||
|
|
4a579c00ce | ||
|
|
9cb4d8e088 | ||
|
|
5d3ee7755a | ||
|
|
c047c46587 | ||
|
|
54f53be5b5 | ||
|
|
8bb9e5b22f | ||
|
|
379791a326 | ||
|
|
38ec68b303 | ||
|
|
a5095b04ad | ||
|
|
a27ddb40be | ||
|
|
bc36be8a5e | ||
|
|
1e35556034 | ||
|
|
882cd41d4b | ||
|
|
724fb4bf8f | ||
|
|
b07437dbfa | ||
|
|
96f05cd518 | ||
|
|
77411e94a4 | ||
|
|
0da9c62ef8 | ||
|
|
52a7885f3c | ||
|
|
f98f089d63 | ||
|
|
6b618f3abe | ||
|
|
0732ffa76e | ||
|
|
b5b4636e56 | ||
|
|
ca12d040e1 | ||
|
|
3388b9fafa | ||
|
|
954b36e14c | ||
|
|
4d43814220 | ||
|
|
f6262c82e1 | ||
|
|
7ead12922f | ||
|
|
33a6a7869c | ||
|
|
bf995f989c | ||
|
|
21de6c6520 | ||
|
|
c14aa6851e | ||
|
|
8260eced2d | ||
|
|
d028465dc5 | ||
|
|
29dab5e47d | ||
|
|
9e655631b4 | ||
|
|
179c7b80bb | ||
|
|
349bf29122 | ||
|
|
295357f12b | ||
|
|
940f8d999e | ||
|
|
5605d53a5f | ||
|
|
116d103119 | ||
|
|
2fd8c643af | ||
|
|
4367ae7934 | ||
|
|
749461334d | ||
|
|
e83a027023 | ||
|
|
1883b477a3 | ||
|
|
37e2cd40da | ||
|
|
81a9329975 | ||
|
|
0eb019fc3c | ||
|
|
4129c75475 | ||
|
|
3d66f03f58 | ||
|
|
7b83104fd6 | ||
|
|
794aede27f | ||
|
|
08eb39b206 | ||
|
|
2566c7f3d7 | ||
|
|
a8522bb3b5 | ||
|
|
92b9142902 | ||
|
|
d07e3e6522 | ||
|
|
29aabdfba8 | ||
|
|
9af1b0cfdc | ||
|
|
6e32c7fe85 | ||
|
|
ddf5915c6a | ||
|
|
cdbf1fa73a | ||
|
|
5d926b022b | ||
|
|
50bcca10e2 | ||
|
|
a5528c06ee | ||
|
|
94526de04b | ||
|
|
1ddf7abe6f | ||
|
|
a742c1b034 | ||
|
|
6e726ac2a6 | ||
|
|
5877b40be5 | ||
|
|
a3c7f5aa46 | ||
|
|
4e28bf03bd | ||
|
|
f92482d89e | ||
|
|
3c54429fe0 | ||
|
|
4c4c22e861 | ||
|
|
00e27d9327 | ||
|
|
f082278041 | ||
|
|
09dde64c57 | ||
|
|
b52a6357f6 | ||
|
|
f14a566d06 | ||
|
|
b352ec6888 | ||
|
|
4ec1bad03d | ||
|
|
f20bbc119d | ||
|
|
8555c3d422 | ||
|
|
71f9d03b19 | ||
|
|
0b31379078 | ||
|
|
29c204b2c2 | ||
|
|
657e881963 | ||
|
|
ef6bdc70a4 | ||
|
|
e4a36115a2 | ||
|
|
325f86832c | ||
|
|
e3dbdd6b09 | ||
|
|
919041e879 | ||
|
|
182231a183 | ||
|
|
46d4ae8fc5 | ||
|
|
73ab1936a3 | ||
|
|
59c72527b5 | ||
|
|
5ea3bcc1dd | ||
|
|
c140052822 | ||
|
|
f7832774d9 | ||
|
|
279d25c03a | ||
|
|
f1984047a8 | ||
|
|
eae7c1bd60 | ||
|
|
d7f592ebda | ||
|
|
1798ce002a | ||
|
|
57a0cca595 | ||
|
|
b26fbd7693 | ||
|
|
43b0e25bdb | ||
|
|
3377af1305 | ||
|
|
c81c1006b7 | ||
|
|
f313857f96 | ||
|
|
d5fbe02149 | ||
|
|
159cee0b39 | ||
|
|
5da4dd6cca | ||
|
|
e9daf05f16 | ||
|
|
a12643194a | ||
|
|
25d8fc08f7 | ||
|
|
a72378dd4d | ||
|
|
9aed70408b | ||
|
|
5ae2c71c3a | ||
|
|
4edce515b8 | ||
|
|
67ff664eb8 | ||
|
|
71c1a4f102 | ||
|
|
ba4ba1b9fc | ||
|
|
59f10f06ca | ||
|
|
f7953cbc37 | ||
|
|
9a74e81754 | ||
|
|
420e4b6766 | ||
|
|
aed48ffc93 | ||
|
|
0cebe69ff8 | ||
|
|
e5990dba81 | ||
|
|
c9b0d01250 | ||
|
|
4918ed3f3c | ||
|
|
b176ce4251 | ||
|
|
518ff5409e | ||
|
|
803bd3c5b2 | ||
|
|
03d4e73304 | ||
|
|
55a820b09f | ||
|
|
f2a65dc360 | ||
|
|
7b4a889ea7 | ||
|
|
f627d251c3 | ||
|
|
d5b7125415 | ||
|
|
67dd3cf0e3 | ||
|
|
b8b62bb5af | ||
|
|
b4a9d1ac18 | ||
|
|
8aae651c2c | ||
|
|
fc9465b324 | ||
|
|
579a50be2c | ||
|
|
9c5b967e4c | ||
|
|
d41deb729b | ||
|
|
a93a89f3f0 | ||
|
|
11d642a25f | ||
|
|
67448498ea | ||
|
|
489b8da713 | ||
|
|
728d4d0fa8 | ||
|
|
e70f4ff3a6 | ||
|
|
04032c0dfe | ||
|
|
ecba6461df | ||
|
|
433ba6c250 | ||
|
|
ff6c55d1d0 | ||
|
|
d9f8facf2e | ||
|
|
6bd3e5cc8f | ||
|
|
936fb5ae45 | ||
|
|
8e661e1260 | ||
|
|
987d9b0502 | ||
|
|
f6a9169446 | ||
|
|
d5c1c43eb1 | ||
|
|
20c74dac60 | ||
|
|
30161275a3 | ||
|
|
7e9479806e | ||
|
|
39b9ea9f1d | ||
|
|
5a9165d7c6 | ||
|
|
4f851156b7 | ||
|
|
b8fa278173 | ||
|
|
2cfb344e13 | ||
|
|
1d2bf92abe | ||
|
|
cefcda9f40 | ||
|
|
a4bc67ff8e | ||
|
|
48b3dea256 | ||
|
|
1f5231d905 | ||
|
|
c526e18992 | ||
|
|
4594765cbd | ||
|
|
e2a787e519 | ||
|
|
45b3e0aa6a | ||
|
|
7bdb2615d4 | ||
|
|
019a502490 | ||
|
|
6793fd5bc7 | ||
|
|
8ac5cf759e | ||
|
|
19880567ba | ||
|
|
5c5a3fefc1 | ||
|
|
90057828a5 | ||
|
|
7cfc3a09a2 | ||
|
|
b8bbee27f8 | ||
|
|
8e5c665e49 | ||
|
|
7b1e6fb953 | ||
|
|
79936e69c5 | ||
|
|
a19cd327f3 | ||
|
|
5c831c156b | ||
|
|
602df08df5 | ||
|
|
3b8d06a9e3 | ||
|
|
5fd43b50e0 | ||
|
|
15d39413f3 | ||
|
|
5e01946981 | ||
|
|
576d11f09a | ||
|
|
a43c296eef | ||
|
|
6015591e34 | ||
|
|
6f5f031b24 | ||
|
|
1499e78795 | ||
|
|
1b45ab7222 | ||
|
|
005b158ad3 | ||
|
|
26d003e840 | ||
|
|
192fb39302 | ||
|
|
1650dfcc29 | ||
|
|
fda83c4c1d | ||
|
|
1a6ebbb8e5 | ||
|
|
ea1f8912b8 | ||
|
|
a5885d2628 | ||
|
|
716d4c6f28 | ||
|
|
b3a36c82bb | ||
|
|
75fff1d52f | ||
|
|
2d88987cb3 | ||
|
|
8bbbdbd359 | ||
|
|
8a2aa44de8 | ||
|
|
3715c555d3 | ||
|
|
5e4de6cc5f | ||
|
|
4cadfc64f6 | ||
|
|
a3f0d47cad | ||
|
|
bc55b5fdda | ||
|
|
43474712eb | ||
|
|
bf48cbb89c | ||
|
|
c50d8e216a | ||
|
|
8c160bcddf | ||
|
|
6fd6aa2733 | ||
|
|
d7431c459d | ||
|
|
464a11cd69 | ||
|
|
154535a7b1 | ||
|
|
f379a759bb | ||
|
|
8cbd7eafb8 | ||
|
|
6fb304d010 | ||
|
|
089b2290bd | ||
|
|
c4e5bfdabb | ||
|
|
9d5cb104c3 | ||
|
|
d577b8b34e | ||
|
|
6219cd1d86 | ||
|
|
dfd1ac7e99 | ||
|
|
e3838b291f | ||
|
|
04a073c7cf | ||
|
|
04d5c60957 | ||
|
|
2be6cad7e1 | ||
|
|
b44e036be3 | ||
|
|
7ac6e9d37b | ||
|
|
6854e96fb8 | ||
|
|
2df3a386ad | ||
|
|
187b47eddd | ||
|
|
763aeb5fae | ||
|
|
3cad43006a | ||
|
|
8e4cdb78a0 | ||
|
|
9d80458250 | ||
|
|
d601c2ae64 | ||
|
|
87cd6dfb54 | ||
|
|
3dea1b3d7c | ||
|
|
f882c166cd | ||
|
|
9be0acd75b | ||
|
|
0901683880 | ||
|
|
216f3045db | ||
|
|
ce544adb62 | ||
|
|
107d299822 | ||
|
|
d577a34135 | ||
|
|
394c3412e5 | ||
|
|
e3aa726f12 | ||
|
|
e46b7aaf35 | ||
|
|
68947de06a | ||
|
|
1739de95a6 | ||
|
|
759f184d36 | ||
|
|
8a8d3437a6 | ||
|
|
87f0d6930e | ||
|
|
e659f11f05 | ||
|
|
b53920ddc5 | ||
|
|
21c2bb281f | ||
|
|
b0232eb917 | ||
|
|
f8c855eab1 | ||
|
|
8a277aebd7 | ||
|
|
d4c25476d2 | ||
|
|
8fa35da2d1 | ||
|
|
dcf5852432 | ||
|
|
e2aa342539 | ||
|
|
e28e5230d7 | ||
|
|
cbe5b15abd | ||
|
|
566546748d | ||
|
|
320f56e473 | ||
|
|
54e7998bf7 | ||
|
|
479dcb18b3 | ||
|
|
1a9329d1df | ||
|
|
7fda51a0c9 | ||
|
|
28368cd6c0 | ||
|
|
5d6b1280a8 | ||
|
|
8fddb4d6bf | ||
|
|
81a9b65559 | ||
|
|
d20533e048 | ||
|
|
6eb32b06af | ||
|
|
9c9970d6fb | ||
|
|
887d634fde | ||
|
|
9c334fb021 | ||
|
|
8c9880145e | ||
|
|
6fa6f3d1dc | ||
|
|
fe0c9fe57a | ||
|
|
3ab820763b | ||
|
|
31a6f6af93 | ||
|
|
32f4245024 | ||
|
|
92a8aba69c | ||
|
|
c2fec0a030 | ||
|
|
e0ef53431a | ||
|
|
093dcba4ba | ||
|
|
1cffb64344 | ||
|
|
caffe7b455 | ||
|
|
2e87db99c2 | ||
|
|
220ef5ae1a | ||
|
|
395f7272c3 | ||
|
|
fd427c3931 | ||
|
|
2ea07688be | ||
|
|
b1f8324c21 | ||
|
|
e920fb16d4 | ||
|
|
802ae3643c | ||
|
|
42cc031200 | ||
|
|
d6cf4648a2 | ||
|
|
d6deb52731 | ||
|
|
a4c8221fc4 | ||
|
|
50b921f318 | ||
|
|
e0526b9584 | ||
|
|
f822f6d931 | ||
|
|
0a25d75682 | ||
|
|
0eea2c672f | ||
|
|
e4e7b8f449 | ||
|
|
54a5c621c4 | ||
|
|
03e1d9a863 | ||
|
|
098fc340ca | ||
|
|
27c8a03c16 | ||
|
|
1ddf829a6d | ||
|
|
6952a8b72a | ||
|
|
2ee720760d | ||
|
|
ccee0eecb5 | ||
|
|
2b7e97a09f | ||
|
|
a2926b8fe9 | ||
|
|
9009317f83 | ||
|
|
67772e7221 | ||
|
|
cc02da47d7 | ||
|
|
af540183a6 | ||
|
|
6581bb4ab4 | ||
|
|
53e5edcb75 | ||
|
|
ae22066bad | ||
|
|
6ee5daf884 | ||
|
|
93edc56f1d | ||
|
|
41970348cd | ||
|
|
815a823104 | ||
|
|
c27f99ab58 | ||
|
|
ed35a878a5 | ||
|
|
bd1ab5e80b | ||
|
|
a1fae66800 | ||
|
|
c4d1761687 | ||
|
|
a647347e10 | ||
|
|
2d42ae9a36 | ||
|
|
8ce900acca | ||
|
|
60ee55b602 | ||
|
|
75cd62b7c2 | ||
|
|
55fe47d4f3 | ||
|
|
440d5072b7 | ||
|
|
80968ee6c4 | ||
|
|
99254c964b | ||
|
|
d2a4cf74e8 | ||
|
|
fd918ef15f | ||
|
|
e16ed5ca1a | ||
|
|
25e5470c5e | ||
|
|
59320e6828 | ||
|
|
f9aacb4c66 | ||
|
|
28faf3888c | ||
|
|
a9e22947ef | ||
|
|
7585fc80b4 | ||
|
|
5139af9f48 | ||
|
|
f2521717ae | ||
|
|
0f935b92cb | ||
|
|
5e3a2b456e | ||
|
|
632f29d1d6 | ||
|
|
7353d69f1a | ||
|
|
0b1cdf6a68 | ||
|
|
e0350b2837 | ||
|
|
1a521505a6 | ||
|
|
12d457e3ee | ||
|
|
d58933ea8d | ||
|
|
5bbfd15f38 | ||
|
|
acf4662d2f | ||
|
|
1d55ee7f93 | ||
|
|
b86afd7fc9 | ||
|
|
a5164dc0b6 | ||
|
|
cc919ea614 | ||
|
|
42d0d49682 | ||
|
|
07e81a117b | ||
|
|
a56bcb09b0 | ||
|
|
28b3272cd0 | ||
|
|
5c7221d0d7 | ||
|
|
0aa79d28f8 | ||
|
|
4b4cdb85ff | ||
|
|
654f7742e4 | ||
|
|
f082bca6c9 | ||
|
|
f7ace9d92a | ||
|
|
9759b44cbb | ||
|
|
5dfe8a83cd | ||
|
|
3c47f21337 | ||
|
|
b75360bdb1 | ||
|
|
32bace863c | ||
|
|
acda1e4dd8 | ||
|
|
a342def43f | ||
|
|
b100435d9c | ||
|
|
b2a29e2b13 | ||
|
|
fc0af56136 | ||
|
|
fd246aac93 | ||
|
|
7d90ecf91f | ||
|
|
d2aa72d873 | ||
|
|
d0b57c0419 | ||
|
|
5bee714437 | ||
|
|
9af537c985 | ||
|
|
30c56a0ec9 | ||
|
|
563a72ad95 | ||
|
|
2d4aff3b08 | ||
|
|
e0783cad60 | ||
|
|
feaad367df | ||
|
|
6b5b58ea94 | ||
|
|
60d1d336d6 | ||
|
|
2b93407e64 | ||
|
|
d5dec31b39 | ||
|
|
2608aa4c97 | ||
|
|
c174ca1c7e | ||
|
|
80c26a9550 | ||
|
|
f4ad6b963f | ||
|
|
b470cdf60c | ||
|
|
585bf4ccb4 | ||
|
|
6b0fedfabf | ||
|
|
9a1510a4f1 | ||
|
|
bcb4594afa | ||
|
|
0ebec74bed | ||
|
|
d10f5734fb | ||
|
|
91171727e4 | ||
|
|
e8a91d2631 | ||
|
|
cd020e536b | ||
|
|
c05605a286 | ||
|
|
63a698b679 | ||
|
|
3d3ef6f37b | ||
|
|
9e839e130e | ||
|
|
3e2ca2fd5f | ||
|
|
ee26262b6e | ||
|
|
4327af5133 | ||
|
|
6cce2ee70a | ||
|
|
70d07433d5 | ||
|
|
8109c63250 | ||
|
|
32ba7361ac | ||
|
|
a9da1b648f | ||
|
|
1fcd64a1ab | ||
|
|
f8362b6bfa | ||
|
|
fb0541fddf | ||
|
|
61b645c51c | ||
|
|
84b5a69074 | ||
|
|
374f445953 | ||
|
|
bf78db9b75 | ||
|
|
e3882b78db | ||
|
|
5389d4fc13 | ||
|
|
01529cc981 | ||
|
|
93fb571725 | ||
|
|
ab4aea315a | ||
|
|
90ca50ae7a | ||
|
|
3cb36d36bf | ||
|
|
8f51f025d0 | ||
|
|
e048fc6653 | ||
|
|
a44fc62fee | ||
|
|
9da29bf99a | ||
|
|
1edb669583 | ||
|
|
8e4e2469b0 | ||
|
|
f91953f67a | ||
|
|
0bb61149e8 | ||
|
|
e5f7d1077d | ||
|
|
7c91a77442 | ||
|
|
fb24398b01 | ||
|
|
43107965a9 | ||
|
|
67b7435624 | ||
|
|
78d3fd3768 | ||
|
|
243e4889b9 | ||
|
|
42564e683b | ||
|
|
96097d1cd3 | ||
|
|
4f35a8d79f | ||
|
|
3b8bed5b0a | ||
|
|
5897f0e3ed | ||
|
|
2aba80d0ca | ||
|
|
af6cd2e38b | ||
|
|
5ab2664c70 | ||
|
|
39c31ef5d9 | ||
|
|
bd1c7a35e2 | ||
|
|
0eed041986 | ||
|
|
1958c34bcb | ||
|
|
f5ceede3cd | ||
|
|
d98473f5e3 | ||
|
|
19d7027361 | ||
|
|
a34c2863bd | ||
|
|
4521dce1a9 | ||
|
|
3564c1a45c | ||
|
|
b6e2d25462 | ||
|
|
256ce50c49 | ||
|
|
fb0ad73035 | ||
|
|
71636c3413 | ||
|
|
5ef28bab9a | ||
|
|
757433629a | ||
|
|
050ae2a512 | ||
|
|
4c81eac8fb | ||
|
|
a4544d7943 | ||
|
|
9d65537317 | ||
|
|
b6a284e2bc | ||
|
|
f4e5690841 | ||
|
|
fa84479254 | ||
|
|
6ef1ca0a0b | ||
|
|
927a1481a9 | ||
|
|
7f9f45c720 | ||
|
|
7ddc607fd2 | ||
|
|
4e1b0c6abe | ||
|
|
e4d7b53112 | ||
|
|
f9ff57a26e | ||
|
|
2af9f19c3a | ||
|
|
dbb8e2b245 | ||
|
|
689ad18c71 | ||
|
|
4422787d4e | ||
|
|
8d1057477b | ||
|
|
b3dff3a520 | ||
|
|
6141be7310 | ||
|
|
2762459acf | ||
|
|
53d8d4dbb4 | ||
|
|
5ae72f3e6f | ||
|
|
872b36124f | ||
|
|
f762ab6aa7 | ||
|
|
c311f9a4ed | ||
|
|
0a7517ecdd | ||
|
|
03febd9484 | ||
|
|
655b97bfd5 | ||
|
|
fdbc7f4621 | ||
|
|
f9fe303671 | ||
|
|
d4459eb6d6 | ||
|
|
9087a3ebbd | ||
|
|
56669f4247 | ||
|
|
22be1a1104 | ||
|
|
a80c585086 | ||
|
|
6d5dbd9729 | ||
|
|
0b4e3de455 | ||
|
|
8faef235a6 | ||
|
|
559bc7c731 | ||
|
|
3d7fdd0f35 | ||
|
|
726fcf861c | ||
|
|
839105f41d | ||
|
|
a2a27e2216 | ||
|
|
f866c8a61f | ||
|
|
472082cb03 | ||
|
|
b5a59ddb6a | ||
|
|
82da3c0027 | ||
|
|
908e75de39 |
5
.gitattributes
vendored
@@ -1,5 +0,0 @@
|
|||||||
# Ignore all JavaScript files in a directory
|
|
||||||
src/main/resources/static/pdfjs/* linguist-vendored
|
|
||||||
src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
|
||||||
src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
|
||||||
src/main/resources/static/css/fonts/* linguist-vendored
|
|
||||||
13
.github/FUNDING.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
custom: ['https://paypal.me/froodleplex?country.x=GB&locale.x=en_GB'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "gradle" # See documentation for possible values
|
|
||||||
directory: "/" # Location of package manifests
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
55
.github/workflows/codeql.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
|
|
||||||
name: "Build repo"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ "main" ]
|
|
||||||
schedule:
|
|
||||||
- cron: '15 12 * * 1'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
|
|
||||||
# - name: Initialize CodeQL
|
|
||||||
# uses: github/codeql-action/init@v2
|
|
||||||
# with:
|
|
||||||
# languages: java
|
|
||||||
|
|
||||||
- uses: gradle/gradle-build-action@v2.3.3
|
|
||||||
with:
|
|
||||||
gradle-version: 7.6
|
|
||||||
arguments: assemble --no-build-cache
|
|
||||||
|
|
||||||
#- name: Perform CodeQL analysis
|
|
||||||
# uses: github/codeql-action/analyze@v2
|
|
||||||
79
.github/workflows/push-docker.yml
vendored
@@ -1,79 +0,0 @@
|
|||||||
name: Push Docker Image with VersionNumber
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- testGit
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
push:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
|
|
||||||
|
|
||||||
- uses: gradle/gradle-build-action@v2.3.3
|
|
||||||
with:
|
|
||||||
gradle-version: 7.6
|
|
||||||
arguments: clean build
|
|
||||||
|
|
||||||
- name: Make Gradle wrapper executable
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
- name: Get version number
|
|
||||||
id: versionNumber
|
|
||||||
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
|
||||||
|
|
||||||
# - name: Check if tag exists
|
|
||||||
# id: checkIdExists
|
|
||||||
# continue-on-error: true
|
|
||||||
# run: |
|
|
||||||
# response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
|
|
||||||
# result=$(echo $response | jq ".results")
|
|
||||||
# if [ "$result" == "[]" ]; then
|
|
||||||
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
|
|
||||||
# else
|
|
||||||
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
|
|
||||||
# exit 1;
|
|
||||||
# fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Setup buildx
|
|
||||||
run: |
|
|
||||||
docker buildx create --name mybuilder
|
|
||||||
docker buildx use mybuilder
|
|
||||||
|
|
||||||
- name: Build and push versioned amd64 and v8
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
run: |
|
|
||||||
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}-alpha" .
|
|
||||||
|
|
||||||
|
|
||||||
- name: Build and push versioned amd64 and v8
|
|
||||||
if: github.ref == 'refs/heads/master'
|
|
||||||
run: |
|
|
||||||
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .
|
|
||||||
|
|
||||||
|
|
||||||
- name: Build and push latest amd64 and v8
|
|
||||||
if: github.ref == 'refs/heads/master'
|
|
||||||
run: |
|
|
||||||
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:latest" .
|
|
||||||
|
|
||||||
|
|
||||||
122
.gitignore
vendored
@@ -1,112 +1,10 @@
|
|||||||
|
node_modules/
|
||||||
|
*.code-workspace
|
||||||
### Eclipse ###
|
.idea/
|
||||||
.metadata
|
dist/
|
||||||
bin/
|
android/
|
||||||
tmp/
|
ios/
|
||||||
*.tmp
|
releases/
|
||||||
*.bak
|
.vscode/
|
||||||
*.swp
|
.env.local
|
||||||
*~.nib
|
/server-node/jobs
|
||||||
local.properties
|
|
||||||
.settings/
|
|
||||||
.loadpath
|
|
||||||
.recommenders
|
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.gradle
|
|
||||||
.lock
|
|
||||||
|
|
||||||
# External tool builders
|
|
||||||
.externalToolBuilders/
|
|
||||||
|
|
||||||
# Locally stored "Eclipse launch configurations"
|
|
||||||
*.launch
|
|
||||||
|
|
||||||
# PyDev specific (Python IDE for Eclipse)
|
|
||||||
*.pydevproject
|
|
||||||
|
|
||||||
# CDT-specific (C/C++ Development Tooling)
|
|
||||||
.cproject
|
|
||||||
|
|
||||||
# CDT- autotools
|
|
||||||
.autotools
|
|
||||||
|
|
||||||
# Java annotation processor (APT)
|
|
||||||
.factorypath
|
|
||||||
|
|
||||||
# PDT-specific (PHP Development Tools)
|
|
||||||
.buildpath
|
|
||||||
|
|
||||||
# sbteclipse plugin
|
|
||||||
.target
|
|
||||||
|
|
||||||
# Tern plugin
|
|
||||||
.tern-project
|
|
||||||
|
|
||||||
# TeXlipse plugin
|
|
||||||
.texlipse
|
|
||||||
|
|
||||||
# STS (Spring Tool Suite)
|
|
||||||
.springBeans
|
|
||||||
|
|
||||||
# Code Recommenders
|
|
||||||
.recommenders/
|
|
||||||
|
|
||||||
# Annotation Processing
|
|
||||||
.apt_generated/
|
|
||||||
.apt_generated_test/
|
|
||||||
|
|
||||||
# Scala IDE specific (Scala & Java development for Eclipse)
|
|
||||||
.cache-main
|
|
||||||
.scala_dependencies
|
|
||||||
.worksheet
|
|
||||||
|
|
||||||
# Uncomment this line if you wish to ignore the project description file.
|
|
||||||
# Typically, this file would be tracked if it contains build/dependency configurations:
|
|
||||||
#.project
|
|
||||||
|
|
||||||
### Eclipse Patch ###
|
|
||||||
# Spring Boot Tooling
|
|
||||||
.sts4-cache/
|
|
||||||
|
|
||||||
### Git ###
|
|
||||||
# Created by git for backups. To disable backups in Git:
|
|
||||||
# $ git config --global mergetool.keepBackup false
|
|
||||||
*.orig
|
|
||||||
|
|
||||||
# Created by git when using merge tools for conflicts
|
|
||||||
*.BACKUP.*
|
|
||||||
*.BASE.*
|
|
||||||
*.LOCAL.*
|
|
||||||
*.REMOTE.*
|
|
||||||
*_BACKUP_*.txt
|
|
||||||
*_BASE_*.txt
|
|
||||||
*_LOCAL_*.txt
|
|
||||||
*_REMOTE_*.txt
|
|
||||||
|
|
||||||
### Java ###
|
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
/build
|
|
||||||
35
CONTRIBUTE.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Contribute
|
||||||
|
|
||||||
|
This file should introduce you with the concepts and tools used in this project.
|
||||||
|
|
||||||
|
## Basic Setup
|
||||||
|
|
||||||
|
- Install/Update **Node (v22.2.0, [nvm](https://github.com/coreybutler/nvm-windows))** & NPM(>10.2.1)
|
||||||
|
- To install all dependecies `npm run update-all-dependencies` (in [root](/))
|
||||||
|
- To test your current setup and boot a complete install of spdf v2 run `npm run dev-all` (in [root](/))
|
||||||
|
|
||||||
|
## Nomenclature
|
||||||
|
|
||||||
|
- API - Probably refers to the “normal“ API of spdf v2 without workflows unless otherwise noted.
|
||||||
|
- Workflow - Either the express-endpoint for running workflows or the user defined workflow-json
|
||||||
|
- Action - A sub-element of a workflow describing what operation should run on the inputted file / output of the last action.
|
||||||
|
- Operation - The actual function that will run on the pdf, including parameters.
|
||||||
|
- Operator - The actual code/implementation of the Operation (e.g. impose.ts) OR The parent class of every Operator.
|
||||||
|
- Validator - A function that makes sure things are as they should be. Every Operator must have one.
|
||||||
|
- Decorator - Explanations and Human Readable names of fields, these will be displayed in the frontend and used to provide better errors for the (workflow-)API
|
||||||
|
|
||||||
|
## Folder structure
|
||||||
|
|
||||||
|
- client-tauri - The frontend - Can be built to web and to a desktop app (with extra functions) using tauri.
|
||||||
|
- server-node - The backend - Provides extra functionality for the web client.
|
||||||
|
- shared-operatons - Components (e.g. Operators) that are shared between frontend and backend.
|
||||||
|
|
||||||
|
## Adding a PDF Operator
|
||||||
|
|
||||||
|
An Operator is either shared by the server and the client or it might have different implementations based on if its executed by the client, desktop-backend or web-backend. The current structure allows us to define where the Operator can be run.
|
||||||
|
|
||||||
|
## PDF Library Docs
|
||||||
|
- [pdf-lib](https://pdf-lib.js.org) - js
|
||||||
|
- [mozilla's pdfjs-dist/pdf.js](https://www.npmjs.com/package/pdfjs-dist) - js
|
||||||
|
- [pdfcpu](https://pdfcpu.io) - go-wasm
|
||||||
|
- [opencv-wasm](https://www.npmjs.com/package/opencv-wasm) - c++-wasm
|
||||||
58
Dockerfile
@@ -1,58 +0,0 @@
|
|||||||
# Build jbig2enc in a separate stage
|
|
||||||
FROM debian:bullseye-slim as jbig2enc_builder
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
git \
|
|
||||||
automake \
|
|
||||||
autoconf \
|
|
||||||
libtool \
|
|
||||||
libleptonica-dev \
|
|
||||||
pkg-config \
|
|
||||||
ca-certificates \
|
|
||||||
zlib1g-dev \
|
|
||||||
make \
|
|
||||||
g++
|
|
||||||
|
|
||||||
RUN git clone https://github.com/agl/jbig2enc && \
|
|
||||||
cd jbig2enc && \
|
|
||||||
./autogen.sh && \
|
|
||||||
./configure && \
|
|
||||||
make && \
|
|
||||||
make install
|
|
||||||
|
|
||||||
# Main stage
|
|
||||||
FROM openjdk:17-jdk-slim
|
|
||||||
|
|
||||||
# Install necessary dependencies
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
libreoffice-core \
|
|
||||||
libreoffice-common \
|
|
||||||
libreoffice-writer \
|
|
||||||
libreoffice-calc \
|
|
||||||
libreoffice-impress \
|
|
||||||
python3-uno \
|
|
||||||
python3-pip \
|
|
||||||
unoconv \
|
|
||||||
pngquant \
|
|
||||||
ocrmypdf && \
|
|
||||||
pip install --user --upgrade ocrmypdf
|
|
||||||
|
|
||||||
# Copy the jbig2enc binary from the builder stage
|
|
||||||
COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2
|
|
||||||
|
|
||||||
# Copy the application JAR file
|
|
||||||
COPY build/libs/*.jar app.jar
|
|
||||||
|
|
||||||
# Expose the application port
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Set environment variables
|
|
||||||
ENV LOG_LEVEL=INFO
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
ENTRYPOINT ["java","-jar","/app.jar","-Dlogging.level=${LOG_LEVEL}"]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
# How to add new languages to Stirling-PDF
|
|
||||||
|
|
||||||
Fork Stirling-PDF and make a new branch out of Main
|
|
||||||
|
|
||||||
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
|
||||||
|
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/navbar.html#L80
|
|
||||||
|
|
||||||
For example to add Polish you would add
|
|
||||||
```
|
|
||||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">Polish</a>
|
|
||||||
```
|
|
||||||
The data-language-code is the code used to reference the file in the next step.
|
|
||||||
|
|
||||||
Start by copying the existing english property file
|
|
||||||
|
|
||||||
[https://github.com/Frooodle/Stirling-PDF/tree/langSetup/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_US.properties)
|
|
||||||
|
|
||||||
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
|
|
||||||
|
|
||||||
|
|
||||||
Then simply translate all property entries within that file and make a PR into main for others to use!
|
|
||||||
|
|
||||||
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but wont be able to verify the translations themselves)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# OCR Language Packs and Setup
|
|
||||||
|
|
||||||
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
|
||||||
|
|
||||||
## How does the OCR Work
|
|
||||||
Stirling-PDF uses OCRmyPDF which in turn uses tesseract for its text recognition.
|
|
||||||
All credit goes to them for this awesome work!
|
|
||||||
|
|
||||||
## Language Packs
|
|
||||||
|
|
||||||
Tesseract OCR supports a variety of languages. You can find additional language packs in the Tesseract GitHub repositories:
|
|
||||||
|
|
||||||
- [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast): These language packs are smaller and faster to load, but may provide lower recognition accuracy.
|
|
||||||
- [tessdata](https://github.com/tesseract-ocr/tessdata): These language packs are larger and provide better recognition accuracy, but may take longer to load.
|
|
||||||
|
|
||||||
Depending on your requirements, you can choose the appropriate language pack for your use case. By default Stirling-PDF uses the tessdata_fast eng but this can be replaced.
|
|
||||||
|
|
||||||
### Installing Language Packs
|
|
||||||
|
|
||||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
|
||||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/4.00/tessdata`
|
|
||||||
|
|
||||||
#### Docker
|
|
||||||
|
|
||||||
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
|
|
||||||
#### Docker Compose
|
|
||||||
Modify your `docker-compose.yml` file to include the following volume configuration:
|
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
your_service_name:
|
|
||||||
image: your_docker_image_name
|
|
||||||
volumes:
|
|
||||||
- /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Docker run
|
|
||||||
Add the following to your existing docker run command
|
|
||||||
```bash
|
|
||||||
-v /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Non-Docker
|
|
||||||
If you are not using Docker, you need to install the OCR components, including the ocrmypdf app.
|
|
||||||
You can see [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html)
|
|
||||||
|
|
||||||
|
|
||||||
33
Jenkinsfile
vendored
@@ -1,33 +0,0 @@
|
|||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
stages {
|
|
||||||
stage('Build') {
|
|
||||||
steps {
|
|
||||||
sh 'chmod 755 gradlew'
|
|
||||||
sh './gradlew build'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Docker Build') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
|
|
||||||
def image = "frooodle/s-pdf:$appVersion"
|
|
||||||
sh "docker build -t $image ."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Docker Push') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
|
|
||||||
def image = "frooodle/s-pdf:$appVersion"
|
|
||||||
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
|
|
||||||
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
|
|
||||||
sh "docker push $image"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
201
LICENSE
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
196
README.md
@@ -1,77 +1,159 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
# StirlingPDF rewrite
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
This is the development repository for the new StirlingPDF backend. With the power of JS, WASM & GO this will provide almost all functionality SPDF can do currently directly on the client. For automation purposes this will still provide an API to automate your workflows.
|
||||||
[](https://discord.gg/Cn8pWhQRxZ)
|
|
||||||
[](https://github.com/Frooodle/Stirling-PDF/)
|
|
||||||
[](https://github.com/Frooodle/stirling-pdf)
|
|
||||||
[](https://www.paypal.com/paypalme/froodleplex)
|
|
||||||
|
|
||||||
This is a locally hosted web application that allows you to perform various operations on PDF files, such as splitting and adding images.
|

|
||||||
|
This image is here to reflect current progress and will be updated accordingly.
|
||||||
|
|
||||||
Started off as a 100% ChatGPT made application, slowly moving away from that as more features are added
|
## Try the new API!
|
||||||
|
|
||||||
I will support and fix/add things to this if there is a demand [Discord](https://discord.gg/Cn8pWhQRxZ)
|
[](https://documenter.getpostman.com/view/30633786/2s9YRB1Wto)
|
||||||
|
|
||||||
|
## Understanding Workflows
|
||||||
|
|
||||||

|
Workflows define how to apply operations to a PDF, including their order and relations with eachother.
|
||||||
|
|
||||||
|
Workflows can be created via the web-ui and then exported or, if you want to brag a bit, you can create the JSON object yourself.
|
||||||
|
|
||||||
|
### Basics
|
||||||
|
|
||||||
|
To create your own, you have to understand a few key features first. You can also look at more examples our github repository.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outputOptions": {
|
||||||
|
"zip": false
|
||||||
|
},
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"type": "extract",
|
||||||
|
"values": {
|
||||||
|
"pageIndexes": [0, 2]
|
||||||
|
},
|
||||||
|
"actions": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The workflow above will extract the first (p\[0\]) and third (p\[2\]) page of the document.
|
||||||
|
|
||||||
|
You can also nest workflows like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outputOptions": {
|
||||||
|
"zip": false
|
||||||
|
},
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"type": "extract",
|
||||||
|
"values": {
|
||||||
|
"pageIndexes": [0, 2]
|
||||||
|
},
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"type": "impose",
|
||||||
|
"values": {
|
||||||
|
"nup": 2, // 2 pages of the input document will be put on one page of the output document.
|
||||||
|
"format": "A4L" // A4L -> The page size of the Ouput will be an A4 in Landscape. You can also use other paper formats and "P" for portrait output.
|
||||||
|
},
|
||||||
|
"actions": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you look at it closely, you will see that the extract operation has another nested operation of the type impose. This workflow will produce a PDF with the 1st and 2nd page of the input on one single page.
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
|
||||||
|
If that is not enought for you usecase, there is also the possibility to connect operations with eachother.
|
||||||
|
|
||||||
|
You can also do different operations to produce two different output PDFs from one input.
|
||||||
|
|
||||||
|
If you are interested in learning about this, take a look at the Example workflows provided in the repository, ask on the discord, or wait for me to finish this documentation.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
### Rewrite Roadmap
|
||||||
- Merge multiple PDFs together into a single resultant file
|
|
||||||
- Convert PDFs to and from images
|
|
||||||
- Reorganize PDF pages into different orders.
|
|
||||||
- Add images to PDFs at specified locations. (WIP)
|
|
||||||
- Rotating PDFs in 90 degree increments.
|
|
||||||
- Compressing PDFs to decrease their filesize.
|
|
||||||
- Add and remove passwords
|
|
||||||
- Set PDF Permissions
|
|
||||||
- Add watermark(s)
|
|
||||||
- Edit metadata
|
|
||||||
- Dark mode support.
|
|
||||||
|
|
||||||
## Technologies used
|
* [X] Client side PDF-Manipulation
|
||||||
- Spring Boot + Thymeleaf
|
* [X] Workflows
|
||||||
- PDFBox
|
* [X] passportjs backend (auth)
|
||||||
- e-iceblue spire.pdf.free (for PDF compression untill i find a nicer way)
|
* [ ] Auth in frontend
|
||||||
- HTML, CSS, JavaScript
|
* [ ] Feature equivalent with S-PDF v1
|
||||||
- Docker
|
* [ ] Stateful UI
|
||||||
|
* [ ] Node based editing of Workflows
|
||||||
|
|
||||||
## How to use
|
### Functions
|
||||||
|
|
||||||
### Locally
|
Current functions of spdf and their progress in this repo.
|
||||||
|
|
||||||
Prerequisites
|
#### PDF Functions
|
||||||
- Java 17 or later
|
|
||||||
- Gradle 7.0 or later
|
|
||||||
|
|
||||||
1. Clone or download the repository.
|
| Status | Feature | Description |
|
||||||
2. Build the project using Gradle by running `./gradlew build`
|
| ------ | -------------------------------------------------- | ----------- |
|
||||||
3. Start the application by running `./gradlew bootRun` or by calling the build jar in build/libs with java -jar jarName.jar
|
| ✔️ | arrange | |
|
||||||
|
| ✔️ | extract | |
|
||||||
|
| ✔️ | impose | |
|
||||||
|
| ✔️ | merge | |
|
||||||
|
| ✔️ | remove blank | |
|
||||||
|
| ✔️ | remove | |
|
||||||
|
| ✔️ | rotate pages | |
|
||||||
|
| ✔️ | scale content | |
|
||||||
|
| ✔️ | scale pages | |
|
||||||
|
| ✔️ | split by preset | |
|
||||||
|
| ✔️ | split by index | |
|
||||||
|
| ✔️ | update metadata | |
|
||||||
|
| ✔️ | pdf to single large page | |
|
||||||
|
| 🚧 | remove annotations | |
|
||||||
|
| 🚧 | flatten | |
|
||||||
|
| 🚧 | overlay pdfs | |
|
||||||
|
| 🚧 | compress | |
|
||||||
|
| 🚧 | change permissions | |
|
||||||
|
| 🚧 | pdf to pdf/a | |
|
||||||
|
| 🚧 | add page numbers | |
|
||||||
|
| 🚧 | add image | |
|
||||||
|
| 🚧 | add watermark | |
|
||||||
|
| 🚧 | auto rename | |
|
||||||
|
| 🚧 | add stamp | |
|
||||||
|
| ❌ | repair | |
|
||||||
|
| ❌ | sign with cert | |
|
||||||
|
| ❌ | ocr | |
|
||||||
|
| ❌ | auto split by size/count (+split by preset) | |
|
||||||
|
| ❌ | split pdfs by sections/chapters (+split by preset) | |
|
||||||
|
| ❌ | adjust colors/contrast | |
|
||||||
|
| ❌ | adjust colors/contrast | |
|
||||||
|
| ❌ | sanitize | |
|
||||||
|
| ❌ | sign | |
|
||||||
|
| ❌ | basic text editing | |
|
||||||
|
| ❌ | auto redact | |
|
||||||
|
|
||||||
|
#### Generic Filetype (Filetypes are not supported by workflows yet. Coming Soon™)
|
||||||
|
|
||||||
### Docker
|
| Status | Feature | Description |
|
||||||
https://hub.docker.com/r/frooodle/s-pdf
|
| ------ | ------------------- | ----------- |
|
||||||
|
| 🚧 | image to pdf | |
|
||||||
|
| 🚧 | pdf to image | |
|
||||||
|
| 🚧 | extract images | |
|
||||||
|
| 🚧 | show javascript | |
|
||||||
|
| ❌ | convert file to pdf | |
|
||||||
|
| ❌ | pdf to word | |
|
||||||
|
| ❌ | pdf to presentation | |
|
||||||
|
| ❌ | pdf to rtf | |
|
||||||
|
| ❌ | pdf to html | |
|
||||||
|
| ❌ | pdf to xml | |
|
||||||
|
| ❌ | url/website to pdf | |
|
||||||
|
| ❌ | markdown to pdf | |
|
||||||
|
| ❌ | pdf to csv | |
|
||||||
|
| ❌ | get all info | |
|
||||||
|
| ❌ | compare | |
|
||||||
|
|
||||||
Docker Run
|
✔️: Done, 🚧: Possible with current Libraries, ❌: Planned Feature
|
||||||
```
|
|
||||||
docker run -p 8080:8080 frooodle/s-pdf
|
|
||||||
```
|
|
||||||
Docker Compose
|
|
||||||
```
|
|
||||||
version: '3.3'
|
|
||||||
services:
|
|
||||||
s-pdf:
|
|
||||||
ports:
|
|
||||||
- '8080:8080'
|
|
||||||
image: frooodle/s-pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to View
|
## Contribute
|
||||||
1. Open a web browser and navigate to `http://localhost:8080/`
|
|
||||||
2. Use the application by following the instructions on the website.
|
|
||||||
|
|
||||||
## Note
|
For initial instructions look at [CONTRIBUTE.md](./CONTRIBUTE.md)
|
||||||
The application is currently not thread-safe
|
|
||||||
44
build.gradle
@@ -1,44 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'org.springframework.boot' version '3.0.3'
|
|
||||||
id 'io.spring.dependency-management' version '1.1.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
group = 'stirling.software'
|
|
||||||
version = '0.4.0'
|
|
||||||
sourceCompatibility = '17'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
||||||
|
|
||||||
implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
|
|
||||||
|
|
||||||
//general PDF
|
|
||||||
implementation 'org.apache.pdfbox:pdfbox:2.0.27'
|
|
||||||
implementation 'com.itextpdf:itextpdf:5.5.13.3'
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
enabled = false
|
|
||||||
manifest {
|
|
||||||
attributes 'Implementation-Title': 'Stirling-PDF',
|
|
||||||
'Implementation-Version': project.version
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('test') {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
|
|
||||||
task printVersion {
|
|
||||||
println project.version
|
|
||||||
}
|
|
||||||
2
client-tauri/.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_USE_AUTH=False
|
||||||
|
VITE_BACKEND=""
|
||||||
24
client-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
client-tauri/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||||
|
}
|
||||||
15
client-tauri/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Use an existing image as a base
|
||||||
|
FROM node:22.2.0-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --legacy-peer-deps
|
||||||
|
|
||||||
|
RUN npm i -g serve
|
||||||
|
|
||||||
|
COPY ./dist ./dist
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "serve", "-s", "dist", "-p", "3000" ]
|
||||||
13
client-tauri/Makefile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
vite-build:
|
||||||
|
npx tsc
|
||||||
|
npx vite build
|
||||||
|
|
||||||
|
vite-dockerize:
|
||||||
|
make vite-build
|
||||||
|
bash -c "cp ../package-lock.json ./"
|
||||||
|
docker build . -t stirling-pdf:latest
|
||||||
|
bash -c "rm ./package-lock.json"
|
||||||
|
docker image prune
|
||||||
|
|
||||||
|
tauri-build:
|
||||||
|
npx tauri build
|
||||||
11
client-tauri/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Stirling-PDF frontend
|
||||||
|
|
||||||
|
Tauri + Vite + React + Typescript
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Use the package scripts to start developement
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Use the make file to build for the correct platform
|
||||||
14
client-tauri/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/stirling-pdf-logo.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Tauri + React + TS</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
43
client-tauri/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@stirling-pdf/client-tauri",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"vite:dev": "vite",
|
||||||
|
"vite:preview-prod": "make vite-build && vite preview",
|
||||||
|
"tauri:build-debug": "npx tauri build --debug",
|
||||||
|
"tauri:dev": "tauri dev"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@stirling-pdf/shared-operations": "^0.0.0",
|
||||||
|
"@tauri-apps/api": "^1.5.1",
|
||||||
|
"@types/semver": "^7.5.8",
|
||||||
|
"archiver": "^6.0.1",
|
||||||
|
"i18next": "^23.6.0",
|
||||||
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"pdfjs-dist": "^4.0.189",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-i18next": "^15.0.1",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-material-symbols": "^4.4.0",
|
||||||
|
"react-router-bootstrap": "^0.26.2",
|
||||||
|
"react-router-dom": "^6.18.0",
|
||||||
|
"vite": "^5.4.2",
|
||||||
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
|
"vite-plugin-top-level-await": "^1.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^1.5.0",
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
|
"@types/react": "^18.2.15",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"@types/react-router-bootstrap": "^0.26.5",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"typescript": "^5.0.2",
|
||||||
|
"vite-plugin-compile-time": "^0.2.1",
|
||||||
|
"vite-plugin-dynamic-import": "^1.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
client-tauri/public/stirling-pdf-logo.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
4
client-tauri/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
3903
client-tauri/src-tauri/Cargo.lock
generated
Normal file
23
client-tauri/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "StirlingPDF"
|
||||||
|
version = "2.0.0" # needed for dev build?
|
||||||
|
description = "Selfhosted PDF processing"
|
||||||
|
authors = ["LaserKaspar, SaudF"]
|
||||||
|
license = ""
|
||||||
|
repository = ""
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "1.5", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "1.5", features = [ "fs-remove-dir", "fs-create-dir", "shell-all", "fs-write-file", "fs-read-file", "dialog-save", "dialog-open"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
# DO NOT REMOVE!!
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
3
client-tauri/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
BIN
client-tauri/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client-tauri/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
client-tauri/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
client-tauri/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client-tauri/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
client-tauri/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
client-tauri/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
client-tauri/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
client-tauri/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
client-tauri/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
client-tauri/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
client-tauri/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
client-tauri/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
client-tauri/src-tauri/icons/icon.icns
Normal file
BIN
client-tauri/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
client-tauri/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
15
client-tauri/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||||
|
#[tauri::command]
|
||||||
|
fn greet(name: &str) -> String {
|
||||||
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.invoke_handler(tauri::generate_handler![greet])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
78
client-tauri/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "npm run vite:dev",
|
||||||
|
"beforeBuildCommand": "make vite-build",
|
||||||
|
"devPath": "http://localhost:1420",
|
||||||
|
"distDir": "../dist",
|
||||||
|
"withGlobalTauri": false
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "StirlingPDF",
|
||||||
|
"version": "2.0.0"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"all": false,
|
||||||
|
"shell": {
|
||||||
|
"all": true,
|
||||||
|
"open": true,
|
||||||
|
"scope": [
|
||||||
|
{
|
||||||
|
"name": "libreoffice-version",
|
||||||
|
"cmd": "libreoffice",
|
||||||
|
"args": ["--version"]
|
||||||
|
},{
|
||||||
|
"name": "libreoffice-convert",
|
||||||
|
"cmd": "libreoffice",
|
||||||
|
"args": ["--headless","--convert-to",{ "validator": "\\S+" },{ "validator": "\\S+" },"--outdir",{ "validator": "\\S+" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"all": false,
|
||||||
|
"ask": false,
|
||||||
|
"confirm": false,
|
||||||
|
"message": false,
|
||||||
|
"open": true,
|
||||||
|
"save": true
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"all": false,
|
||||||
|
"readFile": true,
|
||||||
|
"writeFile": true,
|
||||||
|
"readDir": false,
|
||||||
|
"copyFile": false,
|
||||||
|
"createDir": true,
|
||||||
|
"removeDir": true,
|
||||||
|
"removeFile": false,
|
||||||
|
"renameFile": false,
|
||||||
|
"exists": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"identifier": "com.stirlingtools.pdf",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"category": "Business"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"fullscreen": false,
|
||||||
|
"resizable": true,
|
||||||
|
"title": "StirlingPDF",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
82
client-tauri/src/App.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Fragment, Suspense } from "react";
|
||||||
|
|
||||||
|
import { Routes, Route, Outlet, Navigate } from "react-router-dom";
|
||||||
|
import Home from "./pages/Home";
|
||||||
|
import Operators from "./pages/Operators";
|
||||||
|
import NoMatch from "./pages/NoMatch";
|
||||||
|
import NavBar from "./components/NavBar";
|
||||||
|
|
||||||
|
import { useTranslation, initReactI18next } from "react-i18next";
|
||||||
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
|
||||||
|
import i18next from "i18next";
|
||||||
|
import resourcesToBackend from "i18next-resources-to-backend";
|
||||||
|
import { listOperatorNames } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
|
import AuthenticatedRoute from "./components/AuthenticatedRoute";
|
||||||
|
import Login from "./pages/Auth/Login";
|
||||||
|
import Logout from "./pages/Auth/Logout";
|
||||||
|
import Register from "./pages/Auth/Register";
|
||||||
|
|
||||||
|
i18next.use(LanguageDetector).use(initReactI18next).use(resourcesToBackend((language: string, namespace: string) => import(`@stirling-pdf/shared-operations/public/locales/${namespace}/${language}.json`).catch((e) => console.warn("some component tried to render with an unsupported language, falling back to en", e))))
|
||||||
|
.init({
|
||||||
|
debug: false,
|
||||||
|
ns: ["common"], // Preload this namespace, no need to add the others, they will load once their module is loaded
|
||||||
|
defaultNS: "common",
|
||||||
|
fallbackLng: "en",
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
initImmediate: false // Makes loading blocking but sync
|
||||||
|
}); // TODO: use i18next.config.ts instead
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Loading/>}>
|
||||||
|
{/* Routes nest inside one another. Nested route paths build upon
|
||||||
|
parent route paths, and nested route elements render inside
|
||||||
|
parent route elements. See the note about <Outlet> below. */}
|
||||||
|
<Routes>
|
||||||
|
<Route path="/auth" element={<Layout />}>
|
||||||
|
<Route index element={<Navigate to="/auth/login" />}/>
|
||||||
|
<Route path="login" element={<Login />}></Route>
|
||||||
|
<Route path="logout" element={<Logout />}></Route>
|
||||||
|
<Route path="register" element={<Register />}></Route>
|
||||||
|
<Route path="*" element={<NoMatch />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route element={<AuthenticatedRoute />}>
|
||||||
|
<Route path="/" element={<Layout />}>
|
||||||
|
<Route index element={<Home />} />
|
||||||
|
<Route path="*" element={<NoMatch />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/operators" element={<Layout />}>
|
||||||
|
<Route index element={<NoMatch />} />
|
||||||
|
{listOperatorNames().map((name) => {
|
||||||
|
return <Route key={name} path={name} element={<Operators/>} />;
|
||||||
|
})}
|
||||||
|
<Route path="*" element={<NoMatch />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Loading() {
|
||||||
|
return "Loading";
|
||||||
|
}
|
||||||
|
|
||||||
|
function Layout() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div lang-direction={t("language.direction")}>
|
||||||
|
<NavBar/>
|
||||||
|
|
||||||
|
{/* An <Outlet> renders whatever child route is currently active,
|
||||||
|
so you can think about this <Outlet> as a placeholder for
|
||||||
|
the child routes we defined above. */}
|
||||||
|
<Outlet/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
client-tauri/src/assets/favicon.svg
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
style="enable-background:new 0 0 512 512;"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="favicon.svg"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
inkscape:export-filename="favicon.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs173">
|
||||||
|
|
||||||
|
|
||||||
|
<linearGradient
|
||||||
|
id="XMLID_5_"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="304.496"
|
||||||
|
y1="422.9102"
|
||||||
|
x2="316.036"
|
||||||
|
y2="326.2626">
|
||||||
|
<stop
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#DCF1F3"
|
||||||
|
id="stop156" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
style="stop-color:#C2C2C9"
|
||||||
|
id="stop158" />
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
</defs><sodipodi:namedview
|
||||||
|
id="namedview171"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.4142136"
|
||||||
|
inkscape:cx="219.91021"
|
||||||
|
inkscape:cy="232.63813"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2054"
|
||||||
|
inkscape:window-x="2869"
|
||||||
|
inkscape:window-y="-11"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="XMLID_4_" />
|
||||||
|
<style
|
||||||
|
type="text/css"
|
||||||
|
id="style150">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
.st1{fill:#C02223;}
|
||||||
|
.st2{fill:#882425;}
|
||||||
|
.st3{fill:url(#XMLID_5_);}
|
||||||
|
.st4{fill:url(#XMLID_7_);}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="XMLID_4_">
|
||||||
|
<path
|
||||||
|
id="XMLID_131_"
|
||||||
|
class="st1"
|
||||||
|
d="M 347.01402,14.355825 98.978019,69.02261 C 73.825483,74.547445 55.942464,96.792175 55.942464,122.52628 v 315.06096 c 0,22.39012 16.719895,41.14548 38.819234,43.76251 L 224.8861,498.36042 339.48636,384.26465 455.76603,265.15425 453.73057,84.870162 C 453.43979,62.916214 433.08513,46.632491 411.71274,51.284984 l -28.78729,6.251786 0.14539,-13.666697 C 383.36162,24.678542 365.62399,10.284894 347.01402,14.355825 Z"
|
||||||
|
sodipodi:nodetypes="ccssccccccccc"
|
||||||
|
style="stroke-width:1.45391" /><path
|
||||||
|
id="XMLID_117_"
|
||||||
|
class="st2"
|
||||||
|
d="m 383.21622,57.53677 v 285.8375 L 456.05681,265.00885 454.02135,78.763767 C 453.87595,59.863016 436.28372,45.905539 417.81914,49.97647 Z"
|
||||||
|
style="stroke-width:1.45391" /><polygon
|
||||||
|
id="XMLID_18_"
|
||||||
|
class="st3"
|
||||||
|
points="234.7,422.6 368.5,387.7 393.5,262.2 "
|
||||||
|
style="fill:url(#XMLID_5_)"
|
||||||
|
transform="matrix(1.4556308,0,0,1.4548265,-116.73161,-116.45231)" />
|
||||||
|
<linearGradient
|
||||||
|
id="XMLID_7_"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="223.0838"
|
||||||
|
y1="372.7559"
|
||||||
|
x2="241.4174"
|
||||||
|
y2="114.557"
|
||||||
|
gradientTransform="matrix(1.4539039,0,0,1.4539039,-116.19976,-116.20474)">
|
||||||
|
<stop
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#DCF1F3"
|
||||||
|
id="stop163" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
style="stop-color:#C2C2C9"
|
||||||
|
id="stop165" />
|
||||||
|
</linearGradient>
|
||||||
|
<path
|
||||||
|
id="XMLID_6_"
|
||||||
|
class="st4"
|
||||||
|
d="m 282.89686,214.84917 c 0,0 -22.24473,-28.93269 -38.67384,-36.78377 -10.46811,-4.94327 -26.02489,-6.83335 -38.23768,-0.72695 -18.02841,9.0142 -19.91848,34.31213 -3.34397,44.34406 3.92553,2.47165 9.15959,4.50711 15.99294,6.10641 36.63838,8.43264 97.12077,25.87949 89.70587,96.10304 0,0 -4.21633,65.86185 -73.56753,73.42215 -12.2128,1.30851 -24.57098,0.43617 -36.493,-2.32625 -16.42911,-3.63476 -45.50719,-11.04967 -59.75545,-19.91849 l -2.61703,-75.16682 h 6.97875 c 0,0 13.81208,33.43978 53.06749,49.57812 7.26952,2.90781 15.26599,4.07093 22.97168,2.90781 9.74116,-1.45391 21.22699,-6.68796 25.87949,-22.53551 0,0 7.85108,-23.11707 -32.85823,-35.76604 -32.56744,-10.17733 -63.24481,-20.64543 -75.89378,-54.95757 -5.961,-16.28371 -6.97874,-34.31212 -2.90781,-51.61358 5.37944,-22.53551 20.79082,-54.23062 64.40794,-67.89732 0,0 57.28381,-15.55677 96.53922,5.52484 l -1.74468,89.70587 z"
|
||||||
|
style="fill:url(#XMLID_7_);stroke-width:1.45391" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
1
client-tauri/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
22
client-tauri/src/components/AuthenticatedRoute.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Navigate, Outlet } from "react-router-dom";
|
||||||
|
|
||||||
|
interface AuthenticatedRouteProps {
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
children?: ReactNode; // Accepting children
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isAuthenticated() {
|
||||||
|
if (import.meta.env.VITE_USE_AUTH == "True") {
|
||||||
|
// TODO: if user is set in localstorage and is valid (either by time or by checking online) return true
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticatedRoute({}: {}): JSX.Element {
|
||||||
|
return isAuthenticated() ? <Outlet /> : <Navigate to="/auth/login" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticatedRoute;
|
||||||
22
client-tauri/src/components/BuildForm.module.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.fields {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields input, select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin: 8px 0;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
background-color: var(--md-sys-color-primary);;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
28
client-tauri/src/components/BuildForm.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Joi from "@stirling-tools/joi";
|
||||||
|
import { GenericField } from "./fields/GenericField";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import styles from "./BuildForm.module.css";
|
||||||
|
|
||||||
|
interface BuildFormProps {
|
||||||
|
/** The text to display inside the button */
|
||||||
|
schemaDescription: Joi.Description | undefined;
|
||||||
|
onSubmit: React.FormEventHandler<HTMLFormElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BuildForm({ schemaDescription, onSubmit }: BuildFormProps) {
|
||||||
|
console.log("Render Build Fields", schemaDescription);
|
||||||
|
const values = (schemaDescription?.keys as any)?.values.keys as { [key: string]: Joi.Description};
|
||||||
|
return (
|
||||||
|
<form onSubmit={(e) => { onSubmit(e); e.preventDefault(); }}>
|
||||||
|
<div className={styles.fields}>
|
||||||
|
{
|
||||||
|
values ? Object.keys(values).map((key) => {
|
||||||
|
return (<GenericField key={key} fieldName={key} joiDefinition={values[key]} />)
|
||||||
|
}) : undefined
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<input className={styles.submit} type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
client-tauri/src/components/NavBar.module.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.navbar_brand {
|
||||||
|
color: var(--md-sys-color-on-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 42px;
|
||||||
|
vertical-align: middle;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_text {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 4px;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
15
client-tauri/src/components/NavBar.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import StirlingLogo from "../assets/favicon.svg";
|
||||||
|
import NavBarStyles from "./NavBar.module.css";
|
||||||
|
|
||||||
|
function NavBar() {
|
||||||
|
return (
|
||||||
|
<nav>
|
||||||
|
<a className={NavBarStyles.navbar_brand} href="/">
|
||||||
|
<img className={NavBarStyles.main_icon} src={StirlingLogo} alt="icon"/>
|
||||||
|
<span className={NavBarStyles.icon_text}>Stirling PDF</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavBar;
|
||||||
22
client-tauri/src/components/OperatorCard.module.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.operator_card {
|
||||||
|
border: 1px solid var(--md-sys-color-surface-5);
|
||||||
|
border-radius: 1.75rem;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: var(--md-sys-color-surface-5);
|
||||||
|
transition: transform 0.3s, border 0.3s;
|
||||||
|
transform-origin: center center;
|
||||||
|
outline: 0px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator_card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator_card:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
transform: scale(1.08);
|
||||||
|
box-shadow: var(--md-sys-elevation-2);
|
||||||
|
}
|
||||||
34
client-tauri/src/components/OperatorCard.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
|
|
||||||
|
import styles from './OperatorCard.module.css';
|
||||||
|
import { MaterialSymbol, MaterialSymbolProps } from 'react-material-symbols';
|
||||||
|
|
||||||
|
interface OperatorCardProps {
|
||||||
|
/** The text to display inside the button */
|
||||||
|
operatorInternalName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OperatorCard({ operatorInternalName }: OperatorCardProps) {
|
||||||
|
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||||
|
const [materialSymbolName, setMaterialSymbolName] = useState<MaterialSymbolProps["icon"]>("error");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSchemaByName(operatorInternalName).then(schema => {
|
||||||
|
if(schema) {
|
||||||
|
setSchema(schema.schema);
|
||||||
|
setMaterialSymbolName(schema.materialSymbolName || "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [operatorInternalName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a key={operatorInternalName} href={"/operators/" + operatorInternalName}>
|
||||||
|
<div className={styles.operator_card}>
|
||||||
|
<h3><MaterialSymbol icon={materialSymbolName} size={30} fill grade={-25} color='black'></MaterialSymbol> { schema?.describe().flags.label }</h3>
|
||||||
|
{ schema?.describe().flags.description }
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
143
client-tauri/src/components/fields/GenericField.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import Joi from "@stirling-tools/joi";
|
||||||
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
interface GenericFieldProps {
|
||||||
|
fieldName: string,
|
||||||
|
joiDefinition: Joi.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Flags {
|
||||||
|
label: string,
|
||||||
|
description: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GenericField({ fieldName, joiDefinition }: GenericFieldProps) {
|
||||||
|
const flags = joiDefinition.flags as Flags;
|
||||||
|
|
||||||
|
switch (joiDefinition.type) {
|
||||||
|
case "number":
|
||||||
|
var validValues = joiDefinition.allow;
|
||||||
|
if(validValues) { // Restrained number input
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="number" list={fieldName} name={fieldName}/>
|
||||||
|
<datalist id={fieldName}>
|
||||||
|
{joiDefinition.allow.map((e: string) => {
|
||||||
|
return (<option key={e} value={e}/>)
|
||||||
|
})}
|
||||||
|
</datalist>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else { // Unrestrained number input
|
||||||
|
// TODO: Check if integer or not.
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="number" list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
if(joiDefinition.allow) { // Restrained text input
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="text" list={fieldName} name={fieldName}/>
|
||||||
|
<datalist id={fieldName}>
|
||||||
|
{joiDefinition.allow.map((e: string) => {
|
||||||
|
return (<option key={e} value={e}/>)
|
||||||
|
})}
|
||||||
|
</datalist>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="text" list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "comma_array":
|
||||||
|
if(joiDefinition.items.length == 1) {
|
||||||
|
const item: Joi.Description = joiDefinition.items[0];
|
||||||
|
|
||||||
|
if(item.type == "number") {
|
||||||
|
const props: any = {};
|
||||||
|
|
||||||
|
item.rules.forEach((rule: { args: any, name: string}) => {
|
||||||
|
|
||||||
|
switch (rule.name) {
|
||||||
|
case "integer":
|
||||||
|
if(props.pattern) {
|
||||||
|
return (<div>props.pattern was already set, this is not implemented.</div>);
|
||||||
|
}
|
||||||
|
props.pattern = `(\\d+)(,\\s*\\d+)*`;
|
||||||
|
break;
|
||||||
|
case "min":
|
||||||
|
// TODO: Could validate this in frontend first.
|
||||||
|
break;
|
||||||
|
case "max":
|
||||||
|
// TODO: Could validate this in frontend first.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return (<div>comma_array, item rule {rule.name} is not implemented.</div>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="text" pattern={props.pattern} list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (<div>comma_array, other types than numbers are not implemented yet.</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Implement multiple items if necessary
|
||||||
|
return (<div>comma_array, joi items are empty or bigger than one, this is not implemented</div>);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "alternatives": // TODO: Better support this. It is currently used by ScaleContent (working) and SplitByPreset (incompatible, but with that operator it isn't even considered a field so we need a different schema for that)
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="text" list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
case "boolean":
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="checkbox" list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
case "date":
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||||
|
<input type="date" list={fieldName} name={fieldName}/>
|
||||||
|
<br/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
console.log(joiDefinition);
|
||||||
|
return (<div>GenericField.tsx: <br/> "{fieldName}": requested type "{joiDefinition.type}" not found. Check console for further info.</div>)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
10
client-tauri/src/components/fields/InputField.module.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.custom_file_upload input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom_file_upload {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
29
client-tauri/src/components/fields/InputField.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { forwardRef, ForwardedRef, FormEvent } from 'react';
|
||||||
|
|
||||||
|
import styles from "./InputField.module.css";
|
||||||
|
|
||||||
|
function InputField(_props: {}, inputRef: ForwardedRef<HTMLInputElement>) {
|
||||||
|
function onChange(e: FormEvent<HTMLInputElement>) {
|
||||||
|
const files = (e.target as HTMLInputElement).files;
|
||||||
|
if(files) {
|
||||||
|
const filesArray: File[] = Array.from(files as any);
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = filesArray[i];
|
||||||
|
if(file) {
|
||||||
|
console.log(file.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Error("This should not happen. Contact maintainers.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className={styles.custom_file_upload}>
|
||||||
|
<input onChange={onChange} type="file" id="pdfFile" accept=".pdf" multiple ref={inputRef}/>
|
||||||
|
Upload your PDF(s)!
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef(InputField);
|
||||||
30
client-tauri/src/components/toolbar/LanguagePicker.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
// import NavDropdown from "react-bootstrap/NavDropdown";
|
||||||
|
// import { useTranslation } from "react-i18next";
|
||||||
|
// import { BsGlobe2 } from "react-icons/bs";
|
||||||
|
|
||||||
|
// function generateSublist() {
|
||||||
|
// const { i18n } = useTranslation();
|
||||||
|
// const out: JSX.Element[] = [];
|
||||||
|
// const languages = i18n.options.resources;
|
||||||
|
|
||||||
|
// for (const key in languages) {
|
||||||
|
// const lang: any = languages[key].translation;
|
||||||
|
// const staticKey = key;
|
||||||
|
// out.push((
|
||||||
|
// <NavDropdown.Item key={"language-"+key} className="nav-icon" onClick={()=>i18n.changeLanguage(staticKey)}>
|
||||||
|
// <span>{lang.language?.flag}</span>
|
||||||
|
// <span>{lang.language?.name}</span>
|
||||||
|
// </NavDropdown.Item>
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// return <>{out}</>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default function LanguagePicker() {
|
||||||
|
// return (
|
||||||
|
// <NavDropdown id="languages-dropdown" title={<><span className="nav-icon"><BsGlobe2/></span></>}>
|
||||||
|
// {generateSublist()}
|
||||||
|
// </NavDropdown>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
15
client-tauri/src/main.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
import "./root.css";
|
||||||
|
import 'react-material-symbols/rounded';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
0
client-tauri/src/pages/Auth/Auth.module.css
Normal file
13
client-tauri/src/pages/Auth/Login.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// TODO: Store user info in localstorage. Session cookie will be set automatically
|
||||||
|
|
||||||
|
// TODO: Check if user login is enabled on this server
|
||||||
|
|
||||||
|
import styles from './Auth.module.css';
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
return (
|
||||||
|
"Login Test Page"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
13
client-tauri/src/pages/Auth/Logout.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// TODO: Delete user info in localstorage. Send request to logout endpoint -> session cookie will be removed automatically.
|
||||||
|
|
||||||
|
// TODO: Check if user login is enabled on this server
|
||||||
|
|
||||||
|
import styles from './Auth.module.css';
|
||||||
|
|
||||||
|
function Logout() {
|
||||||
|
return (
|
||||||
|
"Logout Test Page"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logout;
|
||||||
13
client-tauri/src/pages/Auth/Register.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// TODO: Register user and login in the same request.
|
||||||
|
|
||||||
|
// TODO: Check if user registration & login is enabled on this server
|
||||||
|
|
||||||
|
import styles from './Auth.module.css';
|
||||||
|
|
||||||
|
function Register() {
|
||||||
|
return (
|
||||||
|
"Register Test Page"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Register;
|
||||||
32
client-tauri/src/pages/Home.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { listOperatorNames } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
|
import { OperatorCard } from "../components/OperatorCard";
|
||||||
|
|
||||||
|
|
||||||
|
import styles from './Home.module.css';
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const operators = listOperatorNames();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
Stirling PDF
|
||||||
|
</h1>
|
||||||
|
<h2>
|
||||||
|
Your locally hosted one-stop-shop for all your PDF needs
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/**TODO: Search bar */}
|
||||||
|
|
||||||
|
<div className={styles.operator_container}>
|
||||||
|
{
|
||||||
|
operators.map((operator) => {
|
||||||
|
return (<OperatorCard key={operator} operatorInternalName={operator}></OperatorCard>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
15
client-tauri/src/pages/NoMatch.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Fragment } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
function NoMatch() {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h2>The Page you are trying to access does not exist.</h2>
|
||||||
|
<p>
|
||||||
|
<Link to="/">Go back home...</Link>
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NoMatch;
|
||||||
98
client-tauri/src/pages/Operators.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Fragment, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import { BaseSyntheticEvent, useState } from "react";
|
||||||
|
import { BuildForm } from "../components/BuildForm";
|
||||||
|
import { getOperatorByName, getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
|
import { PdfFile, RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
|
||||||
|
import { Action } from "@stirling-pdf/shared-operations/declarations/Action";
|
||||||
|
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
import InputField from "../components/fields/InputField";
|
||||||
|
|
||||||
|
|
||||||
|
function Operators() {
|
||||||
|
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const operatorInternalName = location.pathname.split("/")[2]; // /operators/<operatorInternalName>
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSchemaByName(operatorInternalName).then(schema => {
|
||||||
|
if(schema) {
|
||||||
|
setSchema(schema.schema);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
async function handleSubmit(e: BaseSyntheticEvent) {
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const values = Object.fromEntries(formData.entries());
|
||||||
|
let action: Action = {type: operatorInternalName, values: values};
|
||||||
|
|
||||||
|
|
||||||
|
// Validate PDF File
|
||||||
|
|
||||||
|
// Createing the pdffile before validation because joi cant handle it for some reason and I can't fix the underlying issue / I want to make progress, wasted like 3 hours on this already. TODO: The casting should be done in JoiPDFFileSchema.ts if done correctly...
|
||||||
|
const files = inputRef.current?.files;
|
||||||
|
const inputs: PdfFile[] = [];
|
||||||
|
|
||||||
|
if(files) {
|
||||||
|
const filesArray: File[] = Array.from(files as any);
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = filesArray[i];
|
||||||
|
if(file) {
|
||||||
|
inputs.push(new PdfFile(
|
||||||
|
file.name.replace(/\.[^/.]+$/, ""), // Strip Extension
|
||||||
|
new Uint8Array(await file.arrayBuffer()),
|
||||||
|
RepresentationType.Uint8Array
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Error("This should not happen. Contact maintainers.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationResults = schema.validate({input: inputs, values: action.values});
|
||||||
|
|
||||||
|
if(validationResults.error) {
|
||||||
|
console.error({error: "Validation failed", details: validationResults.error.message}, validationResults.error.stack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
action.values = validationResults.value.values;
|
||||||
|
const Operator = (await getOperatorByName(operatorInternalName))!;
|
||||||
|
|
||||||
|
const operation = new Operator(action);
|
||||||
|
operation.run(validationResults.value.input, (progress) => {
|
||||||
|
console.log("OperationProgress: " + progress.operationProgress, "CurFileProgress: " + progress.curFileProgress);
|
||||||
|
}).then(async pdfFiles => {
|
||||||
|
console.log("Result", pdfFiles);
|
||||||
|
|
||||||
|
for await (const pdfFile of (pdfFiles as PdfFile[])) {
|
||||||
|
var blob = new Blob([await pdfFile.uint8Array], {type: "application/pdf"});
|
||||||
|
var objectUrl = URL.createObjectURL(blob);
|
||||||
|
window.open(objectUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h1>{ schema?.describe().flags.label }</h1>
|
||||||
|
<h2>{ schema?.describe().flags.description }</h2>
|
||||||
|
|
||||||
|
<InputField ref={inputRef} />
|
||||||
|
|
||||||
|
<div id="values">
|
||||||
|
<BuildForm schemaDescription={schema?.describe()} onSubmit={handleSubmit}></BuildForm>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Operators;
|
||||||
5
client-tauri/src/pages/home.module.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.operator_container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
||||||
|
gap: 30px 30px;
|
||||||
|
}
|
||||||
151
client-tauri/src/root.css
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
:root {
|
||||||
|
--md-sys-color-primary: rgb(0 96 170);
|
||||||
|
--md-sys-color-surface-tint: rgb(0 96 170);
|
||||||
|
--md-sys-color-on-primary: rgb(255 255 255);
|
||||||
|
--md-sys-color-primary-container: rgb(80 163 255);
|
||||||
|
--md-sys-color-on-primary-container: rgb(0 20 43);
|
||||||
|
--md-sys-color-secondary: rgb(65 96 136);
|
||||||
|
--md-sys-color-on-secondary: rgb(255 255 255);
|
||||||
|
--md-sys-color-secondary-container: rgb(188 215 255);
|
||||||
|
--md-sys-color-on-secondary-container: rgb(32 65 103);
|
||||||
|
--md-sys-color-tertiary: rgb(88 90 138);
|
||||||
|
--md-sys-color-on-tertiary: rgb(255 255 255);
|
||||||
|
--md-sys-color-tertiary-container: rgb(151 153 205);
|
||||||
|
--md-sys-color-on-tertiary-container: rgb(7 9 55);
|
||||||
|
--md-sys-color-error: rgb(186 26 26);
|
||||||
|
--md-sys-color-on-error: rgb(255 255 255);
|
||||||
|
--md-sys-color-error-container: rgb(255 218 214);
|
||||||
|
--md-sys-color-on-error-container: rgb(65 0 2);
|
||||||
|
--md-sys-color-background: rgb(248 249 255);
|
||||||
|
--md-sys-color-on-background: rgb(24 28 34);
|
||||||
|
--md-sys-color-surface: rgb(248 249 255);
|
||||||
|
--md-sys-color-on-surface: rgb(24 28 34);
|
||||||
|
--md-sys-color-surface-variant: rgb(220 227 241);
|
||||||
|
--md-sys-color-on-surface-variant: rgb(64 71 83);
|
||||||
|
--md-sys-color-outline: rgb(112 119 132);
|
||||||
|
--md-sys-color-outline-variant: rgb(192 199 213);
|
||||||
|
--md-sys-color-shadow: rgb(0 0 0);
|
||||||
|
--md-sys-color-scrim: rgb(0 0 0);
|
||||||
|
--md-sys-color-inverse-surface: rgb(45 49 55);
|
||||||
|
--md-sys-color-inverse-on-surface: rgb(238 241 250);
|
||||||
|
--md-sys-color-inverse-primary: rgb(162 201 255);
|
||||||
|
--md-sys-color-primary-fixed: rgb(211 228 255);
|
||||||
|
--md-sys-color-on-primary-fixed: rgb(0 28 56);
|
||||||
|
--md-sys-color-primary-fixed-dim: rgb(162 201 255);
|
||||||
|
--md-sys-color-on-primary-fixed-variant: rgb(0 72 130);
|
||||||
|
--md-sys-color-secondary-fixed: rgb(211 228 255);
|
||||||
|
--md-sys-color-on-secondary-fixed: rgb(0 28 56);
|
||||||
|
--md-sys-color-secondary-fixed-dim: rgb(169 201 246);
|
||||||
|
--md-sys-color-on-secondary-fixed-variant: rgb(40 72 111);
|
||||||
|
--md-sys-color-tertiary-fixed: rgb(225 224 255);
|
||||||
|
--md-sys-color-on-tertiary-fixed: rgb(20 22 66);
|
||||||
|
--md-sys-color-tertiary-fixed-dim: rgb(193 194 248);
|
||||||
|
--md-sys-color-on-tertiary-fixed-variant: rgb(64 67 112);
|
||||||
|
--md-sys-color-surface-dim: rgb(215 218 227);
|
||||||
|
--md-sys-color-surface-bright: rgb(248 249 255);
|
||||||
|
--md-sys-color-surface-container-lowest: rgb(255 255 255);
|
||||||
|
--md-sys-color-surface-container-low: rgb(241 243 253);
|
||||||
|
--md-sys-color-surface-container: rgb(235 238 247);
|
||||||
|
--md-sys-color-surface-container-high: rgb(229 232 241);
|
||||||
|
--md-sys-color-surface-container-highest: rgb(223 226 235);
|
||||||
|
--md-nav-section-color-opacity: 1;
|
||||||
|
--md-nav-on-section-color-opacity: 1;
|
||||||
|
--md-nav-section-color-sign: rgba(25, 101, 212, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-sign: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-organize: rgba(120, 130, 255, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-organize: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-convert: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-security: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-other: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-advance: rgba(245, 84, 84, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-advance: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-image: rgba(212, 172, 25, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-image: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-word: rgba(61, 153, 245, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-word: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
--md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity));
|
||||||
|
--md-nav-on-section-color-ppt: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||||
|
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(html, .light-theme, .dark-theme), .tokens, :host {
|
||||||
|
--md-sys-color-surface-1:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.05) 5%);
|
||||||
|
--md-sys-color-surface-2:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.08) 5%);
|
||||||
|
--md-sys-color-surface-3:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.11) 5%);
|
||||||
|
--md-sys-color-surface-4:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.12) 5%);
|
||||||
|
--md-sys-color-surface-5:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.14) 5%);
|
||||||
|
--md-sys-icon-fill-0: 'FILL' 0, 'wght' 500;
|
||||||
|
--md-sys-icon-fill-1: 'FILL' 1, 'wght' 500;
|
||||||
|
--md-sys-state-hover-opacity:
|
||||||
|
color-mix(in srgb, var(--md-sys-color-primary), rgba(0, 0, 0, 0) 80%);
|
||||||
|
--md-sys-color-shadow: #000000;
|
||||||
|
--md-elevation-shadow-color-rgb: 0, 0, 0;
|
||||||
|
--md-elevation-shadow-color: var(--md-elevation-shadow-color-rgb);
|
||||||
|
--md-sys-elevation-0: 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.2), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
--md-sys-elevation-1: 0px 3px 1px -2px rgb(var(--md-elevation-shadow-color), 0.2), 0px 2px 2px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 5px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
--md-sys-elevation-2: 0px 2px 4px -1px rgb(var(--md-elevation-shadow-color), 0.2), 0px 4px 5px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 10px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
--md-sys-elevation-3: 0px 5px 5px -3px rgb(var(--md-elevation-shadow-color), 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
--md-sys-elevation-4: 0px 5px 5px -3px rgb(0, 0, 0 / 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
--md-sys-elevation-5: 0px 8px 10px -6px rgb(var(--md-elevation-shadow-color), 0.2), 0px 16px 24px 2px rgb(var(--md-elevation-shadow-color), 0.14), 0px 6px 30px 5px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
body, select, textarea {
|
||||||
|
background-color: var(--md-sys-color-surface);
|
||||||
|
color: var(--md-sys-color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 10px auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-family: var(--bs-font-sans-serif);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-symbols {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
9
client-tauri/src/utils/file-utils.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export function appendToFilename(inputPath: string, toAppend: string) {
|
||||||
|
const parts = inputPath.split(".");
|
||||||
|
if (parts.length > 1) {
|
||||||
|
parts[parts.length-2] = parts[parts.length-2] + toAppend;
|
||||||
|
}
|
||||||
|
return parts.join(".");
|
||||||
|
}
|
||||||
48
client-tauri/src/utils/libre-office-utils.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
import { readBinaryFile, writeBinaryFile, removeDir, BaseDirectory } from "@tauri-apps/api/fs";
|
||||||
|
import { PdfFile,RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
|
||||||
|
import { runShell, isTauriAvailable } from "./tauri-wrapper";
|
||||||
|
|
||||||
|
export async function fileToPdf(byteArray: Uint8Array, filename: string): Promise<PdfFile> {
|
||||||
|
const randUuid = crypto.randomUUID();
|
||||||
|
const tempDir = "StirlingPDF/"+randUuid;
|
||||||
|
const srcFile = tempDir+"/"+filename;
|
||||||
|
|
||||||
|
await writeBinaryFile(srcFile, byteArray);
|
||||||
|
await writeBinaryFile(srcFile, new Uint8Array([]), { dir: BaseDirectory.Temp });
|
||||||
|
|
||||||
|
const messageList: string[] = [];
|
||||||
|
await runShell("libreoffice-convert", ["--headless","--convert-to","pdf",srcFile,"--outdir",tempDir], (message, stream) => {
|
||||||
|
if (stream === "stdout") {
|
||||||
|
messageList.push(message);
|
||||||
|
}
|
||||||
|
console.debug(`${stream}, ${randUuid}: ${message}`);
|
||||||
|
});
|
||||||
|
const lastMessage = messageList[messageList.length-1];
|
||||||
|
const outputFilePath = lastMessage.split(" -> ")[1].split(".pdf")[0]+".pdf";
|
||||||
|
const outputFilePathSplit = outputFilePath.toString().split("[\\/]");
|
||||||
|
const outputFileName = outputFilePathSplit[outputFilePathSplit.length-1];
|
||||||
|
const outputBytes = await readBinaryFile(outputFilePath);
|
||||||
|
|
||||||
|
await removeDir(tempDir);
|
||||||
|
|
||||||
|
return new PdfFile(outputFileName, outputBytes, RepresentationType.Uint8Array);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isLibreOfficeInstalled() {
|
||||||
|
if (!isTauriAvailable()) return false;
|
||||||
|
|
||||||
|
const messageList: string[] = [];
|
||||||
|
try {
|
||||||
|
await runShell("libreoffice-version", ["--version"], (message, stream) => {
|
||||||
|
if (stream === "stdout") {
|
||||||
|
messageList.push(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log("messageList", messageList);
|
||||||
|
const result = messageList[0].match("LibreOffice ([0-9]+\.){4}.*");
|
||||||
|
return result ? true : false;
|
||||||
|
}
|
||||||
169
client-tauri/src/utils/tauri-wrapper.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
|
||||||
|
import { open, save } from "@tauri-apps/api/dialog";
|
||||||
|
import { readBinaryFile, writeBinaryFile } from "@tauri-apps/api/fs";
|
||||||
|
import { Command } from "@tauri-apps/api/shell";
|
||||||
|
|
||||||
|
export interface TauriBrowserFile {
|
||||||
|
name: string,
|
||||||
|
relativePath?: string,
|
||||||
|
data: Uint8Array,
|
||||||
|
getPath: ()=>string
|
||||||
|
}
|
||||||
|
function byteArrayToFile(byteArray: Uint8Array, filePath: string): TauriBrowserFile | null {
|
||||||
|
const separator = filePath.includes("\\") ? "\\" : "/";
|
||||||
|
|
||||||
|
const split = filePath.split(separator);
|
||||||
|
const fileName = split.pop();
|
||||||
|
const path = split.join(separator);
|
||||||
|
if (!fileName) return null;
|
||||||
|
return {
|
||||||
|
name: fileName,
|
||||||
|
data: byteArray,
|
||||||
|
relativePath: path?path:undefined,
|
||||||
|
getPath: ()=> (path?path:undefined) + separator + fileName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTauriAvailable() {
|
||||||
|
return (window as any).__TAURI_IPC__ ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [*] = Not available in browser
|
||||||
|
interface SelectFilesDialogOptions {
|
||||||
|
defaultPath?: string, // [*] the default path to open the dialog on
|
||||||
|
directory?: boolean, // should the dialog be a directory dialog
|
||||||
|
filters?: { // list of file type filters
|
||||||
|
name: string, // category name eg. 'Images'
|
||||||
|
extensions: string[] // list of extensions eg ['png', 'jpeg', 'jpg']
|
||||||
|
}[],
|
||||||
|
multiple?: boolean, // allow multiple selections
|
||||||
|
recursive?: boolean, // [*] If directory is true, indicates that it will be read recursively later. Defines whether subdirectories will be allowed on the scope or not.
|
||||||
|
title?: string // [*] the title of the dialog
|
||||||
|
}
|
||||||
|
export function openFiles(options: SelectFilesDialogOptions): Promise<TauriBrowserFile[] | null> {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
if (isTauriAvailable()) {
|
||||||
|
let selected = await open(options);
|
||||||
|
if (!selected) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(selected)) {
|
||||||
|
selected = [selected];
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: TauriBrowserFile[] = [];
|
||||||
|
for (const s of selected) {
|
||||||
|
const contents = await readBinaryFile(s);
|
||||||
|
const res = byteArrayToFile(contents, s);
|
||||||
|
if (res) {
|
||||||
|
files.push(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(files);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
if (options.directory) input.setAttribute("webkitdirectory", "");
|
||||||
|
if (options.filters) input.setAttribute("accept", options.filters.flatMap(f => f.extensions).map(ext => "."+ext).join(", "));
|
||||||
|
if (options.multiple) input.setAttribute("multiple", "");
|
||||||
|
|
||||||
|
input.onchange = async () => {
|
||||||
|
if (input.files && input.files.length) {
|
||||||
|
console.log("input.files", input.files);
|
||||||
|
const files: TauriBrowserFile[] = [];
|
||||||
|
for (const f of input.files) {
|
||||||
|
const contents = new Uint8Array(await f.arrayBuffer());
|
||||||
|
const res = byteArrayToFile(contents, f.name);
|
||||||
|
if (res) {
|
||||||
|
files.push(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(files);
|
||||||
|
}
|
||||||
|
input.onchange = null;
|
||||||
|
document.body.onfocus = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// detect the user clicking cancel
|
||||||
|
document.body.onfocus = () => {
|
||||||
|
setTimeout(()=>{ resolve(null) }, 200); // the timeout is needed because 'document.body.onfocus' is called before 'input.onchange'
|
||||||
|
};
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// [*] = Not available in browser
|
||||||
|
interface DownloadFilesDialogOptions {
|
||||||
|
defaultPath?: string, // the default path to open the dialog on
|
||||||
|
filters?: { // [*] list of file type filters
|
||||||
|
name: string, // category name eg. 'Images'
|
||||||
|
extensions: string[] // list of extensions eg ['png', 'jpeg', 'jpg']
|
||||||
|
}[],
|
||||||
|
title?: string // [*] the title of the dialog
|
||||||
|
}
|
||||||
|
export async function downloadFile(fileData: Uint8Array, options: DownloadFilesDialogOptions): Promise<undefined> {
|
||||||
|
if (isTauriAvailable()) {
|
||||||
|
const pathToSave = await save(options);
|
||||||
|
console.log("pathToSave", pathToSave);
|
||||||
|
if (pathToSave) {
|
||||||
|
await writeBinaryFile(pathToSave, fileData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const pdfBlob = new Blob([fileData], { type: "application/pdf" });
|
||||||
|
const url = URL.createObjectURL(pdfBlob);
|
||||||
|
const downloadOption = localStorage.getItem("downloadOption");
|
||||||
|
|
||||||
|
// ensure filename is not a path
|
||||||
|
const separator = options.defaultPath?.includes("\\") ? "\\" : "/";
|
||||||
|
const filename = options.defaultPath?.split(separator).pop();
|
||||||
|
const filenameToUse = filename ? filename : "edited.pdf";
|
||||||
|
|
||||||
|
if (downloadOption === "sameWindow") {
|
||||||
|
// Open the file in the same window
|
||||||
|
window.location.href = url;
|
||||||
|
} else if (downloadOption === "newWindow") {
|
||||||
|
// Open the file in a new window
|
||||||
|
window.open(url, "_blank");
|
||||||
|
} else {
|
||||||
|
// Download the file
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.href = url;
|
||||||
|
downloadLink.download = filenameToUse;
|
||||||
|
downloadLink.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dont forget to whitelist the Command in src-tauri/tauri.conf.json! (tauri.allowlist.shell.scope)
|
||||||
|
* @param commandName The name of the command to run. Must be defined in tauri.allowlist.shell.scope[].name
|
||||||
|
* @param args The args to pass into the command
|
||||||
|
* @param callback A callback function that is called when output is logged
|
||||||
|
* @returns A log of all the outputs logged
|
||||||
|
*/
|
||||||
|
export function runShell(commandName: string, args: string[], callback: (message: any, stream:"stdout"|"stderr"|"error") => void): Promise<void> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
|
const comm = new Command(commandName, args);
|
||||||
|
comm.on("close", data => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Command failed with exit code ${data.code} and signal ${data.signal}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
comm.on("error", error => { callback(error, "error") });
|
||||||
|
comm.stdout.on("data", line => { callback(line, "stdout") });
|
||||||
|
comm.stderr.on("data", line => { callback(line, "stderr") });
|
||||||
|
|
||||||
|
const child = await comm.spawn();
|
||||||
|
console.debug(`Started child process with pid: ${child.pid}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
1
client-tauri/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
29
client-tauri/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"declarations/*.d.ts",
|
||||||
|
"../shared-operations/declarations/*.d.ts"
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
client-tauri/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
52
client-tauri/vite.config.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import topLevelAwait from "vite-plugin-top-level-await";
|
||||||
|
import dynamicImport from 'vite-plugin-dynamic-import';
|
||||||
|
import compileTime from "vite-plugin-compile-time";
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [
|
||||||
|
// Thanks: https://stackoverflow.com/questions/74417822/how-can-i-use-buffer-process-in-vite-app
|
||||||
|
nodePolyfills({
|
||||||
|
include: [],
|
||||||
|
|
||||||
|
globals: {
|
||||||
|
Buffer: true, // can also be 'build', 'dev', or false
|
||||||
|
global: false,
|
||||||
|
process: true,
|
||||||
|
},
|
||||||
|
// Whether to polyfill `node:` protocol imports.
|
||||||
|
protocolImports: false,
|
||||||
|
}),
|
||||||
|
react(),
|
||||||
|
topLevelAwait({
|
||||||
|
// The export name of top-level await promise for each chunk module
|
||||||
|
promiseExportName: "__tla",
|
||||||
|
// The function to generate import names of top-level await promise in each chunk module
|
||||||
|
promiseImportName: i => `__tla_${i}`
|
||||||
|
}),
|
||||||
|
compileTime(),
|
||||||
|
dynamicImport(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'#pdfcpu': fileURLToPath(new URL("../shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client", import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
//
|
||||||
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false,
|
||||||
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
// 3. to make use of `TAURI_DEBUG` and other env variables
|
||||||
|
// https://tauri.app/v1/api/config#buildconfig.beforedevcommand
|
||||||
|
envPrefix: ["VITE_"],
|
||||||
|
base: '/', // relative paths sadly don't work with react router dom sub-dirs for some reason...
|
||||||
|
}));
|
||||||
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -1,310 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="99.537987mm"
|
|
||||||
height="95.209366mm"
|
|
||||||
viewBox="0 0 99.537987 95.209366"
|
|
||||||
version="1.1"
|
|
||||||
id="svg745"
|
|
||||||
xml:space="preserve"
|
|
||||||
inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)"
|
|
||||||
sodipodi:docname="stirling.svg"
|
|
||||||
inkscape:export-filename="stirling.png"
|
|
||||||
inkscape:export-xdpi="80"
|
|
||||||
inkscape:export-ydpi="80"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
|
||||||
id="namedview747"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.914906"
|
|
||||||
inkscape:cx="184.17193"
|
|
||||||
inkscape:cy="509.88845"
|
|
||||||
inkscape:window-width="2293"
|
|
||||||
inkscape:window-height="1387"
|
|
||||||
inkscape:window-x="122"
|
|
||||||
inkscape:window-y="25"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg745" /><defs
|
|
||||||
id="defs742"><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient72198"><stop
|
|
||||||
style="stop-color:#ccd6d7;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop72194" /><stop
|
|
||||||
style="stop-color:#0f3a3f;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop72196" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient71928"><stop
|
|
||||||
style="stop-color:#4b0005;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop71924" /><stop
|
|
||||||
style="stop-color:#8f000c;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop71926" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient71920"><stop
|
|
||||||
style="stop-color:#4b0005;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop71916" /><stop
|
|
||||||
style="stop-color:#8f000c;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop71918" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient69598"><stop
|
|
||||||
style="stop-color:#6a0007;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop69594" /><stop
|
|
||||||
style="stop-color:#b8000f;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop69596" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient46361"><stop
|
|
||||||
style="stop-color:#f7f6f8;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop46359" /><stop
|
|
||||||
style="stop-color:#b7b7b5;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop46357" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient40554"><stop
|
|
||||||
style="stop-color:#f4f2f4;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop40550" /><stop
|
|
||||||
style="stop-color:#57767b;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop40552" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient39095"><stop
|
|
||||||
style="stop-color:#285459;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop39093" /><stop
|
|
||||||
style="stop-color:#a6b6af;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop39091" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient36672"><stop
|
|
||||||
style="stop-color:#da453f;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop36668" /><stop
|
|
||||||
style="stop-color:#a60008;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop36670" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient19524"><stop
|
|
||||||
style="stop-color:#3a4b4f;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop19522" /><stop
|
|
||||||
style="stop-color:#617979;stop-opacity:0.97461927;"
|
|
||||||
offset="1"
|
|
||||||
id="stop19520" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient17996"><stop
|
|
||||||
style="stop-color:#401016;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop17994" /><stop
|
|
||||||
style="stop-color:#761f19;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop17992" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient15569"><stop
|
|
||||||
style="stop-color:#8ea8ad;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop15565" /><stop
|
|
||||||
style="stop-color:#e9e7eb;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop15567" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient15557"><stop
|
|
||||||
style="stop-color:#9b0e11;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop15553" /><stop
|
|
||||||
style="stop-color:#370707;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop15555" /></linearGradient><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient15557"
|
|
||||||
id="linearGradient15559"
|
|
||||||
x1="120.06672"
|
|
||||||
y1="63.25761"
|
|
||||||
x2="135.16347"
|
|
||||||
y2="78.078682"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient15569"
|
|
||||||
id="linearGradient15571"
|
|
||||||
x1="127.97037"
|
|
||||||
y1="101.66144"
|
|
||||||
x2="133.88971"
|
|
||||||
y2="104.77026"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient17996"
|
|
||||||
id="linearGradient17998"
|
|
||||||
x1="117.9284"
|
|
||||||
y1="86.055084"
|
|
||||||
x2="130.67392"
|
|
||||||
y2="76.945541"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient19524"
|
|
||||||
id="linearGradient19528"
|
|
||||||
x1="130.98172"
|
|
||||||
y1="82.402977"
|
|
||||||
x2="135.72115"
|
|
||||||
y2="86.45166"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient36672"
|
|
||||||
id="linearGradient36674"
|
|
||||||
x1="63.797714"
|
|
||||||
y1="74.81752"
|
|
||||||
x2="96.636673"
|
|
||||||
y2="120.29293"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient39095"
|
|
||||||
id="linearGradient39097"
|
|
||||||
x1="120.54506"
|
|
||||||
y1="124.76902"
|
|
||||||
x2="128.04152"
|
|
||||||
y2="126.0704"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient40554"
|
|
||||||
id="linearGradient40556"
|
|
||||||
x1="113.84585"
|
|
||||||
y1="114.04285"
|
|
||||||
x2="119.65858"
|
|
||||||
y2="128.50244"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient46361"
|
|
||||||
id="linearGradient46363"
|
|
||||||
x1="73.993439"
|
|
||||||
y1="114.13906"
|
|
||||||
x2="94.845322"
|
|
||||||
y2="71.247383"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient69598"
|
|
||||||
id="linearGradient69600"
|
|
||||||
x1="95.854446"
|
|
||||||
y1="114.66749"
|
|
||||||
x2="103.77842"
|
|
||||||
y2="120.1887"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient71920"
|
|
||||||
id="linearGradient71922"
|
|
||||||
x1="98.580376"
|
|
||||||
y1="87.186958"
|
|
||||||
x2="118.09738"
|
|
||||||
y2="101.19449"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient71928"
|
|
||||||
id="linearGradient71930"
|
|
||||||
x1="78.278267"
|
|
||||||
y1="97.433273"
|
|
||||||
x2="92.313202"
|
|
||||||
y2="104.33479"
|
|
||||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient72198"
|
|
||||||
id="linearGradient72200"
|
|
||||||
x1="125.76636"
|
|
||||||
y1="138.46817"
|
|
||||||
x2="123.3327"
|
|
||||||
y2="126.03291"
|
|
||||||
gradientUnits="userSpaceOnUse" /></defs><g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer4"
|
|
||||||
inkscape:label="background"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:insensitive="true"
|
|
||||||
transform="translate(-51.420144,-44.470286)"><rect
|
|
||||||
style="display:inline;fill:#ccd6d7;fill-opacity:1;stroke:none;stroke-width:4.13755;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.2"
|
|
||||||
id="rect72067"
|
|
||||||
width="99.481979"
|
|
||||||
height="94.999512"
|
|
||||||
x="51.476147"
|
|
||||||
y="44.680138" /></g><g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer5"
|
|
||||||
inkscape:label="shadow"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:insensitive="true"
|
|
||||||
transform="translate(-51.420144,-44.470286)"><path
|
|
||||||
style="display:inline;fill:url(#linearGradient72200);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 84.146049,134.73858 c 0,0 11.721038,2.48294 17.938661,2.91673 6.21763,0.43378 14.75251,0.59994 22.41237,-0.43379 8.01008,-1.081 13.19907,-2.22733 14.50043,-2.66112 1.30136,-0.43379 4.00784,-1.24297 4.15244,-2.25514 0.1446,-1.01217 -3.4703,-2.74733 -6.21763,-3.32571 -2.74732,-0.57838 -12.72444,-1.44596 -14.89337,-1.44596 -2.16894,0 -37.892901,7.20499 -37.892901,7.20499 z"
|
|
||||||
id="path72192"
|
|
||||||
sodipodi:nodetypes="cssssssc" /></g><g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer2"
|
|
||||||
inkscape:label="Origami"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:insensitive="true"
|
|
||||||
transform="translate(-51.420144,-44.470286)"><path
|
|
||||||
style="display:inline;fill:url(#linearGradient40556);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 84.460552,134.86721 35.165798,-6.85679 16.15467,-42.7383 z"
|
|
||||||
id="path960"
|
|
||||||
sodipodi:nodetypes="cccc" /><path
|
|
||||||
style="fill:url(#linearGradient15571);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 135.71493,85.428056 0.3984,45.049024 -9.66213,-20.46173 z"
|
|
||||||
id="path964"
|
|
||||||
sodipodi:nodetypes="cccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient39097);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 119.60518,128.00293 16.5337,2.48693 -9.68769,-20.5512 z"
|
|
||||||
id="path966"
|
|
||||||
sodipodi:nodetypes="cccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient15559);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 118.42209,57.022622 12.70423,-2.766809 -0.0785,25.087175 -12.43878,-13.376518 z"
|
|
||||||
id="path968"
|
|
||||||
sodipodi:nodetypes="ccccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient19528);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 135.72114,85.386768 -4.84219,-6.459493 0.0305,11.126604 z"
|
|
||||||
id="path970"
|
|
||||||
sodipodi:nodetypes="cccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient17998);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 119.10273,65.682415 11.96883,13.44935 -0.0899,10.819868 -11.88577,11.430427 z"
|
|
||||||
id="path972"
|
|
||||||
sodipodi:nodetypes="ccccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient36674);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="M 62.145635,130.15618 62.043392,65.435258 c 0,0 0.179321,-2.778132 1.89516,-4.036097 1.874923,-1.374597 4.341768,-1.894096 4.341768,-1.894096 l 50.91788,-11.349167 -0.0113,53.144272 -34.733274,33.56547 z"
|
|
||||||
id="path958"
|
|
||||||
sodipodi:nodetypes="ccsccccc" /></g><g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer3"
|
|
||||||
inkscape:label="Letter"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:insensitive="true"
|
|
||||||
transform="translate(-51.420144,-44.470286)"><path
|
|
||||||
style="display:inline;fill:url(#linearGradient69600);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 94.780881,91.406803 16.870379,17.074877 -23.723345,23.00249 -18.122131,-17.99816 5.497473,-2.80607 18.404054,-0.0511 2.35163,-8.23071 z"
|
|
||||||
id="path54894"
|
|
||||||
sodipodi:nodetypes="cccccccc" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient71930);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 72.440405,92.224764 16.15467,15.745686 4.089788,-6.79927 z"
|
|
||||||
id="path54892" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient71922);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 95.138739,84.965385 1.124691,-14.109776 22.92453,22.286787 0.008,8.164604 -3.28863,3.16649 z"
|
|
||||||
id="path54890"
|
|
||||||
sodipodi:nodetypes="cccccc"
|
|
||||||
inkscape:label="path54890" /><path
|
|
||||||
style="display:inline;fill:url(#linearGradient46363);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
d="m 95.138739,84.965385 h 1.226936 l -0.05112,-14.109776 c 0,0 -5.776827,-3.220709 -12.167126,-2.40275 -6.390296,0.817957 -8.151582,2.1248 -10.58233,4.396523 -1.90229,1.777838 -2.913974,3.527446 -3.987546,7.157132 -0.512646,1.733226 -0.281963,5.988892 0.613471,8.537436 0.664591,1.891528 2.453873,4.294281 4.958868,6.134686 2.662335,1.956002 8.281825,3.527443 8.281825,3.527443 0,0 5.134614,1.887351 5.572338,4.294281 0.308137,1.69437 -0.102243,3.22071 -1.635914,4.95887 -1.258314,1.42609 -3.62969,1.99377 -6.288054,1.07357 -2.658364,-0.92021 -6.139514,-3.85065 -7.106009,-4.90775 -0.817958,-0.89464 -2.820665,-3.06173 -2.890231,-4.294021 -0.01209,-0.214205 -1.229505,-0.0963 -1.229505,-0.0963 l -0.0723,14.256941 5.879073,2.24938 c 0,0 5.214483,1.78929 8.946415,1.43143 3.731934,-0.35786 7.617235,-0.51122 11.604778,-5.16336 3.987542,-4.65213 3.680812,-12.831715 2.147141,-15.899056 -1.533673,-3.067344 -3.561212,-6.138812 -10.480087,-8.281826 -5.776829,-1.789283 -7.872846,-3.01622 -8.128458,-4.396524 -0.255611,-1.380305 0.0091,-4.253646 2.760607,-5.214481 3.220711,-1.124693 5.623462,-0.05112 7.05489,1.12469 1.431425,1.175817 5.572339,5.623462 5.572339,5.623462 z"
|
|
||||||
id="path46355"
|
|
||||||
sodipodi:nodetypes="cccssssscssssscccssssssscc" /></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
240
gradlew
vendored
@@ -1,240 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
91
gradlew.bat
vendored
@@ -1,91 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
Before Width: | Height: | Size: 92 KiB |
11846
package-lock.json
generated
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "stirling-pdf",
|
||||||
|
"private": "true",
|
||||||
|
"workspaces": [
|
||||||
|
"client-tauri",
|
||||||
|
"server-node",
|
||||||
|
"shared-operations"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev-all": "concurrently --names \"node,tauri\" -c \"red.bold,cyan.bold\" --kill-others \"npm run -w server-node dev\" \"npm run -w client-tauri dev\"",
|
||||||
|
"update-all-dependencies": "npm i --workspace=stirling-pdf --workspace=server-node --workspace=client-tauri --workspace=shared-operation",
|
||||||
|
"update-backend-dependencies": "npm i --workspace=server-node --workspace=shared-operation",
|
||||||
|
"update-frontend-dependencies": "npm i --workspace=client-tauri --workspace=shared-operation"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||||
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
|
"eslint": "^8.56.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=7.24.2"
|
||||||
|
},
|
||||||
|
"engineStrict": true
|
||||||
|
}
|
||||||
7
server-node/.env
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
VITE_JOBS_ENABLED=True
|
||||||
|
VITE_JOBS_DIR="./jobs"
|
||||||
|
|
||||||
|
VITE_AUTH_ENABLED=True
|
||||||
|
VITE_AUTH_SESSION_SECRET="default-secret"
|
||||||
|
|
||||||
|
VITE_SEQUELIZE_LOGGING=False
|
||||||
19
server-node/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Use an existing image as a base
|
||||||
|
FROM node:22.2.0-alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --legacy-peer-deps
|
||||||
|
|
||||||
|
COPY ./dist ./dist
|
||||||
|
|
||||||
|
RUN apk del git
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD [ "node", "./dist/index.js" ]
|
||||||
10
server-node/Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
vite-build:
|
||||||
|
npx tsc
|
||||||
|
npx vite build
|
||||||
|
|
||||||
|
vite-dockerize:
|
||||||
|
make vite-build
|
||||||
|
bash -c "cp ../package-lock.json ./"
|
||||||
|
docker build . -t stirling-pdf-backend:latest
|
||||||
|
bash -c "rm ./package-lock.json"
|
||||||
|
docker image prune
|
||||||
7
server-node/declarations/ExpressUser.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
type UserModel = import("../src/auth/user/user-model").User;
|
||||||
|
|
||||||
|
declare namespace Express {
|
||||||
|
interface User extends UserModel {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
9
server-node/declarations/ProcessEnv.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
export interface ProcessEnv {
|
||||||
|
JOBS_ENABLED: "True" | "False",
|
||||||
|
JOBS_DIR: string,
|
||||||
|
AUTH_ENABLED: "True" | "False",
|
||||||
|
AUTH_SESSION_SECRET: string,
|
||||||
|
SEQUELIZE_LOGGING: "True" | "False"
|
||||||
|
}
|
||||||
|
}
|
||||||
70
server-node/package.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "@stirling-pdf/server-node",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"exports": "./dist/index.js",
|
||||||
|
"imports": {
|
||||||
|
"#pdfcpu": "@stirling-pdf/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"canvas": "^2.11.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"vite:dev": "vite",
|
||||||
|
"vite:preview-prod": "make vite-build && node ./dist/index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-alias": "^5.1.0",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@stirling-tools/joi": "github:stirling-tools/joi",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/multer": "^1.4.10",
|
||||||
|
"@wasmer/wasmfs": "^0.12.0",
|
||||||
|
"archiver": "^6.0.1",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-fileupload": "^1.4.2",
|
||||||
|
"express-session": "^1.18.0",
|
||||||
|
"i18next": "^23.14.0",
|
||||||
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
|
"joi": "^17.11.0",
|
||||||
|
"jsqr": "^1.4.0",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-headerapikey": "^1.2.2",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pdfjs-dist": "^4.5.136",
|
||||||
|
"react-material-symbols": "^4.4.0",
|
||||||
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
|
"rollup-plugin-dynamic-import-variables": "^1.1.0",
|
||||||
|
"sequelize": "^6.37.3",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"toml": "^3.0.0",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"vite": "^5.4.2",
|
||||||
|
"vite-plugin-compile-time": "^0.2.1",
|
||||||
|
"vite-plugin-dynamic-import": "^1.5.0",
|
||||||
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
|
"vite-plugin-top-level-await": "^1.4.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
|
||||||
|
"@rollup/plugin-run": "^3.0.2",
|
||||||
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
|
"@types/express-session": "^1.18.0",
|
||||||
|
"@types/passport-local": "^1.0.38",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
|
"pkgroll": "^2.0.1",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite-plugin-node": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
server-node/src/auth/apikey/apikey-controller.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Error as SequelizeError } from "sequelize";
|
||||||
|
import { User } from "../user/user-model";
|
||||||
|
import { APIKey } from "./apikey-model";
|
||||||
|
|
||||||
|
export function findOne(params: {apikey?: string}, cb: (err: Error | null, apikey?: APIKey | undefined, info?: Object | undefined) => void): undefined {
|
||||||
|
const query: any = params;
|
||||||
|
|
||||||
|
for (let key in query) {
|
||||||
|
if (query[key] === undefined) {
|
||||||
|
delete query[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Object.keys(query).length == 0) {
|
||||||
|
cb(new Error("You need to provide at least one argument."), undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
APIKey.findOne({
|
||||||
|
where: query,
|
||||||
|
include: APIKey.associations.User
|
||||||
|
}).then(apikey => {
|
||||||
|
if(apikey)
|
||||||
|
cb(null, apikey);
|
||||||
|
else
|
||||||
|
cb(null, undefined, { message: "The requested apikey was not found."});
|
||||||
|
}).catch(e =>
|
||||||
|
cb(e, undefined)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAPIKey(user: User | undefined): Promise<APIKey | undefined> {
|
||||||
|
if(!user) throw new Error("User was undefined");
|
||||||
|
|
||||||
|
const apikey = crypto.randomUUID(); // TODO: Is this secure enough?
|
||||||
|
const apikeyEntry = await user.createAPIKey({ apikey: apikey });
|
||||||
|
|
||||||
|
return apikeyEntry;
|
||||||
|
}
|
||||||
17
server-node/src/auth/apikey/apikey-model.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { User } from '../user/user-model';
|
||||||
|
import {
|
||||||
|
Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ForeignKey,
|
||||||
|
} from 'sequelize';
|
||||||
|
|
||||||
|
export class APIKey extends Model<InferAttributes<APIKey>, InferCreationAttributes<APIKey>> {
|
||||||
|
declare id: CreationOptional<number>;
|
||||||
|
declare apikey: string;
|
||||||
|
|
||||||
|
declare UserId: ForeignKey<User['id']>;
|
||||||
|
|
||||||
|
// `User` is an eagerly-loaded association.
|
||||||
|
declare User?: NonAttribute<User>;
|
||||||
|
|
||||||
|
declare createdAt: CreationOptional<Date>;
|
||||||
|
declare updatedAt: CreationOptional<Date>;
|
||||||
|
}
|
||||||
22
server-node/src/auth/auth-controller.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import passport from "passport";
|
||||||
|
import session from "express-session";
|
||||||
|
import { initialize } from "./passport-config";
|
||||||
|
import auth from "../routes/auth/auth-controller";
|
||||||
|
import { Express } from "express";
|
||||||
|
|
||||||
|
export function connect(app: Express) {
|
||||||
|
app.use(session({
|
||||||
|
secret: import.meta.env.VITE_SESSION_SECRET || "default-secret",
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.authenticate(['headerapikey', 'session'], {
|
||||||
|
session: false, // Only set a session on the login request.
|
||||||
|
}));
|
||||||
|
|
||||||
|
initialize(passport);
|
||||||
|
|
||||||
|
app.use("/auth", auth);
|
||||||
|
}
|
||||||
15
server-node/src/auth/authenticationMiddleware.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
|
export function isAuthorized(req: Request, res: Response, next: NextFunction) {
|
||||||
|
if(import.meta.env.VITE_AUTH_ENABLED === "False" || req.user) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(403).json({"Error": "Authentication failed."});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function whenAuthIsEnabled(req: Request, res: Response, next: NextFunction) {
|
||||||
|
if(import.meta.env.VITE_AUTH_ENABLED === "True") {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(403).json({"Error": "Authentication is not enabled."});
|
||||||
|
}
|
||||||
55
server-node/src/auth/passport-config.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Strategy as LocalStrategy} from "passport-local";
|
||||||
|
import { HeaderAPIKeyStrategy as HeaderAPIKeyStrategy } from "passport-headerapikey";
|
||||||
|
|
||||||
|
import * as User from "./user/user-controller";
|
||||||
|
import * as APIKey from "./apikey/apikey-controller";
|
||||||
|
|
||||||
|
export function initialize(passport: typeof import("passport")) {
|
||||||
|
passport.use("local", new LocalStrategy(
|
||||||
|
function(username, password, done) {
|
||||||
|
User.findOne({username: username}, function (err, user) {
|
||||||
|
if (err) {
|
||||||
|
return done(err, false);
|
||||||
|
}
|
||||||
|
if (!user) {
|
||||||
|
return done(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
User.verifyPassword(user, password, (error, success) => {
|
||||||
|
if(error) return done(error, false);
|
||||||
|
|
||||||
|
if(!success) return done(null, false);
|
||||||
|
|
||||||
|
return done(null, user)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
passport.use(new HeaderAPIKeyStrategy(
|
||||||
|
{ header: 'Authorization', prefix: 'Bearer ' },
|
||||||
|
false,
|
||||||
|
function(apikey, done) {
|
||||||
|
APIKey.findOne({ apikey: apikey }, function (err, apikey, info) {
|
||||||
|
if (err) {
|
||||||
|
return done(err, false);
|
||||||
|
}
|
||||||
|
if (!apikey) {
|
||||||
|
return done(null, false, info);
|
||||||
|
}
|
||||||
|
return done(null, apikey.User);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
passport.serializeUser((user, done) => {
|
||||||
|
done(null, user.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
passport.deserializeUser((id: number, done) => {
|
||||||
|
User.findOne({ id: id }, function (err, user) {
|
||||||
|
done(err, user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
73
server-node/src/auth/user/user-controller.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Error as SequelizeError, Op } from "sequelize";
|
||||||
|
import { Password, User } from "./user-model";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
type PickOne<T, F extends keyof T> = Pick<T, F> & { [K in keyof Omit<T, F>]?: never };
|
||||||
|
|
||||||
|
export function findOne(params: {id?: number, username?: string}, cb: (err: Error | null, apikey?: User | undefined, info?: Object | undefined) => void): undefined {
|
||||||
|
const query: any = params;
|
||||||
|
|
||||||
|
for (let key in query) {
|
||||||
|
if (query[key] === undefined) {
|
||||||
|
delete query[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Object.keys(query).length == 0) {
|
||||||
|
cb(new Error("You need to provide at least one argument."), undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
User.findOne({
|
||||||
|
where: query
|
||||||
|
}).then(user => {
|
||||||
|
if(user)
|
||||||
|
cb(null, user);
|
||||||
|
else
|
||||||
|
cb(null, undefined, { message: "The requested user was not found."});
|
||||||
|
}).catch(e =>
|
||||||
|
cb(e, undefined)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Allow other authentication methods
|
||||||
|
export function createUser(params: { username: string, password: string }, cb: (err: SequelizeError | null, user: User | null) => void ) {
|
||||||
|
User.create({ username: params.username }).then(async (user) => {
|
||||||
|
const salt = crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
|
hashPassword(params.password, salt, async (err, derivedKey) => {
|
||||||
|
if(err || !derivedKey) {
|
||||||
|
return cb(err, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPassword(await Password.create({
|
||||||
|
password: derivedKey,
|
||||||
|
salt: salt
|
||||||
|
})).then(password => {
|
||||||
|
cb(null, user as any as User);
|
||||||
|
}).catch(e => {
|
||||||
|
cb(e, null);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}).catch(e =>
|
||||||
|
cb(e, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyPassword(user: User, password: string, cb: (error: Error | null, success: boolean | null) => void) {
|
||||||
|
const passwordRecord = await user.getPassword();
|
||||||
|
if(!passwordRecord) {
|
||||||
|
return cb(new Error("This user does not have a password set!"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashPassword(password, passwordRecord.salt, (err, derivedKey) => {
|
||||||
|
if(err) return cb(err, null);
|
||||||
|
return cb(null, passwordRecord.password == derivedKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashPassword(password: string, salt: string, cb: (err: Error | null, derivedKey: string | null) => void) {
|
||||||
|
crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {
|
||||||
|
if (err) return cb(err, null);
|
||||||
|
cb(null, derivedKey.toString('hex'));
|
||||||
|
});
|
||||||
|
}
|
||||||