update to fisrt version

This commit is contained in:
Wolfang Torres
2026-06-19 20:18:04 +08:00
parent f5452a57c1
commit 62264791ae
9 changed files with 919 additions and 177 deletions

View File

@@ -27,7 +27,9 @@ classifiers = [
]
dependencies = [
"flask",
"gunicorn"
"gunicorn",
"python-dotenv",
"anki-hsk-creator",
]
[project.optional-dependencies]
@@ -35,7 +37,8 @@ dev = [
"pytest",
"black",
"pylint",
"flakehell"
"flake8",
"flake8-pyproject",
]
[project.urls]
@@ -53,6 +56,7 @@ extra-dependencies = [
[tool.hatch.envs.default.scripts]
format = "black --target-version=py314 src tests && isort src tests"
lint = "flake8 src"
[tool.hatch.envs.types]
extra-dependencies = [
@@ -128,7 +132,7 @@ msg-template="{path}:{module}:{line}: [{msg_id}({symbol}), {obj}] {msg}"
logging-format-style="new"
logging-modules="logging"
[tool.flakehell]
[tool.flake8]
max_line_length = 88
format = "grouped"
show_source = false
@@ -143,7 +147,7 @@ exclude = [
"*.egg-info",
]
[tool.flakehell.plugins]
[tool.flake8.plugins]
mccabe = ["+C*"]
pycodestyle = ["+E*", "+W*", "-E203", "-E501", "-W503"]
pyflakes = ["+F*"]

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2026-present Wolfang Torres <wolfang.torres@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
__version__ = "0.0.1"
__version__ = "1.0.0"

View File

@@ -2,17 +2,21 @@
from pathlib import Path
# Pip
from dotenv import load_dotenv
from flask import Flask
# Local
from .homescreen import homescreen
from .mainapp import mainapp
load_dotenv()
def create_app(*args):
def create_app():
"""Creates the Flask App"""
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config["APPLICATION_ROOT"] = "/hsk"
app.config["APPLICATION_ROOT"] = "/hskankicreator"
# ensure the instance folder exists
instance_path = Path(app.instance_path)

View File

@@ -6,18 +6,20 @@ homescreen = Blueprint(
"homescreen",
__name__,
template_folder="templates",
url_prefix="/hsk",
url_prefix="/hskankicreator",
static_folder="static",
)
@homescreen.route("/")
def index():
return render_template(f"index.html")
"""main page"""
return render_template("index.html")
@homescreen.route("/favicon.ico")
def favicon():
"""favicon"""
return send_from_directory(
current_app.static_folder, "favicon.ico", mimetype="image/vnd.microsoft.icon"
)
@@ -26,7 +28,7 @@ def favicon():
@homescreen.route("/", defaults={"page": "index"})
@homescreen.route("/<page>")
def show(page):
print(page)
"""general entry for other pages"""
try:
return render_template(f"{page}.html")
except TemplateNotFound:

View File

@@ -1,28 +1,168 @@
# Standard Library
from pathlib import Path
# Pip
from flask import Blueprint, render_template, request
from anki_hsk_creator import api
from anki_hsk_creator.constants import DICT_TYPE, PHRASES_TYPE
from anki_hsk_creator.utility import ProcessFile
from flask import (
Blueprint,
make_response,
redirect,
render_template,
request,
send_from_directory,
url_for,
)
mainapp = Blueprint(
"mainapp",
__name__,
template_folder="templates",
url_prefix="/hsk/app",
url_prefix="/hskankicreator/app",
static_folder="static",
)
@mainapp.route("/", methods=["GET", "POST"])
def show():
@mainapp.route("/", methods=["GET"])
def app():
"""Main app entry pages"""
return render_template("app.html")
@mainapp.route("/list", methods=["GET"])
@mainapp.route("/list/", methods=["GET"])
@mainapp.route("/list/<path:listing_path>", methods=["GET"])
def file_list(listing_path=""):
"""Path for file lister"""
listing_path = Path(listing_path)
root_files = {file: [] for file in api.list_input_files()}
level = root_files
level_path = Path()
for part in listing_path.parts:
level_path = level_path / part
level[level_path] = {file: [] for file in api.list_input_files(level_path)}
level = level[level_path]
if listing_path:
file_data = api.analize_input_files(listing_path)
else:
file_data = api.analize_input_files(None)
return render_template(
"file_list.html",
file_data=file_data,
root_files=root_files,
listing_path=listing_path,
)
@mainapp.route("/download/<path:listing_path>", methods=["GET"])
def download(listing_path):
"""Download a file from DATA_FOLDER / DOWNLOAD"""
download_path = api.get_output_folder() / listing_path
return send_from_directory(download_path.parent, download_path.name)
@mainapp.route("/resource/<path:listing_path>", methods=["GET"])
def resource(listing_path):
"""Gets a file from DATA_FOLDER / RESOURCES"""
download_path = api.get_resources_folder() / listing_path
return send_from_directory(download_path.parent, download_path.name)
@mainapp.route("/create", methods=["GET", "POST"])
@mainapp.route("/create/", methods=["GET", "POST"])
@mainapp.route("/create/<path:listing_path>", methods=["GET", "POST"])
def create(listing_path=""):
"""Main text editor"""
if request.method == "GET":
if api.is_file(listing_path):
text = api.read_input_file(listing_path)
else:
text = ""
args = request.args.to_dict()
deck_type = args.get("deck_type", "")
language = args.get("language", "")
output_type = args.get("output_type", "")
if not deck_type or not language or not output_type:
language_id = args.get("language_id", "")
if not deck_type and not language_id:
state = "new"
elif not deck_type or not language_id:
state = "partial"
else:
state = "complete"
elif request.method == "POST":
state = "ready"
# output_type = args.get("output_type", "")
# if not deck_type or not language_id or not output_type:
return render_template(
"create.html",
data={
"text": text,
"deck_type": deck_type,
"language_id": language_id,
"state": state,
},
)
else:
listing_path = Path(listing_path)
form = request.form.to_dict()
args = request.args.to_dict()
process_file = api.create_input_file(
listing_path.name.split(".")[0],
"." + args["deck_type"],
form["text"],
listing_path.parent,
)
if args["deck_type"] in DICT_TYPE:
api.pre_process_a_dictionary_file(process_file, args["language_id"])
return redirect(
url_for(
"mainapp.process",
listing_path=process_file.relative_dictionary_resource_file,
language_id=args["language_id"],
),
code=303,
)
elif args["deck_type"] in PHRASES_TYPE:
final_file = api.process_a_phrases_file(process_file, args["language_id"])
response = make_response(
send_from_directory(final_file.parent, final_file.name)
)
response.set_cookie("download_started", "true", max_age=10)
return response
return render_template(f"app.html", data=locals())
@mainapp.route("/process/<path:listing_path>", methods=["GET", "POST"])
def process(listing_path):
"""Post process tsv file"""
listing_path = Path(listing_path)
input_file = listing_path.parent
input_file = input_file.parent / f"{input_file.name}.txt"
process_file = ProcessFile(input_file)
languages = process_file.available_dictionary_languages
args = request.args.to_dict()
language_id = args.get("language_id", listing_path.stem.split(".")[1])
if request.method == "GET":
if not language_id:
state = "new"
text = ""
else:
state = "complete"
text = api.read_dictionary_file(process_file, language_id)
output_type = args.get("output_type", "")
return render_template(
"process.html",
languages=languages,
data={
"output_type": output_type,
"language_id": language_id,
"state": state,
"text": text,
},
)
elif request.method == "POST":
form = request.form.to_dict()
api.write_resource_file(process_file, language_id, form["text"])
final_file = api.process_a_dictionary_file(process_file, language_id)
response = make_response(
send_from_directory(final_file.parent, final_file.name)
)
response.set_cookie("download_started", "true", max_age=10)
return response

View File

@@ -10,7 +10,10 @@
/>
<meta name="description" content="" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="static/assets/css/main.css" />
<link
rel="stylesheet"
href="{{ url_for('static', filename='assets/css/main.css') }}"
/>
<link
rel="shortcut icon"
href="{{ url_for('static', filename='favicon.ico') }}"
@@ -23,108 +26,31 @@
<!-- Section -->
<section id="first" style="padding-top: 3em">
<header>
<h2>Select the deck type</h2>
<h2>HSK Anki Creator</h2>
</header>
<div id="text_area" class="content">
<p>
<strong>Paste here the word list</strong>
</p>
<form id="taxt_form" method="POST">
<div class="fields">
<div class="field">
<textarea name="text" id="text" rows="15"></textarea>
</div>
</div>
<ul class="actions">
<li>
<input
id="process"
type="submit"
value="Process"
class="button primary"
/>
</li>
</ul>
</form>
</div>
<div id="text_area" class="content"></div>
<footer>
<form id="settings_form" method="GET">
<ul class="items">
<li>
<h3>Deck Type</h3>
<h3>Actions:</h3>
<div>
<input
type="radio"
id="type1"
name="deck_type"
value="dictionary"
checked
/>
<label for="type1">Dictionary Deck</label>
<a
href="{{ url_for('mainapp.file_list') }}"
class="button primary large"
>List Files</a
>
</div>
<div>
<input
type="radio"
id="type2"
name="deck_type"
value="phrases"
/>
<label for="type2">Phrases List</label>
<a
href="{{ url_for('mainapp.create') }}"
class="button primary large"
>Create File</a
>
</div>
<div>
<input
type="radio"
id="type3"
name="deck_type"
value="paragraph"
/>
<label for="type3">Paragraph Dictation</label>
</div>
</li>
<li>
<h3>Language</h3>
<select id="language" name="language">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="ru">Russian</option>
<option value="fr">French</option>
</select>
</li>
<li>
<h3>Output</h3>
<div>
<input
type="radio"
id="out1"
name="output_type"
value="anki"
checked
/>
<label for="out1">Anki Deck</label>
</div>
<div>
<input
type="radio"
id="out2"
name="output_type"
value="quiz"
/>
<label for="out2">Quizlet deck</label>
</div>
</li>
<li>
<input
id="settings_button"
type="submit"
value="Lock-in Settings"
/>
<br />
<a href=".">Reset Settings</a>
</li>
</ul>
</form>
</footer>
</section>
@@ -136,63 +62,13 @@
</div>
<!-- Scripts -->
<script src="static/assets/js/jquery.min.js"></script>
<script src="static/assets/js/jquery.scrolly.min.js"></script>
<script src="static/assets/js/browser.min.js"></script>
<script src="static/assets/js/breakpoints.min.js"></script>
<script src="static/assets/js/util.js"></script>
<script src="static/assets/js/main.js"></script>
<script src="static/assets/js/app.js"></script>
<script>
const serverData = {{ data | tojson }};
if (serverData.state === "new") {
$("#text_area").hide();
$("#text").prop('disabled', true);
$("#process").prop('disabled', true);
}
if (serverData.state === "complete") {
// Set values to inputs
if (serverData.deck_type === "dictionary") {
$('#type1').prop('checked', true);
}
if (serverData.deck_type === "phrases") {
$('#type2').prop('checked', true);
}
if (serverData.deck_type === "paragraph") {
$('#type3').prop('checked', true);
}
$('#language').val(serverData.language);
if (serverData.output_type === "anki") {
$('#out1').prop('checked', true);
}
if (serverData.output_type === "quiz") {
$('#out2').prop('checked', true);
}
// Disable inputs
$('#type1').prop('disabled', true);
$('#type2').prop('disabled', true);
$('#type3').prop('disabled', true);
$('#language').prop('disabled', true);
$('#out1').prop('disabled', true);
$('#out2').prop('disabled', true);
$("#settings_button").prop('disabled', true);
}
$('#taxt_form').addEventListener('submit', function(e) {
// Get current URL query parameters
const currentParams = new URLSearchParams(window.location.search);
// Loop through current parameters and append them to the form
currentParams.forEach((value, key) => {
// Check if the input already exists to prevent duplicates
if (!this.querySelector(`input[name="${key}"]`)) {
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = key;
hiddenInput.value = value;
this.appendChild(hiddenInput);
}
});
});
</script>
<script src="{{ url_for('static', filename='assets/js/jquery.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/jquery.scrolly.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/browser.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/breakpoints.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/util.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/main.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/app.js') }}/"></script>
<script></script>
</body>
</html>

View File

@@ -0,0 +1,241 @@
<!doctype html>
<html>
<head>
<title>HSK card creator</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<meta name="description" content="" />
<meta name="keywords" content="" />
<link
rel="stylesheet"
href="{{ url_for('static', filename='assets/css/main.css') }}"
/>
<link
rel="shortcut icon"
href="{{ url_for('static', filename='favicon.ico') }}"
/>
<style>
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1a1a1a; /* Dark background */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999; /* Ensures it stays on top of everything */
transition:
opacity 0.5s ease,
visibility 0.5s ease;
}
/* The spinning visual indicator */
.loader {
width: 50px;
height: 50px;
border: 5px solid #333;
border-top: 5px solid #3498db; /* Blue accent color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Descriptive status text */
.loader-text {
color: #ffffff;
font-family: sans-serif;
margin-top: 15px;
font-size: 14px;
letter-spacing: 1px;
}
/* Keyframe for rotation animation */
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Smooth fade-out state triggered by JS */
.loader-hidden {
opacity: 0;
visibility: hidden;
}
</style>
</head>
<body class="is-preload">
<!-- Full-screen preloader container -->
<div id="loader-wrapper">
<div class="loader"></div>
<p class="loader-text">Loading, please wait...</p>
</div>
<!-- Wrapper -->
<div id="wrapper">
<!-- Section -->
<section id="first" style="padding-top: 3em">
<header>
<h2>Select the deck type</h2>
</header>
<div id="text_area" class="content">
<p>
<strong>Paste here the word list</strong>
</p>
<form id="text_form" method="POST">
<div class="fields">
<div class="field">
<textarea name="text" id="text" rows="15"></textarea>
</div>
</div>
<ul class="actions">
<li>
<input
id="process_button"
type="submit"
value="Process"
class="button primary"
/>
</li>
</ul>
</form>
</div>
<footer>
<form id="settings_form" method="GET">
<ul class="items">
<li>
<h3>Deck Type</h3>
<div>
<input
type="radio"
id="type1"
name="deck_type"
value="dictionary"
checked
/>
<label for="type1">Dictionary Deck</label>
</div>
<div>
<input
type="radio"
id="type2"
name="deck_type"
value="phrases"
/>
<label for="type2">Phrases List</label>
</div>
<div>
<input
type="radio"
id="type3"
name="deck_type"
value="paragraph"
/>
<label for="type3">Paragraph Dictation</label>
</div>
</li>
<li>
<h3>Language</h3>
<select id="language_id" name="language_id">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="ru">Russian</option>
<option value="fr">French</option>
</select>
</li>
<li>
<input
id="settings_button"
type="submit"
value="Lock-in Settings"
/>
<br />
<a href="{{ url_for('mainapp.create') }}">Reset Settings</a>
<br />
<a href="{{ url_for('mainapp.app') }}">Back</a>
</li>
</ul>
</form>
</footer>
</section>
<!-- Copyright -->
<div class="copyright">
&copy; HSK creator. Wolfang Torres. All rights reserved 2026. Design:
<a href="https://html5up.net">HTML5 UP</a>.
</div>
</div>
<!-- Scripts -->
<script src="{{ url_for('static', filename='assets/js/jquery.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/jquery.scrolly.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/browser.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/breakpoints.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/util.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/main.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/app.js') }}/"></script>
<script>
const serverData = {{ data | tojson }};
if (serverData.state === "new") {
$("#process_button").prop('disabled', true);
$('#text').val(serverData.text);
}
if (serverData.state === "partial" || serverData.state === "complete" ) {
$("#process_button").prop('disabled', true);
// Set values to inputs
if (serverData.deck_type !== "" ) {
if (serverData.deck_type === "dictionary") {
$('#type1').prop('checked', true);
}
if (serverData.deck_type === "phrases") {
$('#type2').prop('checked', true);
}
if (serverData.deck_type === "paragraph") {
$('#type3').prop('checked', true);
}
$('#type1').prop('readonly', true);
$('#type2').prop('readonly', true);
$('#type3').prop('readonly', true);
}
if (serverData.language_id !== "" ) {
$('#language_id').val(serverData.language_id);
$('#language_id').prop('readonly', true);
}
$('#text').val(serverData.text);
}
if (serverData.state === "complete") {
$("#process_button").prop('disabled', false);
$("#settings_button").prop('disabled', true);
}
$('#text_form').on('submit', function(e) {
// Add the class that triggers the CSS fade transition
$("#loader-wrapper").removeClass("loader-hidden");
document.cookie = 'download_started=; Max-Age=0; path=/';
const checkDownload = setInterval(() => {
if (document.cookie.includes('download_started=true')) {
clearInterval(checkDownload);
console.log('File successfully downloaded!');
$("#loader-wrapper").addClass("loader-hidden");
this.disabled = false;
document.cookie = 'download_started=; Max-Age=0; path=/';
}
}, 500);
});
$(window).on("load", function() {
$("#loader-wrapper").addClass("loader-hidden");
});
</script>
</body>
</html>

View File

@@ -0,0 +1,256 @@
<!doctype html>
<html>
<head>
<title>HSK card creator</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<meta name="description" content="" />
<meta name="keywords" content="" />
<link
rel="stylesheet"
href="{{ url_for('static', filename='assets/css/main.css') }}"
/>
<link
rel="shortcut icon"
href="{{ url_for('static', filename='favicon.ico') }}"
/>
<style>
/* Base tree styling */
.tree {
--spacing: 1.5rem;
--radius: 4px;
padding-left: 1rem;
}
.tree ul {
margin-left: calc(var(--spacing) / -2);
padding-left: 0;
list-style: none;
}
.tree li {
display: block;
position: relative;
padding-left: calc(var(--spacing) * 1.5);
margin-top: 0.25rem;
}
/* Vertical connecting lines */
.tree ul li::before {
content: "";
position: absolute;
left: calc(var(--spacing) / 2);
top: 0;
bottom: 0;
border-left: 1px solid #ccc;
}
/* Hide the vertical line after the last item
.tree ul li:last-child::before {
display: none;
} */
/* Horizontal branch lines */
.tree ul li::after {
content: "";
position: absolute;
left: calc(var(--spacing) / 2);
top: calc(var(--spacing) / 2);
width: calc(var(--spacing) * 0.8);
border-top: 1px solid #ccc;
}
/* Folder Summary Customization */
.tree summary {
display: block;
cursor: pointer;
outline: none;
font-weight: bold;
}
/* Custom toggle markers using pseudo-elements */
.tree details[closed] > summary::before {
content: "📁 ";
display: inline-block;
}
.tree details[open] > summary::before {
content: "📂 ";
display: inline-block;
}
/* File items customization */
.tree .file::before {
content: "📄 ";
}
.tree .file {
font-weight: normal;
display: inline-block;
}
</style>
</head>
<body class="is-preload">
<!-- Wrapper -->
<div id="wrapper">
<!-- Section -->
<section id="first">
<!-- File broser -->
<header>
<h2>Files</h2>
</header>
<div class="content">
<h3>Current Path: {{ listing_path or "-" }}</h3>
<ul class="items">
<li>
<strong>Input files:</strong>
<ul>
{% for file in file_data['input'] %}
<li>
{% if file.suffixes %}
<a
href="{{ url_for('mainapp.create', listing_path=file , deck_type=file.suffixes[0][1:]) }}"
>
{{ file.name }}
</a>
{% else %} {{ file.name }} {% endif %}
</li>
{% endfor %}
</ul>
</li>
<li>
<strong>Resources files:</strong>
<ul>
{% for file in file_data['resources'] %} {% if file.suffix ==
".wav" %}
<audio controls>
<source
src="{{ url_for('mainapp.resource', listing_path=file) }}"
type="audio/wav"
/>
Your browser does not support the audio element.
</audio>
{% elif file.suffix == ".tsv" %}
<li>
<a href="{{ url_for('mainapp.process', listing_path=file) }}"
>{{ file.name }}</a
>
</li>
{% else %}
<li>{{ file.name }}</li>
{% endif %} {% endfor %}
</ul>
</li>
<li>
<strong>Output files:</strong>
<ul>
{% for file in file_data['output'] %}
<li>
{% if file.suffix %}
<a href="{{ url_for('mainapp.download', listing_path=file) }}"
>{{ file.name }}</a
>
{% else %} {{ file.name }} {% endif %}
</li>
{% endfor %}
</ul>
</li>
</ul>
</div>
<footer>
<div class="tree">
<ul>
<!-- Root Folder -->
{% for file1 in root_files %}
<li>
<details
{%
if
root_files[file1]
%}
open{%
else
%}closed{%
endif
%}
>
<summary>
<a
href="{{ url_for('mainapp.file_list', listing_path=file1) }}"
>{{ file1 }}</a
>
</summary>
<ul>
{% for file2 in root_files[file1] %} {% if file2.suffix %}
<li class="file">
<a
href="{{ url_for('mainapp.file_list', listing_path=file2) }}#first"
>{{ file2.name }}</a
>
</li>
{% else %}
<li>
<details
{%
if
root_files[file1][file2]
%}
open{%
else
%}closed{%
endif
%}
>
<summary>
<a
href="{{ url_for('mainapp.file_list', listing_path=file2) }}#first"
>{{ file2.name }}</a
>
</summary>
<ul>
{% for file3 in root_files[file1][file2] %}
<li class="file">
<a
href="{{ url_for('mainapp.file_list', listing_path=file3) }}#first"
>{{ file3.name }}</a
>
</li>
{% endfor %}
</ul>
</details>
</li>
{% endif %} {% endfor %}
</ul>
</details>
</li>
{% endfor %}
</ul>
</div>
</footer>
</section>
<!-- Copyright -->
<div class="copyright">
&copy; HSK creator. Wolfang Torres. All rights reserved 2026. Design:
<a href="https://html5up.net">HTML5 UP</a>.
</div>
</div>
<!-- Scripts -->
<script src="{{ url_for('static', filename='assets/js/jquery.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/jquery.scrolly.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/browser.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/breakpoints.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/util.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/main.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/app.js') }}/"></script>
<script></script>
</body>
</html>

View File

@@ -0,0 +1,219 @@
<!doctype html>
<html>
<head>
<title>HSK card creator</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<meta name="description" content="" />
<meta name="keywords" content="" />
<link
rel="stylesheet"
href="{{ url_for('static', filename='assets/css/main.css') }}"
/>
<link
rel="shortcut icon"
href="{{ url_for('static', filename='favicon.ico') }}"
/>
<style>
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1a1a1a; /* Dark background */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999; /* Ensures it stays on top of everything */
transition:
opacity 0.5s ease,
visibility 0.5s ease;
}
/* The spinning visual indicator */
.loader {
width: 50px;
height: 50px;
border: 5px solid #333;
border-top: 5px solid #3498db; /* Blue accent color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Descriptive status text */
.loader-text {
color: #ffffff;
font-family: sans-serif;
margin-top: 15px;
font-size: 14px;
letter-spacing: 1px;
}
/* Keyframe for rotation animation */
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Smooth fade-out state triggered by JS */
.loader-hidden {
opacity: 0;
visibility: hidden;
}
</style>
</head>
<body class="is-preload">
<!-- Full-screen preloader container -->
<div id="loader-wrapper">
<div class="loader"></div>
<p class="loader-text">Loading, please wait...</p>
</div>
<!-- Wrapper -->
<div id="wrapper">
<!-- Section -->
<section id="first" style="padding-top: 3em">
<header>
<h2>Select the deck type</h2>
</header>
<div id="text_area" class="content">
<p>
<strong>Delete unwanted Rows and Modify definitions</strong>
</p>
<form id="text_form" method="POST">
<div class="fields">
<div id="table" class="field">
<textarea name="text" id="text" rows="15"></textarea>
</div>
</div>
<ul class="actions">
<li>
<input
id="process_button"
type="submit"
value="Process"
class="button primary"
/>
</li>
</ul>
</form>
</div>
<footer>
<form id="settings_form" method="GET">
<ul class="items">
<li>
<h3>Language</h3>
<select id="language_id" name="language_id">
{% if 'en' in languages %}
<option value="en">English</option>
{% endif %} {% if 'es' in languages %}
<option value="es">Spanish</option>
{% endif %} {% if 'ru' in languages %}
<option value="ru">Russian</option>
{% endif %} {% if 'fr' in languages %}
<option value="fr">French</option>
{% endif %}
</select>
</li>
<li>
<h3>Output</h3>
<div>
<input
type="radio"
id="out1"
name="output_type"
value="anki"
checked
/>
<label for="out1">Anki Deck</label>
</div>
<div>
<input
type="radio"
id="out2"
name="output_type"
value="quiz"
/>
<label for="out2">Quizlet deck</label>
</div>
</li>
<li>
<input
id="settings_button"
type="submit"
value="Lock-in Settings"
/>
<br />
<a href="{{ url_for('mainapp.app') }}">Back</a>
</li>
</ul>
</form>
</footer>
</section>
<!-- Copyright -->
<div class="copyright">
&copy; HSK creator. Wolfang Torres. All rights reserved 2026. Design:
<a href="https://html5up.net">HTML5 UP</a>.
</div>
</div>
<!-- Scripts -->
<script src="{{ url_for('static', filename='assets/js/jquery.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/jquery.scrolly.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/browser.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/breakpoints.min.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/util.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/main.js') }}/"></script>
<script src="{{ url_for('static', filename='assets/js/app.js') }}/"></script>
<script>
// Quizlet is disabled for the moment
$("#out2").prop('disabled', true);
//Get datafor the controls
const serverData = {{ data | tojson }};
if (serverData.state === "new") {
$("#process_button").prop('disabled', true);
$('#text').val(serverData.text);
}
if (serverData.state === "complete" ) {
// Set values to inputs
if (serverData.language_id !== "" ) {
$('#language_id').val(serverData.language_id);
$('#language_id').prop('readonly', true);
}
$('#text').val(serverData.text);
}
// Loader while procesing
$('#text_form').on('submit', function(e) {
// Add the class that triggers the CSS fade transition
$("#loader-wrapper").removeClass("loader-hidden");
document.cookie = 'download_started=; Max-Age=0; path=/';
const checkDownload = setInterval(() => {
if (document.cookie.includes('download_started=true')) {
clearInterval(checkDownload);
console.log('File successfully downloaded!');
$("#loader-wrapper").addClass("loader-hidden");
this.disabled = false;
document.cookie = 'download_started=; Max-Age=0; path=/';
}
}, 500);
});
$(window).on("load", function() {
$("#loader-wrapper").addClass("loader-hidden");
});
</script>
</body>
</html>