Go to Vue3!
This commit is contained in:
parent
a205ebf3a4
commit
0ada059a22
37 changed files with 981 additions and 220 deletions
25
.gitignore
vendored
25
.gitignore
vendored
|
@ -14,3 +14,28 @@ build/
|
|||
*.egg-info/
|
||||
|
||||
cookie.txt
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
package-lock.json
|
44
app/__init__.py
Normal file
44
app/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, current_app, send_file
|
||||
from flask_caching import Cache
|
||||
|
||||
from toloka2MediaServer.main_logic import (
|
||||
add_release_by_url,
|
||||
update_release_by_name,
|
||||
update_releases,
|
||||
search_torrents,
|
||||
get_torrent as get_torrent_external,
|
||||
add_torrent as add_torrent_external,
|
||||
)
|
||||
from app.services.torrents import initiate_config, serialize_operation_result
|
||||
|
||||
#from .api import api_bp
|
||||
from .client import client_bp
|
||||
from .api import *
|
||||
from .db import db
|
||||
|
||||
# App
|
||||
app = Flask(__name__, static_folder='../frontend/dist/assets')
|
||||
app.register_blueprint(client_bp)
|
||||
app.register_blueprint(api_bp)
|
||||
|
||||
# Cache
|
||||
cache = Cache(config={"CACHE_TYPE": "SimpleCache"})
|
||||
cache.init_app(app)
|
||||
|
||||
# Database
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///t2w.db"
|
||||
db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# Production route
|
||||
from .config import Config
|
||||
app.logger.info('>>> {}'.format(Config.FLASK_ENV))
|
||||
|
||||
@app.route('/')
|
||||
def index_client():
|
||||
dist_dir = current_app.config['DIST_DIR']
|
||||
entry = os.path.join(dist_dir, 'index.html')
|
||||
return send_file(entry)
|
7
app/api/__init__.py
Normal file
7
app/api/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from flask import Blueprint
|
||||
|
||||
api_bp = Blueprint('api', __name__)
|
||||
|
||||
from .titles import *
|
||||
from .toloka import *
|
||||
from .config_api import *
|
13
app/api/config_api.py
Normal file
13
app/api/config_api.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from flask import jsonify
|
||||
from app.services.torrents import initiate_config
|
||||
from . import api_bp
|
||||
|
||||
@api_bp.route("/api/config/")
|
||||
def config_route():
|
||||
config = initiate_config()
|
||||
|
||||
config = dict(config.app_config)
|
||||
for section in config.keys():
|
||||
config[section] = dict(config[section])
|
||||
|
||||
return jsonify(config)
|
104
app/api/titles.py
Normal file
104
app/api/titles.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
import os
|
||||
from flask import Blueprint, jsonify, request, current_app
|
||||
from app.services.torrents import initiate_config, serialize_operation_result
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from app.db import db, ImagesCache
|
||||
from app.models.request_data import RequestData
|
||||
from . import api_bp
|
||||
from toloka2MediaServer.main_logic import (
|
||||
add_release_by_url,
|
||||
update_release_by_name,
|
||||
update_releases,
|
||||
search_torrents,
|
||||
get_torrent as get_torrent_external,
|
||||
add_torrent as add_torrent_external,
|
||||
)
|
||||
|
||||
|
||||
@api_bp.route("/api/titles")
|
||||
def titles_route():
|
||||
sections = []
|
||||
config = initiate_config()
|
||||
titles = config.titles_config
|
||||
|
||||
for title in titles.sections():
|
||||
if request.args.get("query", "") in title:
|
||||
image_cache = db.session.execute(
|
||||
db.select(ImagesCache).filter_by(codename=title)
|
||||
).scalar_one_or_none()
|
||||
if image_cache:
|
||||
titles[title]["image"] = image_cache.image
|
||||
else:
|
||||
toloka_torrent = config.toloka.get_torrent(
|
||||
f"https://toloka.to/{titles[title]['guid']}"
|
||||
)
|
||||
toloka_img = (
|
||||
f"https:{toloka_torrent.img}"
|
||||
if toloka_torrent.img.startswith("//")
|
||||
else toloka_torrent.img
|
||||
)
|
||||
db.session.add(ImagesCache(title, toloka_img))
|
||||
db.session.commit()
|
||||
|
||||
titles[title]["image"] = toloka_img
|
||||
|
||||
titles[title]["codename"] = title
|
||||
titles[title]["torrent_name"] = titles[title]["torrent_name"].replace(
|
||||
'"', ""
|
||||
)
|
||||
sections.append(dict(titles[title]))
|
||||
|
||||
return jsonify(sections)
|
||||
|
||||
@api_bp.route("/api/add", methods=['POST'])
|
||||
def add_route():
|
||||
config = initiate_config()
|
||||
|
||||
requestData = RequestData(
|
||||
url=request.json["tolokaUrl"],
|
||||
season=request.json["seasonIndex"],
|
||||
index=int(request.json["episodeIndex"].split('.')[0]),
|
||||
correction=int(request.json["adjustedEpisodeNumber"]),
|
||||
title=request.json["dirname"],
|
||||
)
|
||||
|
||||
config.args = requestData
|
||||
operation_result = add_release_by_url(config)
|
||||
output = serialize_operation_result(operation_result)
|
||||
|
||||
return jsonify({"result": True})
|
||||
|
||||
|
||||
@api_bp.route("/api/delete/<codename>")
|
||||
def delete_route(codename):
|
||||
config = initiate_config()
|
||||
titles = config.titles_config
|
||||
titles.remove_section(codename)
|
||||
with open("data/titles.ini", "w") as f:
|
||||
titles.write(f)
|
||||
|
||||
return f"{codename} успішно видалений."
|
||||
|
||||
|
||||
@api_bp.route("/api/update/", defaults={"codename": None})
|
||||
@api_bp.route("/api/update/<codename>")
|
||||
def update_route(codename):
|
||||
# Process the name to update release
|
||||
try:
|
||||
config = initiate_config()
|
||||
requestData = RequestData(codename=codename)
|
||||
if codename:
|
||||
config.args = requestData
|
||||
operation_result = update_release_by_name(config)
|
||||
else:
|
||||
config.args = RequestData()
|
||||
operation_result = update_releases(config)
|
||||
|
||||
output = serialize_operation_result(operation_result)
|
||||
return output
|
||||
except Exception as e:
|
||||
message = f"Error: {str(e)}"
|
||||
return {"error": message}
|
||||
|
||||
|
||||
|
8
app/api/toloka.py
Normal file
8
app/api/toloka.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from flask import jsonify
|
||||
from app.services.torrents import initiate_config
|
||||
from . import api_bp
|
||||
|
||||
@api_bp.route("/api/toloka/<id>")
|
||||
def toloka_torrent_route(id):
|
||||
config = initiate_config()
|
||||
return jsonify(config.toloka.get_torrent('https://toloka.to/' + id))
|
152
app/app.py
152
app/app.py
|
@ -1,152 +0,0 @@
|
|||
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||
import time
|
||||
from services.torrents import initiate_config, serialize_operation_result
|
||||
|
||||
from models.request_data import RequestData
|
||||
from flask_caching import Cache
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
from toloka2MediaServer.main_logic import (
|
||||
add_release_by_url,
|
||||
update_release_by_name,
|
||||
update_releases,
|
||||
search_torrents,
|
||||
get_torrent as get_torrent_external,
|
||||
add_torrent as add_torrent_external,
|
||||
)
|
||||
|
||||
cache = Cache(config={"CACHE_TYPE": "SimpleCache"})
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///t2w.db"
|
||||
cache.init_app(app)
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
|
||||
|
||||
|
||||
class Base(DeclarativeBase, MappedAsDataclass):
|
||||
pass
|
||||
|
||||
|
||||
db = SQLAlchemy(app, model_class=Base)
|
||||
|
||||
from models.db import ImagesCache
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@cache.cached(timeout=1)
|
||||
def root_route():
|
||||
sections = []
|
||||
config = initiate_config()
|
||||
titles = config.titles_config
|
||||
new_torrent = 0
|
||||
for title in titles.sections():
|
||||
if request.args.get("query", "") in title:
|
||||
image_cache = db.session.execute(
|
||||
db.select(ImagesCache).filter_by(codename=title)
|
||||
).scalar_one_or_none()
|
||||
if image_cache:
|
||||
titles[title]["image"] = image_cache.image
|
||||
else:
|
||||
toloka_torrent = config.toloka.get_torrent(
|
||||
f"https://toloka.to/{titles[title]['guid']}"
|
||||
)
|
||||
toloka_img = (
|
||||
f"https:{toloka_torrent.img}"
|
||||
if toloka_torrent.img.startswith("//")
|
||||
else toloka_torrent.img
|
||||
)
|
||||
db.session.add(ImagesCache(title, toloka_img))
|
||||
db.session.commit()
|
||||
|
||||
titles[title]["image"] = toloka_img
|
||||
|
||||
# Config data
|
||||
titles[title]["codename"] = title
|
||||
titles[title]["torrent_name"] = titles[title]["torrent_name"].replace(
|
||||
'"', ""
|
||||
)
|
||||
sections.append(titles[title])
|
||||
|
||||
return render_template("index.html", titles=sections, new_torrent=new_torrent)
|
||||
|
||||
|
||||
# First stage
|
||||
@app.route("/add")
|
||||
def add_route():
|
||||
config = initiate_config()
|
||||
if request.args.get("query"):
|
||||
torrent = config.toloka.get_torrent(request.args.get("query"))
|
||||
return render_template(
|
||||
"add.html",
|
||||
torrent=torrent,
|
||||
episode_integers=[
|
||||
i
|
||||
for i in "".join(
|
||||
(ch if ch.isdigit() else " ")
|
||||
for ch in f"{torrent.files[0].folder_name}/{torrent.files[0].file_name}"
|
||||
).split()
|
||||
],
|
||||
default_dir=config.app_config.get("Toloka", "default_download_dir"),
|
||||
)
|
||||
|
||||
if len(request.args) == 6:
|
||||
requestData = RequestData(
|
||||
url=request.args["toloka_url"],
|
||||
season=request.args["season-index"],
|
||||
index=int(request.args["episode-index"]),
|
||||
correction=int(request.args["adjusted-episode-number"]),
|
||||
title=request.args["dirname"],
|
||||
)
|
||||
|
||||
config.args = requestData
|
||||
operation_result = add_release_by_url(config)
|
||||
output = serialize_operation_result(operation_result)
|
||||
return redirect(url_for("root_route"))
|
||||
|
||||
return render_template("add.html")
|
||||
|
||||
|
||||
@app.route("/about")
|
||||
def about_route():
|
||||
return render_template("about.html")
|
||||
|
||||
|
||||
@app.route("/settings")
|
||||
def settings_route():
|
||||
return render_template("settings.html")
|
||||
|
||||
|
||||
@app.route("/delete/<codename>")
|
||||
def delete_route(codename):
|
||||
config = initiate_config()
|
||||
titles = config.titles_config
|
||||
titles.remove_section(codename)
|
||||
with open("data/titles.ini", "w") as f:
|
||||
titles.write(f)
|
||||
|
||||
return f"{codename} успішно видалений."
|
||||
|
||||
|
||||
@app.route("/update/", defaults={"codename": None})
|
||||
@app.route("/update/<codename>")
|
||||
def update_route(codename):
|
||||
# Process the name to update release
|
||||
try:
|
||||
config = initiate_config()
|
||||
requestData = RequestData(codename=codename)
|
||||
if codename:
|
||||
config.args = requestData
|
||||
operation_result = update_release_by_name(config)
|
||||
else:
|
||||
config.args = RequestData()
|
||||
operation_result = update_releases(config)
|
||||
|
||||
output = serialize_operation_result(operation_result)
|
||||
return output
|
||||
except Exception as e:
|
||||
message = f"Error: {str(e)}"
|
||||
return {"error": message}
|
9
app/client.py
Normal file
9
app/client.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
""" Client App """
|
||||
|
||||
import os
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
client_bp = Blueprint('client_app', __name__,
|
||||
static_folder='../frontend/dist/assets/',
|
||||
template_folder='../frontend/dist/',
|
||||
)
|
24
app/config.py
Normal file
24
app/config.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
Global Flask Application Setting
|
||||
|
||||
See `.flaskenv` for default settings.
|
||||
"""
|
||||
|
||||
import os
|
||||
from app import app
|
||||
|
||||
class Config(object):
|
||||
# If not set fall back to production for safety
|
||||
FLASK_ENV = os.getenv('FLASK_ENV', 'production')
|
||||
# Set FLASK_SECRET on your production Environment
|
||||
SECRET_KEY = os.getenv('FLASK_SECRET', 'Secret')
|
||||
|
||||
APP_DIR = os.path.dirname(__file__)
|
||||
ROOT_DIR = os.path.dirname(APP_DIR)
|
||||
DIST_DIR = os.path.join(ROOT_DIR, 'frontend/dist')
|
||||
|
||||
if not os.path.exists(DIST_DIR):
|
||||
raise Exception(
|
||||
'DIST_DIR not found: {}'.format(DIST_DIR))
|
||||
|
||||
app.config.from_object('app.config.Config')
|
|
@ -1,52 +0,0 @@
|
|||
[TenseishitaraSlimeDattaKen]
|
||||
episode_index = 2
|
||||
season_number = 03
|
||||
ext_name = .mkv
|
||||
torrent_name = "Tensei shitara Slime Datta Ken"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-08-16 21:57
|
||||
release_group = FanVoxUA
|
||||
meta =
|
||||
hash = 18a83050fa77ae155d0df927c6142824c35094bb
|
||||
adjusted_episode_number = 0
|
||||
guid = t678039
|
||||
|
||||
[DEADDEADDEMONSDEDEDEDEDESTRUCTION]
|
||||
episode_index = 0
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "DEAD DEAD DEMONS DEDEDEDE DESTRUCTION"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-08-18 12:01
|
||||
release_group = FanVoxUA
|
||||
meta =
|
||||
hash = 40c5fbeed2f1c4c11fb7247eb81e72b70255f871
|
||||
adjusted_episode_number = 0
|
||||
guid = t679732
|
||||
|
||||
[ScottPilgrimTakesOff]
|
||||
episode_index = 4
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Scott Pilgrim Takes Off"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-06-29 19:41
|
||||
release_group = Altron320
|
||||
meta =
|
||||
hash = 25946318d080809e65f32249bd3184d265af2146
|
||||
adjusted_episode_number = 0
|
||||
guid = t679822
|
||||
|
||||
[VinlandSaga]
|
||||
episode_index = 2
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Vinland Saga"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 20-06-29 20:08
|
||||
release_group = 11FrYkT
|
||||
meta =
|
||||
hash = 2d1c961f8899156ff4ef995b1ad9b03bc75442d6
|
||||
adjusted_episode_number = 0
|
||||
guid = t111251
|
||||
|
14
app/db.py
Normal file
14
app/db.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Base(DeclarativeBase, MappedAsDataclass):
|
||||
pass
|
||||
|
||||
class ImagesCache(Base):
|
||||
__tablename__ = "image_cache"
|
||||
|
||||
codename: Mapped[str] = mapped_column(primary_key=True)
|
||||
image: Mapped[str] = mapped_column()
|
|
@ -1,9 +0,0 @@
|
|||
from app import db
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
class ImagesCache(db.Model):
|
||||
__tablename__ = "image_cache"
|
||||
|
||||
codename: Mapped[str] = mapped_column(primary_key=True)
|
||||
image: Mapped[str] = mapped_column()
|
|
@ -11,7 +11,7 @@ logging = DEBUG
|
|||
username =
|
||||
password =
|
||||
port = 9091
|
||||
host =
|
||||
host = 192.168.1.2
|
||||
protocol = http
|
||||
rpc = /transmission/rpc
|
||||
category = sonarr
|
||||
|
@ -20,7 +20,7 @@ tag = tolokaAnime
|
|||
[Toloka]
|
||||
username =
|
||||
password =
|
||||
client =
|
||||
default_download_dir =
|
||||
client = transmission
|
||||
default_download_dir = /media/HDD/Jellyfin/Anime
|
||||
wait_time = 10
|
||||
client_wait_time = 2
|
104
data/titles.ini
Normal file
104
data/titles.ini
Normal file
|
@ -0,0 +1,104 @@
|
|||
[HazurewakunoJoutaiIjouSkill]
|
||||
episode_index = 1
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Failure Frame: I Became the Strongest and Annihilated Everything with Low-Level Spells"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-16 14:40
|
||||
release_group = GlassMoon
|
||||
meta =
|
||||
hash = 79419315387ad5121829276e41c148f86bc27b56
|
||||
adjusted_episode_number = 0
|
||||
guid = t679885
|
||||
|
||||
[Metalocalypse]
|
||||
episode_index = 3
|
||||
season_number = 02
|
||||
ext_name = .mkv
|
||||
torrent_name = "Metalocalypse"
|
||||
download_dir = /media/HDD/Jellyfin/Cartoon
|
||||
publish_date = 24-09-01 23:17
|
||||
release_group = fanat22012
|
||||
meta =
|
||||
hash = b1d41c3d7671ca874a904896da3298b80fd4c648
|
||||
adjusted_episode_number = 0
|
||||
guid = t679197
|
||||
|
||||
[Noragami]
|
||||
episode_index = 1
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Noragami"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-08-04 13:26
|
||||
release_group = Amanogawa
|
||||
meta =
|
||||
hash = a5b5c50cc287839af87785d49a90deaccd26672d
|
||||
adjusted_episode_number = 0
|
||||
guid = t680502
|
||||
|
||||
[MakeHeroinegaOosugiru]
|
||||
episode_index = 1
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Make Heroine ga Oosugiru"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-15 21:29
|
||||
release_group = GlassMoon
|
||||
meta =
|
||||
hash = e40b7ed0662832ce1ac5201272ba4697c877d8f9
|
||||
adjusted_episode_number = 0
|
||||
guid = t680215
|
||||
|
||||
[GimaiSeikatsu]
|
||||
episode_index = 1
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Gimai Seikatsu"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-20 22:03
|
||||
release_group = GlassMoon
|
||||
meta =
|
||||
hash = 833ce986aae5ead89ee65f90867bb3d87fc96ea2
|
||||
adjusted_episode_number = 0
|
||||
guid = t679864
|
||||
|
||||
[OokamitoKoushinryouMerchantMeetstheWiseWolf]
|
||||
episode_index = 2
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Ookami to Koushinryou: Merchant Meets the Wise Wolf"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-17 21:48
|
||||
release_group = GlassMoon
|
||||
meta =
|
||||
hash = 2dc1117d464bc5a4363711b81a8027afbc456bca
|
||||
adjusted_episode_number = 0
|
||||
guid = t677900
|
||||
|
||||
[demon_academy]
|
||||
episode_index = 2
|
||||
season_number = 02
|
||||
ext_name = .mkv
|
||||
torrent_name = "The Misfit of Demon King Academy"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-01 17:06
|
||||
release_group = FanVoxUA
|
||||
meta =
|
||||
hash = 4810c24d846237d638d7bfb33d6d3fba68d031d8
|
||||
adjusted_episode_number = 0
|
||||
guid = t664369
|
||||
|
||||
[AngelBeats]
|
||||
episode_index = 0
|
||||
season_number = 01
|
||||
ext_name = .mkv
|
||||
torrent_name = "Angel.Beats"
|
||||
download_dir = /media/HDD/Jellyfin/Anime
|
||||
publish_date = 24-09-19 23:53
|
||||
release_group = stark62
|
||||
meta =
|
||||
hash = abb7518024aa0a875b8d0a7841121e84936e2120
|
||||
adjusted_episode_number = 0
|
||||
guid = t49611
|
||||
|
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
3
frontend/.vscode/extensions.json
vendored
Normal file
3
frontend/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
1
frontend/README.md
Normal file
1
frontend/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Toloka2Web v2 Vue Edition
|
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
26
frontend/package.json
Normal file
26
frontend/package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.5",
|
||||
"beercss": "^3.6.13",
|
||||
"material-dynamic-colors": "^1.1.2",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.4.1",
|
||||
"vite-plugin-pages": "^0.32.3",
|
||||
"vue-tsc": "^2.0.29"
|
||||
}
|
||||
|
||||
}
|
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
23
frontend/src/App.vue
Normal file
23
frontend/src/App.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import "beercss";
|
||||
import "material-dynamic-colors";
|
||||
|
||||
import Navbar from './components/Navbar.vue'
|
||||
|
||||
function getCookie(id: string) {
|
||||
let value = document.cookie.match('(^|;)?' + id + '=([^;]*)(;|$)');
|
||||
return value ? unescape(value[2]) : null;
|
||||
}
|
||||
|
||||
ui("mode", getCookie('my-mode')!);
|
||||
ui("theme", getCookie('my-color')!);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Navbar />
|
||||
<main class="responsive">
|
||||
<router-view />
|
||||
</main>
|
||||
</template>
|
1
frontend/src/assets/vue.svg
Normal file
1
frontend/src/assets/vue.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
69
frontend/src/components/Navbar.vue
Normal file
69
frontend/src/components/Navbar.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
const pathname = location.pathname
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<nav class="left drawer primary-text">
|
||||
<header>
|
||||
<nav>
|
||||
<img src="https://www.beercss.com/favicon.png" class="circle">
|
||||
<h6>Toloka2Web MD3</h6>
|
||||
</nav>
|
||||
</header>
|
||||
<a :class="(pathname == '/') ? 'active' : ''" href="/">
|
||||
<i>home</i>
|
||||
<div>Головна</div>
|
||||
</a>
|
||||
<a :class="(pathname == '/add') ? 'active' : ''" href="/add">
|
||||
<i>add</i>
|
||||
<div>Додати</div>
|
||||
</a>
|
||||
<a :class="(pathname == '/settings') ? 'active' : ''" href="/settings">
|
||||
<i>settings</i>
|
||||
<div>Налаштування</div>
|
||||
</a>
|
||||
<div class="divider small-margin"></div>
|
||||
<label>Інше</label>
|
||||
<a :class="(pathname == '/about') ? 'active' : ''" href="/about">
|
||||
<i>person</i>
|
||||
<div>Про застосунок</div>
|
||||
</a>
|
||||
</nav>
|
||||
<nav class="left m">
|
||||
<header>
|
||||
<img src="https://www.beercss.com/favicon.png" class="circle">
|
||||
</header>
|
||||
<a>
|
||||
<i>home</i>
|
||||
<div>Home</div>
|
||||
</a>
|
||||
<a>
|
||||
<i>search</i>
|
||||
<div>Search</div>
|
||||
</a>
|
||||
<a>
|
||||
<i>settings</i>
|
||||
<div>Settings</div>
|
||||
</a>
|
||||
<a>
|
||||
<i>more_vert</i>
|
||||
<div>More</div>
|
||||
</a>
|
||||
</nav>
|
||||
<nav class="bottom s">
|
||||
<a>
|
||||
<i>home</i>
|
||||
</a>
|
||||
<a>
|
||||
<i>search</i>
|
||||
</a>
|
||||
<a>
|
||||
<i>settings</i>
|
||||
</a>
|
||||
<a>
|
||||
<i>more_vert</i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</template>
|
16
frontend/src/main.ts
Normal file
16
frontend/src/main.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import routes from '~pages'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.mount('#app')
|
||||
|
35
frontend/src/pages/about.vue
Normal file
35
frontend/src/pages/about.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
|
||||
<div class="padding absolute center middle">
|
||||
<article class="medium middle-align center-align">
|
||||
<div>
|
||||
<i class="extra">cloud</i>
|
||||
<h5 class="primary-text">Toloka2Web v2</h5>
|
||||
<p class="secondary-text">Зручний сайт для завантаження аніме до медіасерверу Jellyfin</p>
|
||||
<div class="space"></div>
|
||||
<nav class="center-align">
|
||||
<a href="https://github.com"><i class="chip circle">code</i></a>
|
||||
<a href="https://github.com"><i class="chip circle">bug_report</i></a>
|
||||
<a href="https://mastodon.com"><i class="chip circle">diversity_3</i></a>
|
||||
</nav>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
140
frontend/src/pages/add.vue
Normal file
140
frontend/src/pages/add.vue
Normal file
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
|
||||
<form @submit.prevent="searchTorrent">
|
||||
<div class="field large prefix round fill">
|
||||
<i class="front">search</i>
|
||||
<input v-model="torrentId" type="text" placeholder="https://toloka.to/t680082">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<article v-if="Object.keys(torrentInfo).length > 0" class="no-padding">
|
||||
<div class="grid large-space">
|
||||
<div class="s12 m6 l3">
|
||||
<img class="responsive" :src=torrentInfo.img>
|
||||
</div>
|
||||
<div class="s9">
|
||||
<div class="padding">
|
||||
<h5 class="primary-text">{{ torrentInfo.name }}</h5>
|
||||
|
||||
<hr class="medium">
|
||||
|
||||
<div>
|
||||
<a class="chip no-margin"><i>language_gb_english</i><span>{{ torrentInfo.size }}</span></a>
|
||||
<a class="chip no-margin"><i>signature</i><span>{{ torrentInfo.author }}</span></a>
|
||||
<a class="chip no-margin"><i>calendar_month</i><span>{{ torrentInfo.date }}</span></a>
|
||||
<a class="chip no-margin"><i>star_rate</i><span>{{ torrentInfo.rating }}</span></a>
|
||||
<a class="chip no-margin"><i>thumb_up</i><span>{{ torrentInfo.thanks }}</span></a>
|
||||
</div>
|
||||
|
||||
<hr class="large">
|
||||
|
||||
{{ torrentInfo.description }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article v-if="Object.keys(torrentInfo).length > 0">
|
||||
<form @submit.prevent="addTitle">
|
||||
<div class="grid">
|
||||
|
||||
<div class="field label prefix border s6">
|
||||
<i>folder</i>
|
||||
<input type="text" v-model="dirname">
|
||||
<label>Назва директорії</label>
|
||||
</div>
|
||||
|
||||
<div class="field label prefix border s6">
|
||||
<i>numbers</i>
|
||||
<select v-model="seasonIndex">
|
||||
<option v-for="n in 10">{{ n }}</option>
|
||||
</select>
|
||||
<label>Індекс сезону</label>
|
||||
</div>
|
||||
|
||||
<div class="field label prefix border s6">
|
||||
<i>numbers</i>
|
||||
<select v-model="episodeIndex">
|
||||
<option v-for="n in extractNumbers(torrentInfo.files[0].file_name)">{{ n }}</option>
|
||||
</select>
|
||||
<label>Індекс епізоду</label>
|
||||
</div>
|
||||
|
||||
<div class="field label prefix border s6">
|
||||
<i>route</i>
|
||||
<input type="text" v-model="filepath">
|
||||
<label>Місце завантаження</label>
|
||||
</div>
|
||||
|
||||
<div class="field label prefix border s6">
|
||||
<i>numbers</i>
|
||||
<input type="number" v-model="adjustedEpisodeNumber" value="0">
|
||||
<label>Скільки додати до епізоду</label>
|
||||
</div>
|
||||
|
||||
<button class="border small-round s12" type="submit">
|
||||
<i>add</i>
|
||||
<span>Додати</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
torrentId: '',
|
||||
torrentInfo: {},
|
||||
config_: {},
|
||||
dirname: '',
|
||||
seasonIndex: 0,
|
||||
episodeIndex: 1,
|
||||
filepath: '',
|
||||
adjustedEpisodeNumber: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getConfig()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async searchTorrent() {
|
||||
void await axios.get(`/api/toloka/${this.torrentId.replace(`https://toloka.to`, ``)}`).then(response => {
|
||||
this.torrentInfo = response.data
|
||||
this.dirname = this.torrentInfo.files[0].folder_name
|
||||
})
|
||||
},
|
||||
async getConfig() {
|
||||
void await axios.get(`/api/config/`).then(response => {
|
||||
this.config_ = response.data
|
||||
this.filepath = this.config_.Toloka.default_download_dir
|
||||
})
|
||||
},
|
||||
extractNumbers(input) {
|
||||
const numbers = input.match(/\d+/g);
|
||||
return numbers ? numbers.map((num, index) => `${index + 1}. ${num}`) : [];
|
||||
},
|
||||
async addTitle() {
|
||||
const formData = {
|
||||
dirname: this.dirname,
|
||||
seasonIndex: this.seasonIndex,
|
||||
episodeIndex: this.episodeIndex,
|
||||
filepath: this.filepath,
|
||||
adjustedEpisodeNumber: this.adjustedEpisodeNumber,
|
||||
tolokaUrl: this.torrentId
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/add', formData);
|
||||
} catch (error) {
|
||||
console.error("Error~", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
149
frontend/src/pages/index.vue
Normal file
149
frontend/src/pages/index.vue
Normal file
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
|
||||
|
||||
<form @submit.prevent="filterTitles">
|
||||
<div class="field large prefix round fill">
|
||||
<i class="front">search</i>
|
||||
<input v-model="search" type="text">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<nav>
|
||||
<a class="chip" onclick="updateByCodename('')">
|
||||
<i id="update_all_icon">done_all</i>
|
||||
<span>Завантажити нові серії</span>
|
||||
</a>
|
||||
<a class="chip" @click="fetchTorrents">
|
||||
<i>restart_alt</i>
|
||||
<span>Оновити список</span>
|
||||
</a>
|
||||
<div class="max"></div>
|
||||
<button class="round fill">
|
||||
<span>Сортування</span>
|
||||
<i>arrow_drop_down</i>
|
||||
<menu id="sort-menu">
|
||||
<a @click="sortTitles('name')">За назвою</a>
|
||||
<a @click="sortTitles('date')">За датою</a>
|
||||
<a @click="sortTitles('release')">За релізером</a>
|
||||
</menu>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<article class="no-padding" v-for="title in titles">
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l3">
|
||||
<img class="responsive " :src=title.image>
|
||||
</div>
|
||||
<div class="s9">
|
||||
<div class="padding">
|
||||
<h5 class="primary-text">{{ title.torrent_name }} ({{ title.codename }})</h5>
|
||||
|
||||
<hr class="medium">
|
||||
|
||||
<div>
|
||||
<a class="chip no-margin"><i>route</i><span>{{ title.download_dir }}</span></a>
|
||||
<a class="chip no-margin"><i>signature</i><span>{{ title.release_group }}</span></a>
|
||||
<a class="chip no-margin"><i>calendar_month</i><span>{{ title.publish_date }}</span></a>
|
||||
<a class="chip no-margin"><i>tag</i><span>{{ title.hash }}</span></a>
|
||||
</div>
|
||||
|
||||
<hr class="large">
|
||||
|
||||
<div>
|
||||
<a class="chip primary-border no-margin" :href="'https://toloka.to/' + title.guid">
|
||||
<i>link</i>
|
||||
<span>Посилання</span>
|
||||
</a>
|
||||
<a class="chip green-border no-margin" @click="updateByCodename(title.codename)">
|
||||
<i>update</i>
|
||||
<span>Оновити</span>
|
||||
</a>
|
||||
<a class="chip amber-border no-margin" @click="editByCodename(title.codename)">
|
||||
<i>edit</i>
|
||||
<span>Редагувати</span>
|
||||
</a>
|
||||
<a class="chip red-border no-margin" @click="deleteByCodename(title.codename)">
|
||||
<i>delete</i>
|
||||
<span>Видалити</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<dialog :class="{ 'active': isUpdated }">
|
||||
<h5 class="primary-text">{{ update.response_code }}</h5>
|
||||
<div v-for="operation in update.operation_logs">
|
||||
<p>* {{ operation }}</p>
|
||||
</div>
|
||||
<nav class="right-align no-space">
|
||||
<button @click="updateOk" class="transparent link">Гаразд</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
titles: [], // Titles list
|
||||
search: '', // Search bar
|
||||
update: {}, // Update stuff, notif
|
||||
isUpdated: false // Update notif
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchTorrents()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchTorrents() {
|
||||
void await axios.get(`/api/titles`).then(response => {
|
||||
this.titles = response.data
|
||||
})
|
||||
},
|
||||
filterTitles() {
|
||||
if (!this.search || typeof this.search !== 'string') {
|
||||
this.fetchTorrents()
|
||||
}
|
||||
|
||||
const filteredTitles = this.titles.filter(title =>
|
||||
title.torrent_name && title.torrent_name.includes(this.search)
|
||||
);
|
||||
|
||||
this.titles = filteredTitles;
|
||||
},
|
||||
async updateByCodename(codename) {
|
||||
void await axios.get(`/api/update/${codename}`).then(response => {
|
||||
this.update = response.data
|
||||
this.isUpdated = true
|
||||
})
|
||||
},
|
||||
async deleteByCodename(codename) {
|
||||
void await axios.get(`/api/delete/${codename}`).then(response => {
|
||||
this.update.response_code = `Видалення`
|
||||
this.update.operation_logs = []
|
||||
this.update.operation_logs[0] = response.data
|
||||
this.fetchTorrents()
|
||||
this.isUpdated = true
|
||||
})
|
||||
},
|
||||
updateOk(codename) {
|
||||
this.isUpdated = false
|
||||
},
|
||||
sortTitles(criteria) {
|
||||
if (criteria === 'name') {
|
||||
this.titles.sort((a, b) => a.torrent_name.localeCompare(b.torrent_name));
|
||||
} else if (criteria === 'date') {
|
||||
this.titles.sort((a, b) => a.publish_date.localeCompare(b.publish_date));
|
||||
} else if (criteria === 'release') {
|
||||
this.titles.sort((a, b) => a.release_group.localeCompare(b.release_group));
|
||||
}
|
||||
ui('#sort-menu');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
43
frontend/src/pages/settings.vue
Normal file
43
frontend/src/pages/settings.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
|
||||
<p class="large-text">
|
||||
<h6><i>settings</i> Налаштування WIP</h6>
|
||||
</p>
|
||||
|
||||
<div class="large-space"></div>
|
||||
|
||||
|
||||
<article class="primary-text">
|
||||
<a class="row wave"><i>person</i> Обліковий запис Tokoka.to</a>
|
||||
<hr>
|
||||
<a class="row wave"><i>hub</i> BitTorrent</a>
|
||||
<hr>
|
||||
<a class="row wave"><i>comic_bubble</i> Аніме</a>
|
||||
<hr>
|
||||
<a class="row wave"><i>bug_report</i> Дебагінг</a>
|
||||
</article>
|
||||
|
||||
<article class="secondary-text">
|
||||
<a class="row wave"><i>title</i> Toloka2Web v2 Vue Version</a>
|
||||
<hr>
|
||||
<a class="row wave"><i>conversion_path</i> v1.0.0</a>
|
||||
</article>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
0
frontend/src/style.css
Normal file
0
frontend/src/style.css
Normal file
2
frontend/src/vite-env.d.ts
vendored
Normal file
2
frontend/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pages/client" />
|
24
frontend/tsconfig.app.json
Normal file
24
frontend/tsconfig.app.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
1
frontend/tsconfig.app.tsbuildinfo
Normal file
1
frontend/tsconfig.app.tsbuildinfo
Normal file
|
@ -0,0 +1 @@
|
|||
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/App.vue","./src/components/Navbar.vue","./src/pages/index.vue"],"version":"5.6.2"}
|
7
frontend/tsconfig.json
Normal file
7
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
22
frontend/tsconfig.node.json
Normal file
22
frontend/tsconfig.node.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
1
frontend/tsconfig.node.tsbuildinfo
Normal file
1
frontend/tsconfig.node.tsbuildinfo
Normal file
|
@ -0,0 +1 @@
|
|||
{"root":["./vite.config.ts"],"version":"5.6.2"}
|
18
frontend/vite.config.ts
Normal file
18
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Pages from 'vite-plugin-pages'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ }) => {
|
||||
return defineConfig({
|
||||
plugins: [vue(), Pages()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
// Forward frontend dev server request for /api to flask dev server
|
||||
target: 'http://localhost:5000/',
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
3
run.py
Normal file
3
run.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from app import app
|
||||
|
||||
app.run(port=5000)
|
Loading…
Reference in a new issue