Refactor UI startup and stylesheet handling
Moved the main application stylesheet to a new src/gui/ytsage_stylesheet.py module and applied it via StyleSheet.MAIN. Deferred blocking startup checks (FFmpeg, yt-dlp, Deno, updates) until after the UI is shown to improve responsiveness. Improved subtitle file deletion logic and added destination filename capture in downloader. Added custom style for auto-update checkbox in updater dialog.
This commit is contained in:
@@ -3,8 +3,6 @@ import sys
|
|||||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||||
|
|
||||||
from src.utils.ytsage_logger import logger
|
from src.utils.ytsage_logger import logger
|
||||||
from src.core.ytsage_yt_dlp import check_ytdlp_binary, setup_ytdlp # Import the new yt-dlp setup functions
|
|
||||||
from src.core.ytsage_deno import check_deno_binary, setup_deno # Import the new Deno setup functions
|
|
||||||
from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main
|
from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main
|
||||||
|
|
||||||
|
|
||||||
@@ -22,21 +20,6 @@ def main():
|
|||||||
logger.info("Starting YTSage application")
|
logger.info("Starting YTSage application")
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
# Get the expected binary path and check if it exists
|
|
||||||
if not check_ytdlp_binary():
|
|
||||||
# No app-specific binary found, show setup dialog regardless of Python package
|
|
||||||
logger.warning("No yt-dlp binary found, starting setup process")
|
|
||||||
yt_dlp_path = setup_ytdlp()
|
|
||||||
if yt_dlp_path == "yt-dlp": # If user canceled or something went wrong
|
|
||||||
logger.warning("yt-dlp not configured properly")
|
|
||||||
|
|
||||||
# Check for Deno binary
|
|
||||||
if not check_deno_binary():
|
|
||||||
logger.warning("No Deno binary found, starting setup process")
|
|
||||||
deno_path = setup_deno()
|
|
||||||
if deno_path == "deno": # If user canceled or something went wrong
|
|
||||||
logger.warning("Deno not configured properly")
|
|
||||||
|
|
||||||
window = YTSageApp() # Instantiate the main application class
|
window = YTSageApp() # Instantiate the main application class
|
||||||
window.show()
|
window.show()
|
||||||
logger.info("Application window shown, entering main loop")
|
logger.info("Application window shown, entering main loop")
|
||||||
|
|||||||
@@ -189,9 +189,12 @@ class DownloadThread(QThread):
|
|||||||
|
|
||||||
def safe_delete(path: Path) -> bool:
|
def safe_delete(path: Path) -> bool:
|
||||||
try:
|
try:
|
||||||
|
# Check if file exists before trying to delete
|
||||||
|
if path.exists():
|
||||||
path.unlink(missing_ok=True)
|
path.unlink(missing_ok=True)
|
||||||
logger.debug(f"Deleted subtitle file: {path.name}")
|
logger.debug(f"Deleted subtitle file: {path.name}")
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error deleting subtitle file {path}: {e}")
|
logger.exception(f"Error deleting subtitle file {path}: {e}")
|
||||||
return False
|
return False
|
||||||
@@ -410,8 +413,6 @@ class DownloadThread(QThread):
|
|||||||
self._terminate_process_tree(self.process)
|
self._terminate_process_tree(self.process)
|
||||||
|
|
||||||
# Add delay before cleanup to allow file handles to be released
|
# Add delay before cleanup to allow file handles to be released
|
||||||
# Force garbage collection to help release resources
|
|
||||||
gc.collect()
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
self.cleanup_partial_files()
|
self.cleanup_partial_files()
|
||||||
self.status_signal.emit(_("download.cancelled"))
|
self.status_signal.emit(_("download.cancelled"))
|
||||||
@@ -622,6 +623,14 @@ class DownloadThread(QThread):
|
|||||||
if "Downloading webpage" in line or "Extracting URL" in line:
|
if "Downloading webpage" in line or "Extracting URL" in line:
|
||||||
self.status_signal.emit(_("download.fetching_info"))
|
self.status_signal.emit(_("download.fetching_info"))
|
||||||
self.progress_signal.emit(0)
|
self.progress_signal.emit(0)
|
||||||
|
elif "[download] Destination:" in line:
|
||||||
|
# Extract the destination filename
|
||||||
|
match = re.search(r"Destination: (.+)", line)
|
||||||
|
if match:
|
||||||
|
dest_path = match.group(1).strip()
|
||||||
|
self.current_filename = Path(dest_path).name
|
||||||
|
self.last_file_path = dest_path
|
||||||
|
logger.debug(f"Captured destination filename: {self.current_filename}")
|
||||||
elif "Downloading API JSON" in line:
|
elif "Downloading API JSON" in line:
|
||||||
self.status_signal.emit(_("download.processing_playlist"))
|
self.status_signal.emit(_("download.processing_playlist"))
|
||||||
self.progress_signal.emit(0)
|
self.progress_signal.emit(0)
|
||||||
|
|||||||
@@ -492,6 +492,32 @@ class UpdaterTabWidget(QWidget):
|
|||||||
|
|
||||||
# Enable/Disable auto-update checkbox
|
# Enable/Disable auto-update checkbox
|
||||||
self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates"))
|
self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates"))
|
||||||
|
self.auto_update_enabled.setStyleSheet(
|
||||||
|
"""
|
||||||
|
QCheckBox {
|
||||||
|
color: #ffffff;
|
||||||
|
spacing: 5px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
QCheckBox::indicator {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
QCheckBox::indicator:unchecked {
|
||||||
|
border: 2px solid #666666;
|
||||||
|
background: #1d1e22;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
QCheckBox::indicator:checked {
|
||||||
|
border: 2px solid #c90000;
|
||||||
|
background: #c90000;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
QCheckBox:disabled { color: #888888; }
|
||||||
|
QCheckBox::indicator:disabled { border-color: #555555; background: #444444; }
|
||||||
|
"""
|
||||||
|
)
|
||||||
auto_update_layout.addWidget(self.auto_update_enabled)
|
auto_update_layout.addWidget(self.auto_update_enabled)
|
||||||
|
|
||||||
# Frequency options
|
# Frequency options
|
||||||
|
|||||||
+32
-196
@@ -51,6 +51,7 @@ from src.utils.ytsage_logger import logger
|
|||||||
from src.utils.ytsage_config_manager import ConfigManager
|
from src.utils.ytsage_config_manager import ConfigManager
|
||||||
from src.utils.ytsage_localization import LocalizationManager, _
|
from src.utils.ytsage_localization import LocalizationManager, _
|
||||||
from src.utils.ytsage_history_manager import HistoryManager
|
from src.utils.ytsage_history_manager import HistoryManager
|
||||||
|
from src.gui.ytsage_stylesheet import StyleSheet
|
||||||
|
|
||||||
|
|
||||||
class UpdateCheckThread(QThread):
|
class UpdateCheckThread(QThread):
|
||||||
@@ -105,30 +106,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
saved_language = ConfigManager.get("language") or "en"
|
saved_language = ConfigManager.get("language") or "en"
|
||||||
LocalizationManager.initialize(saved_language)
|
LocalizationManager.initialize(saved_language)
|
||||||
|
|
||||||
# Check for FFmpeg before proceeding
|
|
||||||
if not check_ffmpeg():
|
|
||||||
self.show_ffmpeg_dialog()
|
|
||||||
|
|
||||||
# Check for yt-dlp in our app's bin directory or system PATH
|
|
||||||
ytdlp_path = get_yt_dlp_path()
|
|
||||||
if ytdlp_path == "yt-dlp": # Not found in app dir or PATH
|
|
||||||
self.show_ytdlp_setup_dialog()
|
|
||||||
else:
|
|
||||||
logger.info(f"Using yt-dlp from: {ytdlp_path}")
|
|
||||||
|
|
||||||
# Check for Deno in our app's bin directory
|
|
||||||
deno_path = get_deno_path()
|
|
||||||
if deno_path == "deno": # Not found in app dir
|
|
||||||
self.show_deno_setup_dialog()
|
|
||||||
else:
|
|
||||||
logger.info(f"Using Deno from: {deno_path}")
|
|
||||||
|
|
||||||
self.version = APP_VERSION
|
self.version = APP_VERSION
|
||||||
self.check_for_updates()
|
|
||||||
|
|
||||||
# Check for auto-updates if enabled
|
|
||||||
self.check_auto_update_ytdlp()
|
|
||||||
|
|
||||||
load_saved_path(self)
|
load_saved_path(self)
|
||||||
# Load custom icon
|
# Load custom icon
|
||||||
if ICON_PATH.exists():
|
if ICON_PATH.exists():
|
||||||
@@ -176,178 +154,11 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
self.analysis_completed = False
|
self.analysis_completed = False
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.setStyleSheet(
|
|
||||||
"""
|
# Defer heavy start-up tasks to ensure UI renders immediately
|
||||||
QMainWindow {
|
QTimer.singleShot(100, self._perform_startup_checks)
|
||||||
background-color: #15181b;
|
|
||||||
}
|
self.setStyleSheet(StyleSheet.MAIN)
|
||||||
QWidget {
|
|
||||||
background-color: #15181b;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
QLineEdit {
|
|
||||||
padding: 5px 15px;
|
|
||||||
border: 2px solid #2a2d2e;
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: #1b2021;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
QLineEdit:focus {
|
|
||||||
border-color: #ff6b6b;
|
|
||||||
}
|
|
||||||
QPushButton {
|
|
||||||
padding: 8px 15px;
|
|
||||||
background-color: #c90000;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
QPushButton:hover {
|
|
||||||
background-color: #a50000;
|
|
||||||
}
|
|
||||||
QPushButton:pressed {
|
|
||||||
background-color: #800000;
|
|
||||||
}
|
|
||||||
QPushButton:disabled {
|
|
||||||
background-color: #3d3d3d;
|
|
||||||
color: #888888;
|
|
||||||
}
|
|
||||||
QTableWidget {
|
|
||||||
border: 2px solid #1b2021;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #1b2021;
|
|
||||||
gridline-color: #1b2021;
|
|
||||||
}
|
|
||||||
QHeaderView::section {
|
|
||||||
background-color: #15181b;
|
|
||||||
padding: 5px;
|
|
||||||
border: 1px solid #1b2021;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
QProgressBar {
|
|
||||||
border: 2px solid #1b2021;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
QProgressBar::chunk {
|
|
||||||
background-color: #c90000;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
QLabel {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
/* Style for filter buttons */
|
|
||||||
QPushButton.filter-btn {
|
|
||||||
background-color: #1b2021;
|
|
||||||
padding: 5px 10px;
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
QPushButton.filter-btn:checked {
|
|
||||||
background-color: #c90000;
|
|
||||||
}
|
|
||||||
QPushButton.filter-btn:hover {
|
|
||||||
background-color: #444444;
|
|
||||||
}
|
|
||||||
QPushButton.filter-btn:checked:hover {
|
|
||||||
background-color: #a50000;
|
|
||||||
}
|
|
||||||
/* Modern Scrollbar Styling */
|
|
||||||
QScrollBar:vertical {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
width: 14px;
|
|
||||||
margin: 15px 0 15px 0;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
QScrollBar::handle:vertical {
|
|
||||||
background: #404040;
|
|
||||||
min-height: 30px;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
QScrollBar::handle:vertical:hover {
|
|
||||||
background: #505050;
|
|
||||||
}
|
|
||||||
QScrollBar::sub-line:vertical {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
height: 15px;
|
|
||||||
border-top-left-radius: 7px;
|
|
||||||
border-top-right-radius: 7px;
|
|
||||||
subcontrol-position: top;
|
|
||||||
subcontrol-origin: margin;
|
|
||||||
}
|
|
||||||
QScrollBar::add-line:vertical {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
height: 15px;
|
|
||||||
border-bottom-left-radius: 7px;
|
|
||||||
border-bottom-right-radius: 7px;
|
|
||||||
subcontrol-position: bottom;
|
|
||||||
subcontrol-origin: margin;
|
|
||||||
}
|
|
||||||
QScrollBar::sub-line:vertical:hover,
|
|
||||||
QScrollBar::add-line:vertical:hover {
|
|
||||||
background: #404040;
|
|
||||||
}
|
|
||||||
QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
|
|
||||||
background: none;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
/* Horizontal Scrollbar */
|
|
||||||
QScrollBar:horizontal {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
height: 14px;
|
|
||||||
margin: 0 15px 0 15px;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
QScrollBar::handle:horizontal {
|
|
||||||
background: #404040;
|
|
||||||
min-width: 30px;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
QScrollBar::handle:horizontal:hover {
|
|
||||||
background: #505050;
|
|
||||||
}
|
|
||||||
QScrollBar::sub-line:horizontal {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
width: 15px;
|
|
||||||
border-top-left-radius: 7px;
|
|
||||||
border-bottom-left-radius: 7px;
|
|
||||||
subcontrol-position: left;
|
|
||||||
subcontrol-origin: margin;
|
|
||||||
}
|
|
||||||
QScrollBar::add-line:horizontal {
|
|
||||||
border: none;
|
|
||||||
background: #15181b;
|
|
||||||
width: 15px;
|
|
||||||
border-top-right-radius: 7px;
|
|
||||||
border-bottom-right-radius: 7px;
|
|
||||||
subcontrol-position: right;
|
|
||||||
subcontrol-origin: margin;
|
|
||||||
}
|
|
||||||
QScrollBar::sub-line:horizontal:hover,
|
|
||||||
QScrollBar::add-line:horizontal:hover {
|
|
||||||
background: #404040;
|
|
||||||
}
|
|
||||||
QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {
|
|
||||||
background: none;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
self.signals.update_progress.connect(self.update_progress_bar)
|
self.signals.update_progress.connect(self.update_progress_bar)
|
||||||
|
|
||||||
# After adding format buttons
|
# After adding format buttons
|
||||||
@@ -361,7 +172,32 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
# Initialize UI state based on current mode
|
# Initialize UI state based on current mode
|
||||||
self.handle_mode_change()
|
self.handle_mode_change()
|
||||||
|
|
||||||
# Init_sound method is removed, serve no purpose.
|
|
||||||
|
def _perform_startup_checks(self):
|
||||||
|
"""Perform potentially blocking startup checks after UI is shown."""
|
||||||
|
|
||||||
|
# Check for FFmpeg before proceeding
|
||||||
|
if not check_ffmpeg():
|
||||||
|
self.show_ffmpeg_dialog()
|
||||||
|
|
||||||
|
# Check for yt-dlp in our app's bin directory or system PATH
|
||||||
|
ytdlp_path = get_yt_dlp_path()
|
||||||
|
if ytdlp_path == "yt-dlp": # Not found in app dir or PATH
|
||||||
|
self.show_ytdlp_setup_dialog()
|
||||||
|
else:
|
||||||
|
logger.info(f"Using yt-dlp from: {ytdlp_path}")
|
||||||
|
|
||||||
|
# Check for Deno in our app's bin directory
|
||||||
|
deno_path = get_deno_path()
|
||||||
|
if deno_path == "deno": # Not found in app dir
|
||||||
|
self.show_deno_setup_dialog()
|
||||||
|
else:
|
||||||
|
logger.info(f"Using Deno from: {deno_path}")
|
||||||
|
|
||||||
|
self.check_for_updates()
|
||||||
|
|
||||||
|
# Check for auto-updates if enabled
|
||||||
|
QTimer.singleShot(2000, self.check_auto_update_ytdlp) # Further delay auto-update check
|
||||||
|
|
||||||
def play_notification_sound(self) -> None:
|
def play_notification_sound(self) -> None:
|
||||||
"""Play notification sound asynchronously (non-blocking)."""
|
"""Play notification sound asynchronously (non-blocking)."""
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
class StyleSheet:
|
||||||
|
MAIN = """
|
||||||
|
QMainWindow {
|
||||||
|
background-color: #15181b;
|
||||||
|
}
|
||||||
|
QWidget {
|
||||||
|
background-color: #15181b;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
QLineEdit {
|
||||||
|
padding: 5px 15px;
|
||||||
|
border: 2px solid #2a2d2e;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #1b2021;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
QLineEdit:focus {
|
||||||
|
border-color: #ff6b6b;
|
||||||
|
}
|
||||||
|
QPushButton {
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #c90000;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #a50000;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #800000;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
background-color: #3d3d3d;
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
QTableWidget {
|
||||||
|
border: 2px solid #1b2021;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #1b2021;
|
||||||
|
gridline-color: #1b2021;
|
||||||
|
}
|
||||||
|
QHeaderView::section {
|
||||||
|
background-color: #15181b;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #1b2021;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
QProgressBar {
|
||||||
|
border: 2px solid #1b2021;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: #c90000;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
QLabel {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
/* Style for filter buttons */
|
||||||
|
QPushButton.filter-btn {
|
||||||
|
background-color: #1b2021;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
QPushButton.filter-btn:checked {
|
||||||
|
background-color: #c90000;
|
||||||
|
}
|
||||||
|
QPushButton.filter-btn:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
}
|
||||||
|
QPushButton.filter-btn:checked:hover {
|
||||||
|
background-color: #a50000;
|
||||||
|
}
|
||||||
|
/* Modern Scrollbar Styling */
|
||||||
|
QScrollBar:vertical {
|
||||||
|
border: none;
|
||||||
|
background: #15181b;
|
||||||
|
width: 14px;
|
||||||
|
margin: 15px 0 15px 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical {
|
||||||
|
background: #404040;
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical:hover {
|
||||||
|
background: #505050;
|
||||||
|
}
|
||||||
|
QScrollBar::sub-line:vertical {
|
||||||
|
border: none;
|
||||||
|
background: #15181b;
|
||||||
|
height: 15px;
|
||||||
|
border-top-left-radius: 7px;
|
||||||
|
border-top-right-radius: 7px;
|
||||||
|
subcontrol-position: top;
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
}
|
||||||
|
QScrollBar::add-line:vertical {
|
||||||
|
border: none;
|
||||||
|
background: #15181b;
|
||||||
|
height: 15px;
|
||||||
|
border-bottom-left-radius: 7px;
|
||||||
|
border-bottom-right-radius: 7px;
|
||||||
|
subcontrol-position: bottom;
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
}
|
||||||
|
QScrollBar::sub-line:vertical:hover,
|
||||||
|
QScrollBar::add-line:vertical:hover {
|
||||||
|
background: #404040;
|
||||||
|
}
|
||||||
|
QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
|
||||||
|
background: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
"""
|
||||||
Reference in New Issue
Block a user