refactor: externalize app configuration to JSON and unify UI color scheme constants
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"app_name": "fcco Converter",
|
||||||
|
"app_version": "2.0.0",
|
||||||
|
"repo_owner": "public",
|
||||||
|
"repo_name": "fcco-Converter",
|
||||||
|
"gitea_api_base": "https://git.joelunger.de/api/v1"
|
||||||
|
}
|
||||||
@@ -7,8 +7,59 @@ Verwendet pathlib für alle Pfadoperationen.
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import platform
|
import platform
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ---- Globale App-Informationen (aus config.json) ----
|
||||||
|
APP_INFO = {
|
||||||
|
"app_name": "fcco Converter",
|
||||||
|
"app_version": "2.0.0",
|
||||||
|
"repo_owner": "public",
|
||||||
|
"repo_name": "fcco-Converter",
|
||||||
|
"gitea_api_base": "https://git.joelunger.de/api/v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _load_app_info():
|
||||||
|
try:
|
||||||
|
# Finde config.json relativ zum Skript oder im PyInstaller Bundle
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
base_dir = Path(sys.executable).parent
|
||||||
|
else:
|
||||||
|
base_dir = Path(__file__).parent.parent
|
||||||
|
|
||||||
|
config_path = base_dir / "config.json"
|
||||||
|
if config_path.exists():
|
||||||
|
with open(config_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
APP_INFO.update(data)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
_load_app_info()
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Farbschema
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
T = {
|
||||||
|
"bg": "#000000",
|
||||||
|
"surface": "#111111",
|
||||||
|
"elevated": "#1a1a1a",
|
||||||
|
"card": "#222222",
|
||||||
|
"input": "#000000",
|
||||||
|
"accent": "#ffffff",
|
||||||
|
"accent_h": "#e6e6e6",
|
||||||
|
"success": "#ffffff",
|
||||||
|
"success_h": "#e6e6e6",
|
||||||
|
"warn": "#ffffff",
|
||||||
|
"error": "#ffffff",
|
||||||
|
"text": "#ffffff",
|
||||||
|
"text2": "#b3b3b3",
|
||||||
|
"muted": "#808080",
|
||||||
|
"border": "#333333",
|
||||||
|
"btn2": "#1a1a1a",
|
||||||
|
"btn2_h": "#333333",
|
||||||
|
}
|
||||||
|
|
||||||
# Standardmäßige Export-Kategorien
|
# Standardmäßige Export-Kategorien
|
||||||
DEFAULT_EXPORT_PATHS = {
|
DEFAULT_EXPORT_PATHS = {
|
||||||
|
|||||||
+33
-48
@@ -29,6 +29,8 @@ from .config import (
|
|||||||
load_settings,
|
load_settings,
|
||||||
save_settings,
|
save_settings,
|
||||||
save_export_history,
|
save_export_history,
|
||||||
|
T,
|
||||||
|
APP_INFO,
|
||||||
)
|
)
|
||||||
from .converter import run_batch_conversion, _ensure_ffmpeg
|
from .converter import run_batch_conversion, _ensure_ffmpeg
|
||||||
from .metadata import (
|
from .metadata import (
|
||||||
@@ -50,25 +52,7 @@ IS_MACOS = platform.system() == "Darwin"
|
|||||||
# Farbschema
|
# Farbschema
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
T = {
|
# (Farben werden jetzt aus config.T importiert)
|
||||||
"bg": "#000000",
|
|
||||||
"surface": "#111111",
|
|
||||||
"elevated": "#1a1a1a",
|
|
||||||
"card": "#222222",
|
|
||||||
"input": "#000000",
|
|
||||||
"accent": "#ffffff",
|
|
||||||
"accent_h": "#e6e6e6",
|
|
||||||
"success": "#ffffff",
|
|
||||||
"success_h": "#e6e6e6",
|
|
||||||
"warn": "#ffffff",
|
|
||||||
"error": "#ffffff",
|
|
||||||
"text": "#ffffff",
|
|
||||||
"text2": "#b3b3b3",
|
|
||||||
"muted": "#808080",
|
|
||||||
"border": "#333333",
|
|
||||||
"btn2": "#1a1a1a",
|
|
||||||
"btn2_h": "#333333",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
@@ -82,7 +66,7 @@ class App(ctk.CTk):
|
|||||||
ctk.set_appearance_mode("dark")
|
ctk.set_appearance_mode("dark")
|
||||||
ctk.set_default_color_theme("blue")
|
ctk.set_default_color_theme("blue")
|
||||||
|
|
||||||
self.title(f"fcco Converter v{get_current_version()}")
|
self.title(f"{APP_INFO['app_name']} V{APP_INFO['app_version']}")
|
||||||
self.geometry("900x700")
|
self.geometry("900x700")
|
||||||
self.minsize(700, 500)
|
self.minsize(700, 500)
|
||||||
self.configure(fg_color=T["bg"])
|
self.configure(fg_color=T["bg"])
|
||||||
@@ -227,8 +211,8 @@ class App(ctk.CTk):
|
|||||||
self.conv_btn = self._btn(
|
self.conv_btn = self._btn(
|
||||||
tb, text=" Konvertieren", command=self._convert,
|
tb, text=" Konvertieren", command=self._convert,
|
||||||
image=self.icons.play, width=160, height=36,
|
image=self.icons.play, width=160, height=36,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"],
|
||||||
text_color="#000000", font=ctk.CTkFont(size=14, weight="bold"))
|
text_color="#ffffff", font=ctk.CTkFont(size=14, weight="bold"))
|
||||||
self.conv_btn.grid(row=0, column=3, sticky="e")
|
self.conv_btn.grid(row=0, column=3, sticky="e")
|
||||||
|
|
||||||
def _make_filelist(self):
|
def _make_filelist(self):
|
||||||
@@ -576,7 +560,7 @@ class App(ctk.CTk):
|
|||||||
ctype_var = ctk.StringVar(value="Gottesdienst")
|
ctype_var = ctk.StringVar(value="Gottesdienst")
|
||||||
for i, opt in enumerate(["Gottesdienst", "Hochzeit", "Weissagung", "Jugend", "Sonstiges"]):
|
for i, opt in enumerate(["Gottesdienst", "Hochzeit", "Weissagung", "Jugend", "Sonstiges"]):
|
||||||
ctk.CTkRadioButton(mid, text=opt, variable=ctype_var, value=opt,
|
ctk.CTkRadioButton(mid, text=opt, variable=ctype_var, value=opt,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"], checkmark_color="#ffffff",
|
||||||
border_color=T["border"], text_color=T["text"],
|
border_color=T["border"], text_color=T["text"],
|
||||||
font=ctk.CTkFont(size=12)
|
font=ctk.CTkFont(size=12)
|
||||||
).grid(row=i+1, column=0, sticky="w", pady=1, padx=(0, 24))
|
).grid(row=i+1, column=0, sticky="w", pady=1, padx=(0, 24))
|
||||||
@@ -623,8 +607,8 @@ class App(ctk.CTk):
|
|||||||
|
|
||||||
self._btn(bf, text=" Konvertieren starten", command=_ok,
|
self._btn(bf, text=" Konvertieren starten", command=_ok,
|
||||||
image=self.icons.play, width=180, height=34,
|
image=self.icons.play, width=180, height=34,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"],
|
||||||
text_color="#000000", font=ctk.CTkFont(weight="bold")
|
text_color="#ffffff", font=ctk.CTkFont(weight="bold")
|
||||||
).pack(side="right")
|
).pack(side="right")
|
||||||
|
|
||||||
# Fokus (KEIN grab_set auf macOS!)
|
# Fokus (KEIN grab_set auf macOS!)
|
||||||
@@ -650,10 +634,11 @@ class App(ctk.CTk):
|
|||||||
|
|
||||||
tabs = ctk.CTkTabview(w, fg_color=T["surface"],
|
tabs = ctk.CTkTabview(w, fg_color=T["surface"],
|
||||||
segmented_button_fg_color=T["elevated"],
|
segmented_button_fg_color=T["elevated"],
|
||||||
segmented_button_selected_color=T["accent"],
|
segmented_button_selected_color=T["border"],
|
||||||
segmented_button_selected_hover_color=T["accent_h"],
|
segmented_button_selected_hover_color=T["btn2_h"],
|
||||||
segmented_button_unselected_color=T["elevated"],
|
segmented_button_unselected_color=T["elevated"],
|
||||||
segmented_button_unselected_hover_color=T["btn2_h"])
|
segmented_button_unselected_hover_color=T["btn2_h"],
|
||||||
|
text_color=T["text"])
|
||||||
tabs.pack(fill="both", expand=True, padx=12, pady=(12, 4))
|
tabs.pack(fill="both", expand=True, padx=12, pady=(12, 4))
|
||||||
|
|
||||||
t1 = tabs.add("Allgemein")
|
t1 = tabs.add("Allgemein")
|
||||||
@@ -681,8 +666,8 @@ class App(ctk.CTk):
|
|||||||
font=ctk.CTkFont(size=12)).pack(anchor="w")
|
font=ctk.CTkFont(size=12)).pack(anchor="w")
|
||||||
av = ctk.StringVar(value=self.settings.get("appearance_mode", "Dark"))
|
av = ctk.StringVar(value=self.settings.get("appearance_mode", "Dark"))
|
||||||
ctk.CTkOptionMenu(s1, values=["Dark", "Light", "System"], variable=av,
|
ctk.CTkOptionMenu(s1, values=["Dark", "Light", "System"], variable=av,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"],
|
button_hover_color=T["btn2_h"], text_color=T["text"],
|
||||||
command=lambda v: ctk.set_appearance_mode(v)
|
command=lambda v: ctk.set_appearance_mode(v)
|
||||||
).pack(anchor="w", pady=(2, 10))
|
).pack(anchor="w", pady=(2, 10))
|
||||||
wg["appearance"] = av
|
wg["appearance"] = av
|
||||||
@@ -690,7 +675,7 @@ class App(ctk.CTk):
|
|||||||
self._slabel(s1, "Updates")
|
self._slabel(s1, "Updates")
|
||||||
uv = ctk.BooleanVar(value=self.settings.get("check_updates_on_start", True))
|
uv = ctk.BooleanVar(value=self.settings.get("check_updates_on_start", True))
|
||||||
ctk.CTkCheckBox(s1, text="Beim Start auf Updates prüfen", variable=uv,
|
ctk.CTkCheckBox(s1, text="Beim Start auf Updates prüfen", variable=uv,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"], checkmark_color="#ffffff",
|
||||||
border_color=T["border"], text_color=T["text"],
|
border_color=T["border"], text_color=T["text"],
|
||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=4)
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=4)
|
||||||
wg["auto_update"] = uv
|
wg["auto_update"] = uv
|
||||||
@@ -702,16 +687,16 @@ class App(ctk.CTk):
|
|||||||
self._slabel(s2, "Ausgabeformat")
|
self._slabel(s2, "Ausgabeformat")
|
||||||
fv = ctk.StringVar(value=self.settings.get("audio", {}).get("output_format", "mp3"))
|
fv = ctk.StringVar(value=self.settings.get("audio", {}).get("output_format", "mp3"))
|
||||||
ctk.CTkOptionMenu(s2, values=OUTPUT_FORMAT_OPTIONS, variable=fv,
|
ctk.CTkOptionMenu(s2, values=OUTPUT_FORMAT_OPTIONS, variable=fv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 10))
|
).pack(anchor="w", pady=(2, 10))
|
||||||
wg["fmt"] = fv
|
wg["fmt"] = fv
|
||||||
|
|
||||||
self._slabel(s2, "Encoding")
|
self._slabel(s2, "Encoding")
|
||||||
mv = ctk.StringVar(value=self.settings.get("audio", {}).get("encoding_mode", "CBR"))
|
mv = ctk.StringVar(value=self.settings.get("audio", {}).get("encoding_mode", "CBR"))
|
||||||
ctk.CTkOptionMenu(s2, values=ENCODING_MODE_OPTIONS, variable=mv,
|
ctk.CTkOptionMenu(s2, values=ENCODING_MODE_OPTIONS, variable=mv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 6))
|
).pack(anchor="w", pady=(2, 6))
|
||||||
wg["mode"] = mv
|
wg["mode"] = mv
|
||||||
|
|
||||||
@@ -719,8 +704,8 @@ class App(ctk.CTk):
|
|||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
||||||
bv = ctk.StringVar(value=self.settings.get("audio", {}).get("bitrate", "192"))
|
bv = ctk.StringVar(value=self.settings.get("audio", {}).get("bitrate", "192"))
|
||||||
ctk.CTkOptionMenu(s2, values=BITRATE_OPTIONS, variable=bv,
|
ctk.CTkOptionMenu(s2, values=BITRATE_OPTIONS, variable=bv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 6))
|
).pack(anchor="w", pady=(2, 6))
|
||||||
wg["br"] = bv
|
wg["br"] = bv
|
||||||
|
|
||||||
@@ -728,8 +713,8 @@ class App(ctk.CTk):
|
|||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
||||||
vv = ctk.StringVar(value=self.settings.get("audio", {}).get("vbr_quality", "2"))
|
vv = ctk.StringVar(value=self.settings.get("audio", {}).get("vbr_quality", "2"))
|
||||||
ctk.CTkOptionMenu(s2, values=VBR_QUALITY_OPTIONS, variable=vv,
|
ctk.CTkOptionMenu(s2, values=VBR_QUALITY_OPTIONS, variable=vv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 10))
|
).pack(anchor="w", pady=(2, 10))
|
||||||
wg["vbr"] = vv
|
wg["vbr"] = vv
|
||||||
|
|
||||||
@@ -738,8 +723,8 @@ class App(ctk.CTk):
|
|||||||
font=ctk.CTkFont(size=12)).pack(anchor="w")
|
font=ctk.CTkFont(size=12)).pack(anchor="w")
|
||||||
sv = ctk.StringVar(value=self.settings.get("audio", {}).get("sample_rate", "Original"))
|
sv = ctk.StringVar(value=self.settings.get("audio", {}).get("sample_rate", "Original"))
|
||||||
ctk.CTkOptionMenu(s2, values=SAMPLERATE_OPTIONS, variable=sv,
|
ctk.CTkOptionMenu(s2, values=SAMPLERATE_OPTIONS, variable=sv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 6))
|
).pack(anchor="w", pady=(2, 6))
|
||||||
wg["sr"] = sv
|
wg["sr"] = sv
|
||||||
|
|
||||||
@@ -747,15 +732,15 @@ class App(ctk.CTk):
|
|||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=(4, 0))
|
||||||
cv = ctk.StringVar(value=self.settings.get("audio", {}).get("channels", "Original"))
|
cv = ctk.StringVar(value=self.settings.get("audio", {}).get("channels", "Original"))
|
||||||
ctk.CTkOptionMenu(s2, values=CHANNEL_OPTIONS, variable=cv,
|
ctk.CTkOptionMenu(s2, values=CHANNEL_OPTIONS, variable=cv,
|
||||||
fg_color=T["input"], button_color=T["accent"],
|
fg_color=T["input"], button_color=T["btn2"],
|
||||||
button_hover_color=T["accent_h"], text_color=T["text"]
|
button_hover_color=T["btn2_h"], text_color=T["text"]
|
||||||
).pack(anchor="w", pady=(2, 10))
|
).pack(anchor="w", pady=(2, 10))
|
||||||
wg["ch"] = cv
|
wg["ch"] = cv
|
||||||
|
|
||||||
self._slabel(s2, "Optionen")
|
self._slabel(s2, "Optionen")
|
||||||
ov = ctk.BooleanVar(value=self.settings.get("audio", {}).get("overwrite_existing", True))
|
ov = ctk.BooleanVar(value=self.settings.get("audio", {}).get("overwrite_existing", True))
|
||||||
ctk.CTkCheckBox(s2, text="Bestehende Dateien überschreiben", variable=ov,
|
ctk.CTkCheckBox(s2, text="Bestehende Dateien überschreiben", variable=ov,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"], checkmark_color="#ffffff",
|
||||||
border_color=T["border"], text_color=T["text"],
|
border_color=T["border"], text_color=T["text"],
|
||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
||||||
wg["ow"] = ov
|
wg["ow"] = ov
|
||||||
@@ -796,14 +781,14 @@ class App(ctk.CTk):
|
|||||||
self._slabel(s3, "Automatisierung")
|
self._slabel(s3, "Automatisierung")
|
||||||
atv = ctk.BooleanVar(value=self.settings.get("metadata", {}).get("auto_track_number", True))
|
atv = ctk.BooleanVar(value=self.settings.get("metadata", {}).get("auto_track_number", True))
|
||||||
ctk.CTkCheckBox(s3, text="Tracknummer automatisch vergeben", variable=atv,
|
ctk.CTkCheckBox(s3, text="Tracknummer automatisch vergeben", variable=atv,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"], checkmark_color="#ffffff",
|
||||||
border_color=T["border"], text_color=T["text"],
|
border_color=T["border"], text_color=T["text"],
|
||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
||||||
wg["auto_track"] = atv
|
wg["auto_track"] = atv
|
||||||
|
|
||||||
aav = ctk.BooleanVar(value=self.settings.get("metadata", {}).get("auto_album_from_folder", True))
|
aav = ctk.BooleanVar(value=self.settings.get("metadata", {}).get("auto_album_from_folder", True))
|
||||||
ctk.CTkCheckBox(s3, text="Albumname aus Ordnername ableiten", variable=aav,
|
ctk.CTkCheckBox(s3, text="Albumname aus Ordnername ableiten", variable=aav,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"], checkmark_color="#ffffff",
|
||||||
border_color=T["border"], text_color=T["text"],
|
border_color=T["border"], text_color=T["text"],
|
||||||
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
font=ctk.CTkFont(size=12)).pack(anchor="w", pady=3)
|
||||||
wg["auto_album"] = aav
|
wg["auto_album"] = aav
|
||||||
@@ -848,8 +833,8 @@ class App(ctk.CTk):
|
|||||||
|
|
||||||
self._btn(bf, text=" Speichern", command=lambda: self._save_s(wg, w),
|
self._btn(bf, text=" Speichern", command=lambda: self._save_s(wg, w),
|
||||||
image=self.icons.save, width=140, height=34,
|
image=self.icons.save, width=140, height=34,
|
||||||
fg_color=T["accent"], hover_color=T["accent_h"],
|
fg_color=T["btn2"], hover_color=T["btn2_h"],
|
||||||
text_color="#000000", font=ctk.CTkFont(weight="bold")
|
text_color="#ffffff", font=ctk.CTkFont(weight="bold")
|
||||||
).pack(side="right")
|
).pack(side="right")
|
||||||
|
|
||||||
w.after(50, lambda: (w.lift(), w.focus_force()))
|
w.after(50, lambda: (w.lift(), w.focus_force()))
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ from typing import Callable
|
|||||||
from urllib.request import urlopen, Request
|
from urllib.request import urlopen, Request
|
||||||
from urllib.error import URLError
|
from urllib.error import URLError
|
||||||
import json
|
import json
|
||||||
|
from .config import APP_INFO
|
||||||
|
|
||||||
# Aktuelle Version des Programms
|
# Aktuelle Version des Programms und Repos (aus config.json)
|
||||||
CURRENT_VERSION = "2.0.0"
|
CURRENT_VERSION = APP_INFO["app_version"]
|
||||||
|
GITEA_API_BASE = APP_INFO["gitea_api_base"]
|
||||||
# Gitea API-URL für Releases
|
REPO_OWNER = APP_INFO["repo_owner"]
|
||||||
GITEA_API_BASE = "https://git.joelunger.de/api/v1"
|
REPO_NAME = APP_INFO["repo_name"]
|
||||||
REPO_OWNER = "public"
|
|
||||||
REPO_NAME = "fcco-Converter"
|
|
||||||
RELEASES_URL = f"{GITEA_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/releases"
|
RELEASES_URL = f"{GITEA_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/releases"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user