update to fisrt version
This commit is contained in:
@@ -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*"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
241
src/anki_creator_flask/templates/create.html
Normal file
241
src/anki_creator_flask/templates/create.html
Normal 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">
|
||||
© 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>
|
||||
256
src/anki_creator_flask/templates/file_list.html
Normal file
256
src/anki_creator_flask/templates/file_list.html
Normal 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">
|
||||
© 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>
|
||||
219
src/anki_creator_flask/templates/process.html
Normal file
219
src/anki_creator_flask/templates/process.html
Normal 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">
|
||||
© 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>
|
||||
Reference in New Issue
Block a user