Compare commits
42 Commits
8a717f38fd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d5c390350 | ||
|
|
e13d06d4a7 | ||
|
|
547411ba03 | ||
|
|
cdd26e0bc3 | ||
|
|
f7c73a0c5a | ||
|
|
8e74848397 | ||
|
|
8704aee82e | ||
|
|
594ac1fa00 | ||
|
|
2ee90cd0d7 | ||
|
|
d42bab5b19 | ||
|
|
c3a87ceee6 | ||
|
|
6f2d373292 | ||
|
|
a37206d6a4 | ||
|
|
6bd75bf93f | ||
|
|
5bc24a32d5 | ||
|
|
a9233926e5 | ||
|
|
90235d2788 | ||
|
|
da316a9351 | ||
|
|
e6727daf8e | ||
|
|
d6508c739d | ||
|
|
856c401c06 | ||
|
|
4da77c95d1 | ||
|
|
98742107b2 | ||
| b9eb5e8bd4 | |||
|
|
5c6a8dfba2 | ||
|
|
c30d69d205 | ||
|
|
56d3468889 | ||
|
|
26ea274023 | ||
|
|
0c96d04326 | ||
|
|
352540a3b1 | ||
|
|
7d828a7c3b | ||
|
|
93f2c59997 | ||
|
|
09e1e74af2 | ||
|
|
6b1805c0ac | ||
|
|
8a69b7fbaf | ||
|
|
ec08eea910 | ||
|
|
46f44901e8 | ||
|
|
4a6e74aada | ||
|
|
f13e8711a7 | ||
|
|
d6d3f46c78 | ||
|
|
ac16b1ea7b | ||
|
|
b4c5daa212 |
7
.gitignore
vendored
@@ -2,3 +2,10 @@ app/venv/
|
||||
app/files/
|
||||
app/pwfile.json
|
||||
app/dest
|
||||
app.log
|
||||
init.log
|
||||
app/__pycache__/
|
||||
mariadb/*
|
||||
unizeug
|
||||
.mypy_cache
|
||||
.nvim
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# remote_path="/srv/http/"
|
||||
# remote_path="dev@10.0.0.25:/var/www/html/"
|
||||
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM python:3.13-rc-alpine
|
||||
WORKDIR /usr/src/
|
||||
COPY requirements.txt /usr/src/requirements.txt
|
||||
COPY entrypoint.sh /usr/src/entrypoint.sh
|
||||
RUN apk add --no-cache \
|
||||
gcc \
|
||||
g++ \
|
||||
musl-dev \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
openssl-dev \
|
||||
cargo \
|
||||
make \
|
||||
mariadb-connector-c-dev \
|
||||
jpeg-dev \
|
||||
zlib-dev \
|
||||
freetype-dev \
|
||||
lcms2-dev \
|
||||
openjpeg-dev \
|
||||
tiff-dev \
|
||||
tk-dev \
|
||||
tcl-dev \
|
||||
libwebp-dev \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-data-deu
|
||||
RUN python -m ensurepip --upgrade
|
||||
RUN pip install setuptools wheel
|
||||
RUN pip install -r requirements.txt
|
||||
WORKDIR /python
|
||||
CMD /bin/sh /usr/src/entrypoint.sh
|
||||
# ENTRYPOINT ["/usr/src/entrypoint.sh"]
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
app/graphics/empty.pdf
Normal file
65
app/graphics/empty.svg
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="empty.svg"
|
||||
inkscape:export-filename="empty.pdf"
|
||||
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">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.85670232"
|
||||
inkscape:cx="566.70793"
|
||||
inkscape:cy="542.77896"
|
||||
inkscape:window-width="1504"
|
||||
inkscape:window-height="931"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 48.79125,72.679957 0.7721,56.517593 27.949953,0.7721 0.30884,-5.25027 -22.545267,-0.7721 -0.772099,-8.33866 22.390848,-0.15442 0.30884,-5.55911 -23.008528,0.46326 -0.926518,-30.72951 23.471787,-0.308837 -0.61768,-7.875403 z"
|
||||
id="path1" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 88.01384,130.27848 -1.389777,-57.444103 21.309907,25.942499 24.39831,-25.63366 2.00745,56.672004 -5.86794,0.30884 -0.61768,-43.237495 -19.30246,18.993615 -18.067096,-21.155491 1.853035,44.936111 z"
|
||||
id="path2" />
|
||||
<path
|
||||
id="path3"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 158.99066,69.906981 c -5.69894,0.0074 -10.75335,1.691886 -10.75335,1.691886 l 0.61805,58.525113 h 8.02949 l -0.46303,-28.25874 c 0,0 18.53005,6.02224 18.37563,-16.523026 -0.0869,-12.681712 -8.47959,-15.444734 -15.80679,-15.435233 z m 1.53117,5.377966 c 2.78363,-0.0438 5.81711,1.425874 6.09162,7.895642 0.48035,11.32111 -8.86154,11.068264 -10.18077,10.976591 0.1175,0.12788 -0.16537,-0.01292 -0.16537,-0.01292 0,0 0.0655,0.006 0.16537,0.01292 -0.004,-0.0048 -0.005,-0.0073 -0.0109,-0.01292 -0.15442,-0.154419 -0.61753,-17.603576 -0.61753,-17.603576 0,0 2.2614,-1.217094 4.71754,-1.255738 z" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 45.548438,148.65442 h 34.126741 v 6.48562 H 64.387633 l 0.308841,37.83282 -6.331207,-0.30884 -0.308837,-37.83281 H 45.239599 Z"
|
||||
id="path4" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 98.668793,148.34558 105,148.5 l 8.18424,19.61129 12.66241,-19.76571 h 8.33866 l -29.49415,47.25241 -7.103303,-0.46326 12.044733,-22.08201 z"
|
||||
id="path5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/graphics/greeting.pdf
Normal file
582
app/graphics/greeting.svg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
app/graphics/text2.pdf
Normal file
BIN
app/graphics/unsupported.pdf
Normal file
89
app/graphics/unsupported.svg
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="unsupported.svg"
|
||||
inkscape:export-filename="unsupported.pdf"
|
||||
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">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.60578002"
|
||||
inkscape:cx="149.39416"
|
||||
inkscape:cy="642.14729"
|
||||
inkscape:window-width="1504"
|
||||
inkscape:window-height="931"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 9.4195981,19.765714 9.1107587,86.16616 45.708215,86.320579 45.862634,19.920134 38.759331,20.074553 39.22259,80.607052 14.361026,80.452634 v -60.68692 z"
|
||||
id="path1" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 57.135267,86.320579 -0.926518,-66.400445 11.118215,0.154419 30.11183,56.672009 -1.080937,-58.061785 8.493083,-0.15442 0.46326,66.400445 -11.890318,0.30884 -31.501606,-60.5325 0.463259,62.076696 z"
|
||||
id="path2" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 45.553793,98.674152 C 43.08308,98.82857 10.037277,98.210893 10.037277,98.210893 c 0,0 -1.0809379,31.810447 -0.4632594,31.656027 0.6176784,-0.15442 27.0234374,-0.15442 27.0234374,-0.15442 l 0.154418,27.48669 -26.869016,-0.77209 0.15442,6.48562 31.964866,0.15442 c 0,0 0.926518,-39.22259 0,-39.22259 -0.926518,0 -26.714599,-0.46326 -26.714599,-0.46326 l -1.235357,-21.30991 31.501606,0.61768 z"
|
||||
id="path3" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 56.363168,98.82857 c 0,0.772099 0.926518,65.16509 0.926518,65.16509 l 38.296072,0.15442 0.154418,-66.246028 -7.875399,-0.154418 1.080937,60.532496 -24.861562,-0.15442 -0.926518,-58.988299 z"
|
||||
id="path4" />
|
||||
<path
|
||||
id="path5"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 109.7318 98.26377 C 107.54339 98.239641 106.08655 98.365055 106.08655 98.365055 L 106.08655 165.22919 L 113.4985 164.61114 L 112.88097 124.61668 C 112.88097 124.61668 129.7124 123.5357 129.55798 109.94678 C 129.44217 99.755078 116.29705 98.336154 109.7318 98.26377 z M 113.65301 103.46139 C 113.65301 103.46139 122.45454 104.54227 122.45454 111.95441 C 122.45454 119.36655 113.65301 118.90334 113.65301 118.90334 L 113.65301 103.46139 z " />
|
||||
<path
|
||||
id="path6"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 139.54187 99.033748 C 138.14225 99.050713 137.27896 99.137618 137.27896 99.137618 C 137.27896 100.68182 137.7425 165.38319 137.7425 165.38319 L 146.54403 165.07468 L 146.39003 132.02863 C 146.39003 132.02863 160.90518 134.34533 161.36844 115.50613 C 161.74484 100.19928 145.60687 98.960233 139.54187 99.033748 z M 146.85305 106.08655 C 146.85305 106.08655 155.19184 107.63034 155.6551 116.58668 C 156.11836 125.54302 146.85305 125.85227 146.85305 125.85227 C 146.85305 125.85227 147.00747 124.61691 146.85305 106.08655 z " />
|
||||
<path
|
||||
id="path7"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 8.0300008 183.60481 L 3.8607463 250.77746 L 43.855204 251.08648 L 43.855204 184.06835 L 8.0300008 183.60481 z M 37.67832 191.17128 L 35.825203 244.44606 L 11.272697 245.21862 L 14.515393 191.32579 C 16.522849 191.63463 37.67832 191.17128 37.67832 191.17128 z " />
|
||||
<path
|
||||
id="path8"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 59.1783 184.00479 C 55.307442 184.00008 52.502738 184.37686 52.502738 184.37686 L 52.657251 250.62295 L 60.532739 250.62295 L 60.532739 218.19495 L 77.673295 248.92434 L 86.474825 248.46132 L 61.458781 210.01095 C 61.458781 210.01095 82.151296 214.79777 81.688037 197.65719 C 81.369547 185.87304 67.694188 184.01516 59.1783 184.00479 z M 61.767806 189.16416 C 61.767806 189.16416 72.113748 193.02454 73.503524 198.42923 C 74.893301 203.83392 61.304785 204.14258 61.304785 204.14258 L 61.767806 189.16416 z " />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 94.96808,185.61241 41.07562,-0.15442 0.61768,8.33866 -17.60384,-0.30884 0.30884,53.89245 h -7.72098 l -0.7721,-54.51013 -16.677319,-0.15442 z"
|
||||
id="path9" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="m 142.22049,185.45799 0.46326,62.69437 h 26.40576 l -0.30884,-6.79446 -20.53782,-0.61768 -0.46325,-23.16295 20.69223,0.46326 v -8.33866 h -19.76572 l -0.15442,-19.30245 19.14804,-0.7721 0.15442,-5.40469 z"
|
||||
id="path10" />
|
||||
<path
|
||||
id="path11"
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0.407001;stroke-opacity:0.247707"
|
||||
d="M 186.10389 183.67096 C 184.99641 183.67051 184.24207 183.78828 184.06835 183.75933 C 183.14183 183.60491 176.50189 184.22286 176.50189 184.22286 L 176.65588 248.61583 L 182.83277 248.61583 C 182.83277 248.61583 201.20876 243.82841 201.82644 211.86355 C 202.32831 185.8921 190.90296 183.67292 186.10389 183.67096 z M 182.67826 190.39923 C 182.67826 190.39923 193.79653 191.17146 193.95095 212.94462 C 194.10537 234.71779 182.83277 242.28444 182.83277 242.28444 L 182.67826 190.39923 z " />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
164
app/index.html
Normal file
@@ -0,0 +1,164 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<title>Unizeug uploader</title>
|
||||
<link rel="stylesheet" href="static/style.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js"></script>
|
||||
<script src="static/app.js" defer></script>
|
||||
<script src="static/autocomplete.js" defer></script>
|
||||
<script src="static/dynhide.js" defer></script>
|
||||
<script src="static/filedrop.js" defer></script>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/favicon/favicon-96x96.png"
|
||||
sizes="96x96"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicon/apple-touch-icon.png"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-title" content="Unizeug" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- The Modal -->
|
||||
<div id="loading" class="modal">
|
||||
<!-- Modal content -->
|
||||
<div class="loading-content">
|
||||
<!-- <span class="close">×</span> -->
|
||||
<div class="loader"></div>
|
||||
<p id="upload_status" class="upload_status_text">Uploading</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- main -->
|
||||
<div class="main">
|
||||
<div class="left" id="controldiv">
|
||||
<div id="fileupload">
|
||||
<form id="uploadform" enctype="multipart/form-data">
|
||||
<div class="filetop">
|
||||
<!-- <label for="filepicker">Choose a file</label> -->
|
||||
<input
|
||||
type="file"
|
||||
name="files"
|
||||
id="filepicker"
|
||||
multiple
|
||||
placeholder="Drop File"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" id="upload" method="POST" class="fileupload">
|
||||
Upload
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="submitdiv">
|
||||
<form id="submitform" ,onsubmit="submitFile(event)">
|
||||
<label for="lva">Lehrveranstaltung:</label>
|
||||
<div class="autocomplete">
|
||||
<input
|
||||
type="text"
|
||||
id="lva"
|
||||
name="lva"
|
||||
placeholder="Lehrveranstaltung"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<!-- <br /> -->
|
||||
<label for="prof">Vortragende*r:</label>
|
||||
<div class="autocomplete">
|
||||
<input
|
||||
type="text"
|
||||
id="prof"
|
||||
name="prof"
|
||||
placeholder="Vortragende*r"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<!-- <br /> -->
|
||||
<label for="name">Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="fname"
|
||||
placeholder="Prüfung"
|
||||
/><br />
|
||||
<label for="sem">Semester:</label>
|
||||
<input type="text" id="sem" name="sem" placeholder="2024W" /><br />
|
||||
<input
|
||||
type="radio"
|
||||
id="pruefung"
|
||||
name="stype"
|
||||
value="0"
|
||||
checked="checked"
|
||||
/>
|
||||
<label for="pruefung">Prüfung</label><br />
|
||||
<input type="radio" id="klausur" name="stype" value="1" />
|
||||
<label for="klausur">Klausur</label><br />
|
||||
<input type="radio" id="uebung" name="stype" value="2" />
|
||||
<label for="uebung">Übung</label><br />
|
||||
<input type="radio" id="labor" name="stype" value="3" />
|
||||
<label for="labor">Labor</label><br />
|
||||
<input type="radio" id="unterlagen" name="stype" value="4" />
|
||||
<label for="unterlagen">Unterlagen</label><br />
|
||||
<input type="radio" id="zusammenfassungen" name="stype" value="5" />
|
||||
<label for="zusammenfassungen">Zusammenfassung</label><br />
|
||||
<input type="radio" id="multimedia" name="stype" value="6" />
|
||||
<label for="multimedia">Multimedia</label><br />
|
||||
<br />
|
||||
<div id="subcatdiv">
|
||||
<label for="subcat">Veranstaltung</label>
|
||||
<div class="autocomplete">
|
||||
<input
|
||||
type="text"
|
||||
id="subcat"
|
||||
name="subcat"
|
||||
placeholder="Klausur 1"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="datediv">
|
||||
<label for="date">Datum</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
name="ex_date"
|
||||
placeholder="Drop File"
|
||||
/><br />
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ocr"
|
||||
id="sec_censor"
|
||||
value="True"
|
||||
/><label for="sec_censor">OCR</label><br /><br />
|
||||
<button type="submit" id="send">Senden</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" id="rightdiv">
|
||||
<div class="buttons" id="buttonsdiv">
|
||||
<button id="prev">Prev</button><button id="next">Next</button>
|
||||
<div>
|
||||
<span id="npage"></span>
|
||||
<span>/</span>
|
||||
<span id="npages"></span>
|
||||
</div>
|
||||
<button id="clr">Clear Page</button><button id="ca">Claer All</button>
|
||||
</div>
|
||||
<div id="cnvdiv">
|
||||
<div class="stack" id="cnvcont">
|
||||
<canvas id="cnv"></canvas>
|
||||
<canvas id="drw_cnv"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
120
app/init.py
@@ -1,10 +1,17 @@
|
||||
import paramiko
|
||||
from os.path import isdir
|
||||
from stat import S_ISDIR, S_ISREG
|
||||
import re
|
||||
import pathlib
|
||||
import os
|
||||
|
||||
# from base64 import decodebytes
|
||||
import json
|
||||
import mariadb
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import schedule
|
||||
import time
|
||||
import pytz
|
||||
|
||||
CATEGORIES = [
|
||||
"Prüfungen",
|
||||
@@ -16,9 +23,24 @@ CATEGORIES = [
|
||||
"Multimedia",
|
||||
]
|
||||
SUBCAT_CATEGORIES = ["Klausuren", "Übungen", "Labore"]
|
||||
unizeug_path = "/mnt/save/daten/Unizeug/"
|
||||
unizeug_path = os.environ.get("UNIZEUG_PATH", "./unizeug")
|
||||
APP_ROOT_PATH = Path(os.environ.get("APP_ROOT_PATH", "./app"))
|
||||
FILES_IN_PROGRESS = APP_ROOT_PATH / "files/"
|
||||
log = logging.getLogger(__name__)
|
||||
logging.basicConfig(
|
||||
filename="init.log",
|
||||
level=logging.INFO,
|
||||
format="[%(asctime)s, %(filename)s:%(lineno)s -> %(funcName)10s() ]%(levelname)s: %(message)s",
|
||||
)
|
||||
debug = log.debug
|
||||
info = log.info
|
||||
error = log.error
|
||||
|
||||
db = mariadb.connect(
|
||||
host="localhost", user="wildserver", password="DBPassword", database="Unizeug"
|
||||
host=os.environ.get("DB_HOST", "db"),
|
||||
user=os.environ.get("DB_USER", "user"),
|
||||
password=os.environ.get("DB_PASSWORD", "DBPASSWORD"),
|
||||
database=os.environ.get("DB_DATABASE", "unizeug"),
|
||||
)
|
||||
c = db.cursor()
|
||||
try:
|
||||
@@ -49,36 +71,58 @@ except mariadb.OperationalError:
|
||||
c.execute(
|
||||
"CREATE TABLE SubCats(id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,LId BIGINT(20),PId BIGINT(20),cat TINYINT UNSIGNED,name VARCHAR(256), PRIMARY KEY(id))"
|
||||
)
|
||||
try:
|
||||
c.execute(
|
||||
"CREATE TABLE FIP(id UUID DEFAULT(UUID()), filename VARCHAR(256), filetype VARCHAR(8),initTimeStamp DATETIME, PRIMARY KEY(id))"
|
||||
)
|
||||
except mariadb.OperationalError:
|
||||
pass
|
||||
db.commit()
|
||||
|
||||
|
||||
def remove_old_FIP_entrys():
|
||||
cur = db.cursor(dictionary=True)
|
||||
cur.execute(
|
||||
"SELECT id,filename FROM FIP WHERE HOUR(TIMEDIFF(NOW(),initTimeStamp)) > 24 "
|
||||
)
|
||||
files = cur.fetchall()
|
||||
info(f"Remove Files: {files}")
|
||||
for file in files:
|
||||
c.execute("DELETE FROM FIP WHERE id=?", (file["id"],))
|
||||
os.remove(FILES_IN_PROGRESS / file["filename"])
|
||||
db.commit()
|
||||
|
||||
|
||||
def get_dirstruct():
|
||||
with open("app/pwfile.json", "r") as f:
|
||||
cred = json.load(f)
|
||||
ssh = paramiko.SSHClient()
|
||||
print(cred["sftpurl"])
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
# with open("app/pwfile.json", "r") as f:
|
||||
# cred = json.load(f)
|
||||
# ssh = paramiko.SSHClient()
|
||||
# print(cred["sftpurl"])
|
||||
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
# key=paramiko.RSAKey(data=decodebytes(bytes(cred["key"],"utf-8")))
|
||||
# ssh.get_host_keys().add(cred["sftpurl"], 'ssh-rsa', key)
|
||||
ssh.connect(cred["sftpurl"], username=cred["sftpuser"], password=cred["sftpPW"])
|
||||
sftp = ssh.open_sftp()
|
||||
folders = sftp.listdir_attr(unizeug_path)
|
||||
for entry in folders:
|
||||
# ssh.connect(cred["sftpurl"], username=cred["sftpuser"], password=cred["sftpPW"])
|
||||
# sftp = ssh.open_sftp()
|
||||
# folders = sftp.listdir_attr(unizeug_path)
|
||||
folders = pathlib.Path(unizeug_path)
|
||||
for entry in folders.iterdir():
|
||||
if entry is None:
|
||||
continue
|
||||
if not S_ISDIR(entry.st_mode):
|
||||
if not entry.is_dir():
|
||||
continue
|
||||
fname = str(entry.filename)
|
||||
fname = str(entry.name)
|
||||
regex = re.compile(r"Multimedia_only")
|
||||
if regex.search(fname):
|
||||
continue
|
||||
# print(fname)
|
||||
lvid = re.search(r"[a-zA-Z0-9]{3}\.[a-zA-Z0-9]{3}", fname)
|
||||
print(lvid)
|
||||
# print(lvid)
|
||||
if lvid is None:
|
||||
error(f"Didnt Find LVA ID in Directory {fname}")
|
||||
continue
|
||||
lvid = lvid.group()[:3] + lvid.group()[4:]
|
||||
name = fname[:-8]
|
||||
# name = fname[:-8]
|
||||
name = re.sub(r"[a-zA-Z0-9]{3}\.[a-zA-Z0-9]{3}", "", fname)
|
||||
# print(name)
|
||||
# print(lvid)
|
||||
cur = db.cursor()
|
||||
@@ -88,41 +132,38 @@ def get_dirstruct():
|
||||
cur.execute("SELECT id FROM LVAs WHERE lvid=?", (lvid,))
|
||||
lid = cur.fetchone()[0]
|
||||
db.commit()
|
||||
for profsdir in sftp.listdir_attr(unizeug_path + fname + "/"):
|
||||
if profsdir is None or not S_ISDIR(profsdir.st_mode):
|
||||
for profsdir in entry.iterdir():
|
||||
if profsdir is None:
|
||||
continue
|
||||
if not profsdir.is_dir():
|
||||
continue
|
||||
# print(profsdir.filename)
|
||||
try:
|
||||
lastname, firstname = re.split(r"[_\-\s]", str(profsdir.filename))
|
||||
lastname, firstname = re.split(r"[_\-\s]", str(profsdir.name))
|
||||
pid = link_prof(firstname, lastname, lid)
|
||||
except ValueError:
|
||||
print(f"{name} is broken")
|
||||
error(f"Couldnt get Profs from {fname}")
|
||||
continue
|
||||
for cat in sftp.listdir_attr(
|
||||
unizeug_path + fname + "/" + profsdir.filename + "/"
|
||||
):
|
||||
if cat is None or not S_ISDIR(cat.st_mode):
|
||||
for cat in profsdir.iterdir():
|
||||
if cat is None:
|
||||
continue
|
||||
if cat.filename not in SUBCAT_CATEGORIES:
|
||||
if not cat.is_dir():
|
||||
continue
|
||||
idx = CATEGORIES.index(cat.filename)
|
||||
for subcat in sftp.listdir_attr(
|
||||
unizeug_path
|
||||
+ fname
|
||||
+ "/"
|
||||
+ profsdir.filename
|
||||
+ "/"
|
||||
+ cat.filename
|
||||
+ "/"
|
||||
):
|
||||
if subcat is None or not S_ISDIR(subcat.st_mode):
|
||||
if cat.name not in SUBCAT_CATEGORIES:
|
||||
continue
|
||||
idx = CATEGORIES.index(cat.name)
|
||||
for subcat in cat.iterdir():
|
||||
if subcat is None:
|
||||
continue
|
||||
if not subcat.is_dir():
|
||||
continue
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO SubCats (LId,PId,cat,name) VALUES(?,?,?,?)",
|
||||
(lid, pid, idx, subcat.filename),
|
||||
(lid, pid, idx, subcat.name),
|
||||
)
|
||||
db.commit()
|
||||
remove_old_FIP_entrys()
|
||||
|
||||
|
||||
def link_prof(firstname, lastname, lid):
|
||||
@@ -149,3 +190,8 @@ def link_prof(firstname, lastname, lid):
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_dirstruct()
|
||||
info("Database updated")
|
||||
schedule.every().day.at("04:00", "Europe/Vienna").do(get_dirstruct)
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
|
||||
152
app/init_ssh.py
Normal file
@@ -0,0 +1,152 @@
|
||||
import paramiko
|
||||
from stat import S_ISDIR, S_ISREG
|
||||
import re
|
||||
|
||||
# from base64 import decodebytes
|
||||
import json
|
||||
import mariadb
|
||||
|
||||
CATEGORIES = [
|
||||
"Prüfungen",
|
||||
"Klausuren",
|
||||
"Übungen",
|
||||
"Labore",
|
||||
"Unterlagen",
|
||||
"Zusammenfassungen",
|
||||
"Multimedia",
|
||||
]
|
||||
SUBCAT_CATEGORIES = ["Klausuren", "Übungen", "Labore"]
|
||||
unizeug_path = "/mnt/save/daten/Unizeug/"
|
||||
db = mariadb.connect(
|
||||
host="localhost", user="wildserver", password="DBPassword", database="Unizeug"
|
||||
)
|
||||
c = db.cursor()
|
||||
try:
|
||||
c.execute("DROP TABLE LVAs")
|
||||
except mariadb.OperationalError:
|
||||
pass
|
||||
c.execute(
|
||||
"CREATE TABLE LVAs(id BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,lvid VARCHAR(6), lvname VARCHAR(256), lvpath VARCHAR(256),PRIMARY KEY(id))"
|
||||
)
|
||||
try:
|
||||
c.execute("DROP TABLE Profs")
|
||||
except mariadb.OperationalError:
|
||||
pass
|
||||
c.execute(
|
||||
"CREATE TABLE Profs(id BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,name VARCHAR(256),PRIMARY KEY(id))"
|
||||
)
|
||||
try:
|
||||
c.execute("DROP TABLE LPLink")
|
||||
except mariadb.OperationalError:
|
||||
pass
|
||||
c.execute(
|
||||
"CREATE TABLE LPLink(id BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,LId bigint(20),PId bigint(20),PRIMARY KEY(id))"
|
||||
)
|
||||
try:
|
||||
c.execute("DROP TABLE SubCats")
|
||||
except mariadb.OperationalError:
|
||||
pass
|
||||
c.execute(
|
||||
"CREATE TABLE SubCats(id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,LId BIGINT(20),PId BIGINT(20),cat TINYINT UNSIGNED,name VARCHAR(256), PRIMARY KEY(id))"
|
||||
)
|
||||
db.commit()
|
||||
|
||||
|
||||
def get_dirstruct():
|
||||
with open("app/pwfile.json", "r") as f:
|
||||
cred = json.load(f)
|
||||
ssh = paramiko.SSHClient()
|
||||
print(cred["sftpurl"])
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
# key=paramiko.RSAKey(data=decodebytes(bytes(cred["key"],"utf-8")))
|
||||
# ssh.get_host_keys().add(cred["sftpurl"], 'ssh-rsa', key)
|
||||
ssh.connect(cred["sftpurl"], username=cred["sftpuser"], password=cred["sftpPW"])
|
||||
sftp = ssh.open_sftp()
|
||||
folders = sftp.listdir_attr(unizeug_path)
|
||||
for entry in folders:
|
||||
if entry is None:
|
||||
continue
|
||||
if not S_ISDIR(entry.st_mode):
|
||||
continue
|
||||
fname = str(entry.filename)
|
||||
regex = re.compile(r"Multimedia_only")
|
||||
if regex.search(fname):
|
||||
continue
|
||||
# print(fname)
|
||||
lvid = re.search(r"[a-zA-Z0-9]{3}\.[a-zA-Z0-9]{3}", fname)
|
||||
print(lvid)
|
||||
if lvid is None:
|
||||
continue
|
||||
lvid = lvid.group()[:3] + lvid.group()[4:]
|
||||
# name = fname[:-8]
|
||||
name = re.sub(r"[a-zA-Z0-9]{3}\.[a-zA-Z0-9]{3}", "", fname)
|
||||
# print(name)
|
||||
# print(lvid)
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO LVAs (lvid, lvname, lvpath) VALUES(?,?,?)", (lvid, name, fname)
|
||||
)
|
||||
cur.execute("SELECT id FROM LVAs WHERE lvid=?", (lvid,))
|
||||
lid = cur.fetchone()[0]
|
||||
db.commit()
|
||||
for profsdir in sftp.listdir_attr(unizeug_path + fname + "/"):
|
||||
if profsdir is None or not S_ISDIR(profsdir.st_mode):
|
||||
continue
|
||||
# print(profsdir.filename)
|
||||
try:
|
||||
lastname, firstname = re.split(r"[_\-\s]", str(profsdir.filename))
|
||||
pid = link_prof(firstname, lastname, lid)
|
||||
except ValueError:
|
||||
print(f"{name} is broken")
|
||||
continue
|
||||
for cat in sftp.listdir_attr(
|
||||
unizeug_path + fname + "/" + profsdir.filename + "/"
|
||||
):
|
||||
if cat is None or not S_ISDIR(cat.st_mode):
|
||||
continue
|
||||
if cat.filename not in SUBCAT_CATEGORIES:
|
||||
continue
|
||||
idx = CATEGORIES.index(cat.filename)
|
||||
for subcat in sftp.listdir_attr(
|
||||
unizeug_path
|
||||
+ fname
|
||||
+ "/"
|
||||
+ profsdir.filename
|
||||
+ "/"
|
||||
+ cat.filename
|
||||
+ "/"
|
||||
):
|
||||
if subcat is None or not S_ISDIR(subcat.st_mode):
|
||||
continue
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO SubCats (LId,PId,cat,name) VALUES(?,?,?,?)",
|
||||
(lid, pid, idx, subcat.filename),
|
||||
)
|
||||
db.commit()
|
||||
|
||||
|
||||
def link_prof(firstname, lastname, lid):
|
||||
cur = db.cursor()
|
||||
cur.execute("SELECT id from Profs WHERE name=?", (lastname + " " + firstname,))
|
||||
res = cur.fetchone()
|
||||
if res is not None:
|
||||
cur.execute("INSERT INTO LPLink (LId,PId) VALUES(?,?)", (lid, res[0]))
|
||||
db.commit()
|
||||
return res[0]
|
||||
cur.execute("SELECT id from Profs WHERE name=?", (firstname + " " + lastname,))
|
||||
res = cur.fetchone()
|
||||
if res is not None:
|
||||
cur.execute("INSERT INTO LPLink (LId,PId) VALUES(?,?)", (lid, res[0]))
|
||||
db.commit()
|
||||
return res[0]
|
||||
cur.execute("INSERT INTO Profs (name) VALUES(?)", (lastname + " " + firstname,))
|
||||
cur.execute("SELECT id FROM Profs WHERE name=?", (lastname + " " + firstname,))
|
||||
res = cur.fetchone()
|
||||
cur.execute("INSERT INTO LPLink (LId,PId) VALUES(?,?)", (lid, res[0]))
|
||||
db.commit()
|
||||
return res[0]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_dirstruct()
|
||||
776
app/main.py
@@ -188,6 +188,9 @@ class PDFDocument {
|
||||
}
|
||||
}
|
||||
var mouseIsDown = false;
|
||||
var modal;
|
||||
var close_loading;
|
||||
var upload_status;
|
||||
//var startX = 0;
|
||||
//var startY = 0;
|
||||
//var pdf;
|
||||
@@ -273,24 +276,59 @@ function submitPdf(eve) {
|
||||
formdata.append("fileId", doc.fID);
|
||||
//formdata.append("filename", doc.filename);
|
||||
formdata.append("ftype", doc.filetype);
|
||||
if (!formdata.has("ocr")) {
|
||||
formdata.append("ocr", "False");
|
||||
}
|
||||
console.log(formdata);
|
||||
submitForm(formdata);
|
||||
}
|
||||
async function submitForm(formData) {
|
||||
var updateEventSource = null;
|
||||
try {
|
||||
const response = await fetch("http://127.0.0.1:8000/submit", {
|
||||
updateEventSource = new EventSource("/get_censor_status/" + doc.fID);
|
||||
|
||||
modal.style.display = "flex";
|
||||
// console.log("http://127.0.0.1:8000/get_censor_status/" + doc.fID);
|
||||
updateEventSource.addEventListener("censorUpdate", function(eve) {
|
||||
console.log(eve.data);
|
||||
var data = JSON.parse(eve.data);
|
||||
upload_status.innerText =
|
||||
"Censoring Page " + data.page + "/" + data.pages;
|
||||
});
|
||||
} catch {
|
||||
console.error(
|
||||
"Error geting eventsource for updating censoring page count: " + error,
|
||||
);
|
||||
}
|
||||
try {
|
||||
const response = await fetch("/submit/", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
if (updateEventSource !== null) {
|
||||
updateEventSource.close();
|
||||
}
|
||||
modal.style.display = "none";
|
||||
//let responseJSON=await response.json();
|
||||
if (response.ok) {
|
||||
console.log("Submit OK");
|
||||
console.log(response);
|
||||
doc = new PDFDocument("./files/greeting", "greeting", "pdf");
|
||||
// console.log(response);
|
||||
// window.open(response);
|
||||
// console.log(URL.createObjectURL(response.body));
|
||||
// window.open(response);
|
||||
// window.open(response, (target = "_blank"));
|
||||
// var newWindow = window.open();
|
||||
// newWindow.document.write(response);
|
||||
// var blob = response.blob();
|
||||
const blobURL = URL.createObjectURL(await response.blob());
|
||||
window.open(blobURL, "_blank");
|
||||
} else {
|
||||
console.log("Submit failed");
|
||||
window.alert("Error: " + (await response.json())["detail"]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error" + error);
|
||||
console.error("Error submitting: " + error);
|
||||
}
|
||||
}
|
||||
function uploadPdf(eve) {
|
||||
@@ -308,7 +346,7 @@ function uploadPdf(eve) {
|
||||
}
|
||||
async function uploadFile(formData) {
|
||||
try {
|
||||
const response = await fetch("http://127.0.0.1:8000/uploadfile", {
|
||||
const response = await fetch("/uploadfile/", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
@@ -327,6 +365,7 @@ async function uploadFile(formData) {
|
||||
);
|
||||
} else {
|
||||
console.log("upload failed");
|
||||
window.alert("Error: " + (await response.json())["detail"]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error: " + error);
|
||||
@@ -351,13 +390,23 @@ function initListeners() {
|
||||
doc.clearAll();
|
||||
});
|
||||
}
|
||||
function initLoading() {
|
||||
modal = document.querySelector("#loading");
|
||||
// close_loading = document.querySelector(".close");
|
||||
upload_status = document.querySelector("#upload_status");
|
||||
// close_loading.addEventListener("click", function() {
|
||||
// modal.style.display = "none";
|
||||
// });
|
||||
}
|
||||
const startPdf = () => {
|
||||
doc = new PDFDocument(
|
||||
"./files/b78c869f-e0bb-11ef-9b58-84144d05d665",
|
||||
"b78c869f-e0bb-11ef-9b58-84144d05d665",
|
||||
"pdf",
|
||||
);
|
||||
// doc = new PDFDocument(
|
||||
// "./files/b78c869f-e0bb-11ef-9b58-84144d05d665",
|
||||
// "b78c869f-e0bb-11ef-9b58-84144d05d665",
|
||||
// "pdf",
|
||||
// );
|
||||
//pdf = new PDFView("./VO_Mathematik_3.pdf");
|
||||
doc = new PDFDocument("./files/greeting", "greeting", "pdf");
|
||||
initLoading();
|
||||
initDraw();
|
||||
initUpload();
|
||||
initListeners();
|
||||
@@ -1,4 +1,4 @@
|
||||
var url = "http://127.0.0.1:8000/search/";
|
||||
var url = "/search/";
|
||||
var lid = null;
|
||||
var pid = null;
|
||||
var activeAutocompletion = null;
|
||||
@@ -8,7 +8,11 @@ function autocomplete(inp, type) {
|
||||
the text field element and an array of possible autocompleted values:*/
|
||||
var currentFocus;
|
||||
/*execute a function when someone writes in the text field:*/
|
||||
inp.addEventListener("focus", updateAutocomplete);
|
||||
inp.addEventListener("focus", (e) => {
|
||||
e.target.select();
|
||||
// this.select();
|
||||
updateAutocomplete();
|
||||
});
|
||||
inp.addEventListener("input", updateAutocomplete);
|
||||
async function updateAutocomplete() {
|
||||
activeAutocompletion = type;
|
||||
@@ -17,13 +21,16 @@ function autocomplete(inp, type) {
|
||||
i,
|
||||
apirq,
|
||||
iname,
|
||||
val = this.value;
|
||||
val = inp.value;
|
||||
/*close any already open lists of autocompleted values*/
|
||||
closeAllLists();
|
||||
if (!val && type === "lva") {
|
||||
if (!val && type === "lva" && pid === null) {
|
||||
return false;
|
||||
}
|
||||
if (type === "prof" && lid !== null) {
|
||||
if (type === "lva" && pid !== null) {
|
||||
apirq =
|
||||
url + type + "?searchterm=" + val + "&pid=" + pid + "&searchlim=10";
|
||||
} else if (type === "prof" && lid !== null) {
|
||||
apirq =
|
||||
url + type + "?searchterm=" + val + "&lid=" + lid + "&searchlim=10";
|
||||
} else if (type === "subcat" && lid !== null && pid !== null) {
|
||||
@@ -49,7 +56,7 @@ function autocomplete(inp, type) {
|
||||
a.setAttribute("id", this.id + "autocomplete-list");
|
||||
a.setAttribute("class", "autocomplete-items");
|
||||
/*append the DIV element as a child of the autocomplete container:*/
|
||||
this.parentNode.appendChild(a);
|
||||
inp.parentNode.appendChild(a);
|
||||
/*for each item in the array...*/
|
||||
//await response;
|
||||
if (response.ok) {
|
||||
@@ -167,9 +174,21 @@ function autocomplete(inp, type) {
|
||||
closeAllLists(e.target);
|
||||
});
|
||||
}
|
||||
function enter_current_semeseter() {
|
||||
var semField = document.getElementById("sem");
|
||||
var today = new Date();
|
||||
var year = today.getFullYear();
|
||||
var month = today.getMonth();
|
||||
if (month < 9 && month > 1) {
|
||||
semField.value = String(year) + "S";
|
||||
} else {
|
||||
semField.value = String(year) + "W";
|
||||
}
|
||||
}
|
||||
function init() {
|
||||
autocomplete(document.getElementById("lva"), "lva");
|
||||
autocomplete(document.getElementById("prof"), "prof");
|
||||
autocomplete(document.getElementById("subcat"), "subcat");
|
||||
enter_current_semeseter();
|
||||
}
|
||||
window.addEventListener("load", init);
|
||||
42
app/static/dynhide.js
Normal file
@@ -0,0 +1,42 @@
|
||||
var radiobuttons;
|
||||
var datediv;
|
||||
var subcatdiv;
|
||||
var rdbarr;
|
||||
var subcatcategories = [1, 2, 3];
|
||||
var datecategorires = [0, 1];
|
||||
function changevis() {
|
||||
for (let i = 0; i < rdbarr.length; i++) {
|
||||
if (rdbarr[i].checked) {
|
||||
if (subcatcategories.includes(i)) {
|
||||
subcatdiv.style.display = "block";
|
||||
} else {
|
||||
subcatdiv.style.display = "none";
|
||||
}
|
||||
if (datecategorires.includes(i)) {
|
||||
datediv.style.display = "block";
|
||||
} else {
|
||||
datediv.style.display = "none";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
function starthide() {
|
||||
radiobuttons = document.getElementsByName("stype");
|
||||
datediv = document.getElementById("datediv");
|
||||
subcatdiv = document.getElementById("subcatdiv");
|
||||
rdbarr = [
|
||||
document.getElementById("pruefung"),
|
||||
document.getElementById("klausur"),
|
||||
document.getElementById("uebung"),
|
||||
document.getElementById("labor"),
|
||||
document.getElementById("unterlagen"),
|
||||
document.getElementById("zusammenfassungen"),
|
||||
document.getElementById("multimedia"),
|
||||
];
|
||||
changevis();
|
||||
radiobuttons.forEach((rdb) => {
|
||||
rdb.addEventListener("change", changevis);
|
||||
});
|
||||
}
|
||||
window.addEventListener("load", starthide);
|
||||
10
app/static/filedrop.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var fileinput;
|
||||
function dropHandler(eve) {
|
||||
eve.preventDefault();
|
||||
fileinput.files = eve.dataTransfer.files;
|
||||
}
|
||||
function init() {
|
||||
fileinput = document.getElementById("filepicker");
|
||||
document.getElementById("filepicker").addEventListener("drop", dropHandler);
|
||||
}
|
||||
window.addEventListener("load", init);
|
||||
321
app/static/style.css
Normal file
@@ -0,0 +1,321 @@
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: #2f3957;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.right {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: 75vw;
|
||||
float: right;
|
||||
/* background-color: navy; */
|
||||
}
|
||||
|
||||
.left {
|
||||
/* background-color: blueviolet; */
|
||||
height: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
span {
|
||||
color: lightgray;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.fullsize {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #0872a9;
|
||||
/* border-radius: 20px; */
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#cnvdiv {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/*display: flex;*/
|
||||
/*text-align: center;*/
|
||||
/*justify-content: center;*/
|
||||
}
|
||||
|
||||
.stack {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
/*text-align: center;*/
|
||||
/*float: right;*/
|
||||
/*margin-right: 0;*/
|
||||
/*margin-left: auto;*/
|
||||
/*display: block;*/
|
||||
}
|
||||
|
||||
.stack>canvas {
|
||||
position: absolute;
|
||||
/*display: block;*/
|
||||
/*display: inline;*/
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#submitdiv {
|
||||
/*position: relative;*/
|
||||
width: 500px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
/* color: white; */
|
||||
background-color: grey;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
/* margin: 10px; */
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/*Things I've stolen from https://www.w3schools.com/howto/howto_js_autocomplete.asp*/
|
||||
.autocomplete {
|
||||
/*the container must be positioned relative:*/
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* width: 400px; */
|
||||
width: 100%;
|
||||
padding: none;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#name {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid #818181;
|
||||
background-color: #a1a1a1;
|
||||
padding: 10px;
|
||||
/* height: 50px; */
|
||||
font-size: 12pt;
|
||||
border-radius: 20px;
|
||||
margin-top: 10px;
|
||||
/* margin-bottom: 10px; */
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
background-color: #b1b1b1;
|
||||
/* width: 100%; */
|
||||
width: 478px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
/* input[type="text"]:focus { */
|
||||
/* border-bottom-right-radius: 0px; */
|
||||
/* border-bottom-left-radius: 0px; */
|
||||
/* outline: none; */
|
||||
/* } */
|
||||
|
||||
div>input[type="text"]:focus {
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #0872a9;
|
||||
/* color: #fff; */
|
||||
border-radius: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.autocomplete-items {
|
||||
position: absolute;
|
||||
/* border: 1px solid #d4d4d4; */
|
||||
border: 1px solid #818181;
|
||||
background-color: #b1b1b1;
|
||||
/* background-color: #b1b1b1; */
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
z-index: 99;
|
||||
/*position the autocomplete items to be the same width as the container:*/
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
/* padding: 20px; */
|
||||
/* padding-top: 10px; */
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.autocomplete-items div {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
/* border-radius: 20px; */
|
||||
background-color: #b1b1b1;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
.autocomplete-items div:hover {
|
||||
/*when hovering an item:*/
|
||||
background-color: #0872a9;
|
||||
}
|
||||
|
||||
.autocomplete-active {
|
||||
/*when navigating through the items using the arrow keys:*/
|
||||
background-color: #0872a9 !important;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* filedrop */
|
||||
input[type="file"] {
|
||||
/* flex: 1; */
|
||||
display: flex;
|
||||
background-color: #b1b1b1;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
/* margin-bottom: auto; */
|
||||
}
|
||||
|
||||
input[type="file"]::file-selector-button {
|
||||
background-color: #d1d1d1;
|
||||
border-radius: 20px;
|
||||
border-color: #a1a1a1;
|
||||
}
|
||||
|
||||
#fileupload {
|
||||
/* display: inline-flex; */
|
||||
width: 500px;
|
||||
border-radius: 20px;
|
||||
background-color: #4f5977;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.fileupload {
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 25%;
|
||||
/* align-self: right; */
|
||||
/* float: right; */
|
||||
}
|
||||
|
||||
#uploadform {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filetop {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* background-color: purple; */
|
||||
}
|
||||
|
||||
/* The Modal (background) */
|
||||
.modal {
|
||||
display: none;
|
||||
/* Hidden by default */
|
||||
position: fixed;
|
||||
/* Stay in place */
|
||||
z-index: 1;
|
||||
/* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 100%;
|
||||
/* Full height */
|
||||
overflow: auto;
|
||||
/* Enable scroll if needed */
|
||||
background-color: #4f5977;
|
||||
/* Fallback color */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
/* Black w/ opacity */
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Modal Content/Box */
|
||||
.loading-content {
|
||||
background-color: #4f5977;
|
||||
margin: auto;
|
||||
/* 15% from the top and centered */
|
||||
padding: 20px;
|
||||
/* border: 1px solid #888; */
|
||||
/* width: 80%; */
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Could be more or less, depending on screen size */
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload_status_text {
|
||||
color: #ffffff;
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: auto;
|
||||
border: 16px solid #f3f3f3;
|
||||
/* Light grey */
|
||||
border-top: 16px solid #3498db;
|
||||
/* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
67
compose.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
services:
|
||||
app:
|
||||
container_name: python-app
|
||||
# command: python -m uvicorn app.main:app --host 0.0.0.0 --port 80
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ./app:/python
|
||||
- ./unizeug:/unizeug:source
|
||||
ports:
|
||||
- 80:80
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ENTRY_COMMAND: python -m uvicorn main:app --host 0.0.0.0 --port 80
|
||||
APP_LOG_PATH: /python/app.log
|
||||
APP_ROOT_PATH: /python
|
||||
UNIZEUG_PATH: /unizeug
|
||||
DB_HOST: db
|
||||
DB_USER: app
|
||||
DB_PASSWORD: DBPassword
|
||||
DB_DATABASE: Unizeug
|
||||
TZ: "Europe/Vienna"
|
||||
|
||||
depends_on:
|
||||
- db
|
||||
- scaner
|
||||
db:
|
||||
container_name: db
|
||||
image: mariadb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: DBPassword
|
||||
MARIADB_USER: app
|
||||
UNIZEUG_PATH: /unizeug
|
||||
MARIADB_PASSWORD: DBPassword
|
||||
MARIADB_DATABASE: Unizeug
|
||||
TZ: "Europe/Vienna"
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
start_period: 10s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
volumes:
|
||||
- ./mariadb:/var/lib/mysql
|
||||
scaner:
|
||||
container_name: python-scaner
|
||||
# command: python /python/init.py
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ./app:/python
|
||||
- ./unizeug:/unizeug:source
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ENTRY_COMMAND: python /python/init.py
|
||||
UNIZEUG_PATH: /unizeug
|
||||
APP_ROOT_PATH: /python
|
||||
DB_HOST: db
|
||||
DB_USER: app
|
||||
DB_PASSWORD: DBPassword
|
||||
DB_DATABASE: Unizeug
|
||||
TZ: "Europe/Vienna"
|
||||
depends_on:
|
||||
- db
|
||||
4
entrypoint.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
if [[ -n "$ENTRY_COMMAND" ]]; then
|
||||
/bin/sh -c "$ENTRY_COMMAND"
|
||||
fi
|
||||
90
index.html
@@ -1,90 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<title>Unizeug uploader</title>
|
||||
<link rel="stylesheet" href="static/style.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js"></script>
|
||||
<script src="static/app.js" defer></script>
|
||||
<script src="static/autocomplete.js" defer></script>
|
||||
<link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="Unizeug" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="left" id="controldiv">
|
||||
<div id="fileupload">
|
||||
<form id="uploadform" enctype="multipart/form-data">
|
||||
<label for="filepicker">Choose a pdf file</label>
|
||||
<input type="file" name="files" id="filepicker" multiple />
|
||||
<button type="submit" id="upload" method="POST">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="submitdiv">
|
||||
<form id="submitform" ,onsubmit="submitFile(event)">
|
||||
<label for="lva">Lehrveranstaltung:</label>
|
||||
<div class="autocomplete">
|
||||
<input type="text" id="lva" name="lva" placeholder="Lehrveranstaltung" autocomplete="off" /><br />
|
||||
</div>
|
||||
<br />
|
||||
<label for="prof">Vortragende*r:</label>
|
||||
<div class="autocomplete">
|
||||
<input type="text" id="prof" name="prof" placeholder="Vortragende*r" autocomplete="off" /><br />
|
||||
</div>
|
||||
<br />
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="fname" placeholder="Prüfung" /><br />
|
||||
<label for="sem">Semester:</label>
|
||||
<input type="text" id="sem" name="sem" placeholder="2024W" /><br />
|
||||
<input type="radio" id="pruefung" name="stype" value="0" checked="checked" />
|
||||
<label for="pruefung">Prüfung</label><br />
|
||||
<input type="radio" id="klausur" name="stype" value="1" />
|
||||
<label for="klausur">Klausur</label><br />
|
||||
<input type="radio" id="uebung" name="stype" value="2" />
|
||||
<label for="uebung">Übung</label><br />
|
||||
<input type="radio" id="labor" name="stype" value="3" />
|
||||
<label for="labor">Labor</label><br />
|
||||
<input type="radio" id="unterlagen" name="stype" value="4" />
|
||||
<label for="unterlagen">Unterlagen</label><br />
|
||||
<input type="radio" id="zusammenfassungen" name="stype" value="5" />
|
||||
<label for="zusammenfassungen">Zusammenfassung</label><br />
|
||||
<input type="radio" id="multimedia" name="stype" value="6" />
|
||||
<label for="multimedia">Multimedia</label><br />
|
||||
<label for="subcat">Veranstaltung</label>
|
||||
<div class="autocomplete">
|
||||
<input type="text" id="subcat" name="subcat" placeholder="Klausur 1" autocomplete="off" />
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<label for="date">Datum</label>
|
||||
<input type="date" id="date" name="ex_date" /><br />
|
||||
<button type="submit" id="send">Senden</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" id="rightdiv">
|
||||
<div class="buttons" id="buttonsdiv">
|
||||
<button id="prev">Prev</button><button id="next">Next</button>
|
||||
<div>
|
||||
<span id="npage"></span>
|
||||
<span>/</span>
|
||||
<span id="npages"></span>
|
||||
</div>
|
||||
<button id="clr">Clear Page</button><button id="ca">Claer All</button>
|
||||
</div>
|
||||
<div id="cnvdiv">
|
||||
<div class="stack" id="cnvcont">
|
||||
<canvas id="cnv"></canvas>
|
||||
<canvas id="drw_cnv"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
66
requirements.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
annotated-types==0.7.0
|
||||
anyio==4.8.0
|
||||
bcrypt==4.2.1
|
||||
certifi==2024.12.14
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.1
|
||||
click==8.1.8
|
||||
comtypes==1.4.10
|
||||
cryptography==44.0.0
|
||||
dnspython==2.7.0
|
||||
docopt==0.6.2
|
||||
email_validator==2.2.0
|
||||
fastapi==0.115.7
|
||||
fastapi-cli==0.0.7
|
||||
filetype==1.2.0
|
||||
h11==0.14.0
|
||||
httpcore==1.0.7
|
||||
httptools==0.6.4
|
||||
httpx==0.28.1
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.5
|
||||
mariadb==1.1.11
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==3.0.2
|
||||
mdurl==0.1.2
|
||||
mysql-connector-python==9.2.0
|
||||
mysqlclient==2.2.7
|
||||
orjson==3.10.15
|
||||
packaging==24.2
|
||||
paramiko==3.5.1
|
||||
pdf2image==1.17.0
|
||||
pillow==11.1.0
|
||||
pipreqs==0.4.13
|
||||
pycparser==2.22
|
||||
pydantic==2.10.6
|
||||
pydantic-extra-types==2.10.2
|
||||
pydantic-settings==2.7.1
|
||||
pydantic_core==2.27.2
|
||||
Pygments==2.19.1
|
||||
PyMuPDF==1.25.2
|
||||
PyNaCl==1.5.0
|
||||
pypdf==5.2.0
|
||||
pytesseract==0.3.13
|
||||
python-dotenv==1.0.1
|
||||
python-multipart==0.0.20
|
||||
pytz==2025.2
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.3
|
||||
rich==13.9.4
|
||||
rich-toolkit==0.13.2
|
||||
schedule==1.2.2
|
||||
shellingham==1.5.4
|
||||
sniffio==1.3.1
|
||||
starlette==0.45.3
|
||||
tesseract==0.1.3
|
||||
tqdm==4.67.1
|
||||
typer==0.15.1
|
||||
typing_extensions==4.12.2
|
||||
ujson==5.10.0
|
||||
urllib3==2.3.0
|
||||
uvicorn==0.34.0
|
||||
uvloop==0.21.0
|
||||
watchfiles==1.0.4
|
||||
websockets==14.2
|
||||
yarg==0.1.10
|
||||
9
run.md
Normal file
@@ -0,0 +1,9 @@
|
||||
sudo systemctl start mariadb.service
|
||||
python -m uvicorn app.main:app --reload
|
||||
Requirements:
|
||||
|
||||
- For pymupdf tesseract needs to be installed for the language deu on the system.
|
||||
- All the tables in daatabase_init.sql must be in a database called Unizeug with password: DBPassword
|
||||
- requirements.txt or just all the python modules until pytohn stops complaining. I recommend to use a venv for this project.
|
||||
Just information I dont want to forget:
|
||||
- TISS API maby not nessecarry: https://tiss.tuwien.ac.at/api/course/101685-2024W
|
||||
4
run.txt
@@ -1,4 +0,0 @@
|
||||
sudo systemctl start mariadb.service
|
||||
python -m uvicorn app.main:app --reload
|
||||
Requirements: For pymupdf tesseract needs to be installed for the language deu un the system.
|
||||
TISS API maby not nessecarry: https://tiss.tuwien.ac.at/api/course/101685-2024W
|
||||
135
static/style.css
@@ -1,135 +0,0 @@
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: slategrey;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.right {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: 75vw;
|
||||
float: right;
|
||||
background-color: navy;
|
||||
}
|
||||
|
||||
.left {
|
||||
background-color: blueviolet;
|
||||
height: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
span {
|
||||
color: lightgray;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.fullsize {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#cnvdiv {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/*display: flex;*/
|
||||
/*text-align: center;*/
|
||||
/*justify-content: center;*/
|
||||
}
|
||||
|
||||
.stack {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
/*text-align: center;*/
|
||||
/*float: right;*/
|
||||
/*margin-right: 0;*/
|
||||
/*margin-left: auto;*/
|
||||
/*display: block;*/
|
||||
}
|
||||
|
||||
.stack>canvas {
|
||||
position: absolute;
|
||||
/*display: block;*/
|
||||
/*display: inline;*/
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#submitdiv {
|
||||
/*position: relative;*/
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
/*Things I've stolen from https://www.w3schools.com/howto/howto_js_autocomplete.asp*/
|
||||
.autocomplete {
|
||||
/*the container must be positioned relative:*/
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
background-color: #f1f1f1;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
background-color: #f1f1f1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: DodgerBlue;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.autocomplete-items {
|
||||
position: absolute;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
z-index: 99;
|
||||
/*position the autocomplete items to be the same width as the container:*/
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.autocomplete-items div {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
.autocomplete-items div:hover {
|
||||
/*when hovering an item:*/
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.autocomplete-active {
|
||||
/*when navigating through the items using the arrow keys:*/
|
||||
background-color: DodgerBlue !important;
|
||||
color: #ffffff;
|
||||
}
|
||||