commit
6607a233c0
13 changed files with 251 additions and 152 deletions
81
.github/workflows/create-release.yml
vendored
81
.github/workflows/create-release.yml
vendored
|
|
@ -1,81 +0,0 @@
|
|||
name: Create New Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
new_tag:
|
||||
description: 'Tag name for the new release'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
create_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest release tag from GitHub
|
||||
id: latest_release
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
const latestRelease = await github.rest.repos.getLatestRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
core.info(`Latest release tag: ${latestRelease.data.tag_name}`);
|
||||
core.setOutput('last_release', latestRelease.data.tag_name);
|
||||
} catch (error) {
|
||||
core.info("No previous release found.");
|
||||
// If no release exists, output an empty string.
|
||||
core.setOutput('last_release', '');
|
||||
}
|
||||
|
||||
- name: Set new release tag from input
|
||||
id: new_tag
|
||||
run: |
|
||||
echo "NEW_TAG=${{ github.event.inputs.new_tag }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate commit log for new release
|
||||
id: commits
|
||||
run: |
|
||||
LAST_RELEASE="${{ steps.latest_release.outputs.last_release }}"
|
||||
NEW_TAG="${{ steps.new_tag.outputs.NEW_TAG }}"
|
||||
|
||||
if [ -z "$NEW_TAG" ]; then
|
||||
echo "No new tag provided. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${LAST_RELEASE}" ]; then
|
||||
echo "No previous release found, using the repository’s initial commit as the starting point."
|
||||
FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD)
|
||||
RANGE="${FIRST_COMMIT}..${NEW_TAG}"
|
||||
else
|
||||
RANGE="${LAST_RELEASE}..${NEW_TAG}"
|
||||
fi
|
||||
|
||||
echo "Comparing commits between: ${RANGE}"
|
||||
LOG=$(git log "${RANGE}" --no-merges --pretty=format:"- %h %s by %an")
|
||||
|
||||
echo "LOG<<EOF" >> "$GITHUB_ENV"
|
||||
echo "$LOG" >> "$GITHUB_ENV"
|
||||
echo "EOF" >> "$GITHUB_ENV"
|
||||
|
||||
# Create or update the GitHub release for the new tag.
|
||||
- name: Create / Update GitHub Release for new tag
|
||||
uses: softprops/action-gh-release@master
|
||||
with:
|
||||
tag_name: ${{ steps.new_tag.outputs.NEW_TAG }}
|
||||
name: "${{ steps.new_tag.outputs.NEW_TAG }}"
|
||||
body: ${{ env.LOG }}
|
||||
append_body: false
|
||||
generate_release_notes: false
|
||||
make_latest: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
|
@ -210,15 +210,17 @@ jobs:
|
|||
return;
|
||||
}
|
||||
|
||||
const { data: commits } = await github.rest.repos.compareCommits({
|
||||
const { data: comparison } = await github.rest.repos.compareCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: latestRelease.data[1]?.tag_name || '', // fallback to second latest tag
|
||||
head: tag,
|
||||
});
|
||||
|
||||
const commits = comparison.commits.filter(c => 1 === c.parents.length);
|
||||
|
||||
const changelog = commits.commits.map(
|
||||
c => `- ${c.sha.substring(0, 7)} ${c.commit.message.split('\n')[0]} by ${c.commit.author.name}`
|
||||
c => `- ${c.sha.substring(0, 7)} ${c.commit.message.split('\n')[0]} by @${c.commit.author.name}`
|
||||
).join('\n');
|
||||
|
||||
if (!changelog) {
|
||||
|
|
|
|||
110
.github/workflows/native-build.yml
vendored
Normal file
110
.github/workflows/native-build.yml
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
name: Build WebView wrappers
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
required: true
|
||||
description: "Ref to build from (e.g. v1.0.0)"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: 3.11
|
||||
PNPM_VERSION: 10
|
||||
NODE_VERSION: 20
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout source repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Cache Chocolatey packages
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:\ProgramData\chocolatey\lib
|
||||
key: choco-${{ runner.os }}-qt6
|
||||
restore-keys: |
|
||||
choco-${{ runner.os }}-
|
||||
|
||||
- name: Install Qt (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: choco install -y qt6-base-dev
|
||||
|
||||
- name: Cache Python venv
|
||||
id: cache-python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .venv
|
||||
key: uv-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('uv.lock') }}
|
||||
restore-keys: |
|
||||
uv-${{ runner.os }}-${{ env.PYTHON_VERSION }}-
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: "x64"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install uv
|
||||
uv venv --system-site-packages --relocatable
|
||||
uv sync --link-mode=copy --active
|
||||
|
||||
- name: Install PyInstaller + Qt backend (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
uv pip install pyinstaller pywebview QtPy PySide6
|
||||
|
||||
- name: Install PyInstaller + Qt backend (Other OSs)
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: |
|
||||
uv pip install pyinstaller pywebview[qt]
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: "ui/pnpm-lock.yaml"
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: ui
|
||||
run: |
|
||||
pnpm install --production --prefer-offline --frozen-lockfile
|
||||
pnpm run generate
|
||||
|
||||
- name: Build native binary
|
||||
run: |
|
||||
uv run pyinstaller ./app.spec
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-${{ matrix.os }}-${{ github.event.inputs.tag }}
|
||||
path: dist/*
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.tag }}
|
||||
files: dist/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -25,7 +25,9 @@
|
|||
"copyts",
|
||||
"cronsim",
|
||||
"datas",
|
||||
"dateparser",
|
||||
"daterange",
|
||||
"defusedxml",
|
||||
"dotenv",
|
||||
"edgechromium",
|
||||
"engineio",
|
||||
|
|
@ -50,12 +52,14 @@
|
|||
"matroska",
|
||||
"mbed",
|
||||
"mccabe",
|
||||
"MEIPASS",
|
||||
"Microformat",
|
||||
"microformats",
|
||||
"mkvtoolsnix",
|
||||
"movflags",
|
||||
"mpegts",
|
||||
"msvideo",
|
||||
"multidict",
|
||||
"muxdelay",
|
||||
"nodesc",
|
||||
"noprogress",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ live streams, and includes features like scheduling downloads, sending notificat
|
|||
* Apply `yt-dlp` options per custom defined conditions.
|
||||
* Custom browser extensions, bookmarklets and iOS shortcuts to send links to YTPTube instance.
|
||||
|
||||
### Side project
|
||||
|
||||
We have a side project related to YTPTube, which takes YTPTube source code and build an executable for Windows, macOS and Linux.
|
||||
The project is in early stages, and might not even work yet. However keep an eye at [This build action](https://github.com/arabcoders/ytptube/actions/workflows/native-build.yml) for builds, simply select last successful build and download the executable for your platform. Help us test out the platforms
|
||||
and report any issues you might find. We only have windows/linux machines to test on, so we need your help to test it out on macOS.
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
## Run using docker command
|
||||
|
|
|
|||
9
app.spec
9
app.spec
|
|
@ -25,12 +25,13 @@ with open("./uv.lock", "rb") as f:
|
|||
lock = tomllib.load(f)
|
||||
|
||||
hidden = [
|
||||
*lock.get("dependencies", {}).keys(),
|
||||
*{pkg["name"] for pkg in lock.get("package", [])},
|
||||
"aiohttp",
|
||||
"socketio",
|
||||
"engineio",
|
||||
"engineio.async_drivers.aiohttp",
|
||||
"socketio.async_drivers.aiohttp",
|
||||
"app"
|
||||
]
|
||||
|
||||
hidden = [f.replace("-", "_") for f in hidden]
|
||||
|
|
@ -41,8 +42,7 @@ a = Analysis( # noqa: F821 # type: ignore
|
|||
binaries=binaries,
|
||||
datas=[
|
||||
("ui/exported", "ui/exported"),
|
||||
("app/migrations", "migrations"),
|
||||
("app/library/presets.json", "library"),
|
||||
("app/", "app/"),
|
||||
],
|
||||
hiddenimports=hidden,
|
||||
hookspath=[],
|
||||
|
|
@ -63,6 +63,7 @@ exe = EXE( # type: ignore # noqa: F821
|
|||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
console=False, # Turn on to True if you want a console window for debugging.
|
||||
icon="ui/public/favicon.ico",
|
||||
onefile=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -94,8 +94,7 @@ def calc_download_path(base_path: str | Path, folder: str | None = None, create_
|
|||
if not folder:
|
||||
return str(base_path)
|
||||
|
||||
if folder.startswith("/"):
|
||||
folder = folder[1:]
|
||||
folder = folder.removeprefix("/")
|
||||
|
||||
realBasePath = base_path.resolve()
|
||||
download_path = Path(realBasePath).joinpath(folder).resolve(strict=False)
|
||||
|
|
@ -179,8 +178,7 @@ def extract_info(
|
|||
else:
|
||||
log_wrapper.add_target(target=params["callback"], level=logging.ERROR, name="callback")
|
||||
|
||||
if "callback" in params:
|
||||
del params["callback"]
|
||||
params.pop("callback", None)
|
||||
|
||||
if log_wrapper.has_targets():
|
||||
if "logger" in params:
|
||||
|
|
|
|||
|
|
@ -539,7 +539,7 @@ class Config:
|
|||
try:
|
||||
import subprocess
|
||||
|
||||
branch_result = subprocess.run( # noqa: S603
|
||||
branch_result = subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"], # noqa: S607
|
||||
cwd=os.path.dirname(git_path),
|
||||
capture_output=True,
|
||||
|
|
@ -556,7 +556,7 @@ class Config:
|
|||
logging.warning("Git branch name is empty.")
|
||||
return
|
||||
|
||||
commit_result = subprocess.run( # noqa: S603
|
||||
commit_result = subprocess.run(
|
||||
["git", "log", "-1", "--format=%ct_%H"], # noqa: S607
|
||||
cwd=os.path.dirname(git_path),
|
||||
capture_output=True,
|
||||
|
|
|
|||
165
app/native.py
165
app/native.py
|
|
@ -1,77 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
import platformdirs
|
||||
|
||||
sys.path.insert(0, os.path.join(getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))), "app"))
|
||||
|
||||
APP_NAME = "YTPTube"
|
||||
APP_ROOT = str((Path(__file__).parent / "..").resolve())
|
||||
if APP_ROOT not in sys.path:
|
||||
sys.path.insert(0, APP_ROOT)
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
import socket
|
||||
import threading
|
||||
|
||||
ready = threading.Event()
|
||||
exception_holder = queue.Queue()
|
||||
|
||||
APP_NAME = "YTPTube"
|
||||
|
||||
try:
|
||||
import webview # type: ignore
|
||||
|
||||
if "linux" in sys.platform:
|
||||
os.environ.setdefault("LC_ALL", "C.UTF-8")
|
||||
os.environ.setdefault("LANG", "C.UTF-8")
|
||||
import webview.platforms.qt # type: ignore
|
||||
|
||||
# monkey patch the download handler for pywebview to support qt6.
|
||||
def on_download_requested(self, download):
|
||||
from qtpy import QtCore # type: ignore
|
||||
from qtpy.QtWidgets import QFileDialog # type: ignore
|
||||
|
||||
old_path = download.url().path()
|
||||
suffix = QtCore.QFileInfo(old_path).suffix()
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self, self.localization["global.saveFile"], old_path, "*." + suffix
|
||||
)
|
||||
if filename:
|
||||
if hasattr(download, "setPath"):
|
||||
download.setPath(filename)
|
||||
else:
|
||||
download.setDownloadDirectory(os.path.dirname(filename))
|
||||
download.setDownloadFileName(os.path.basename(filename))
|
||||
download.accept()
|
||||
else:
|
||||
download.cancel()
|
||||
|
||||
webview.platforms.qt.BrowserView.on_download_requested = on_download_requested
|
||||
|
||||
except ImportError as e:
|
||||
msg = "Please run 'pipenv install pywebview[qt]' package to run YTPTube in native mode."
|
||||
pkgs = "pywebview[edgechromium]" if os.name == "nt" else "pywebview[qt]"
|
||||
msg: str = f"Please run 'uv pip install {pkgs}' to run YTPTube in native mode."
|
||||
raise ImportError(msg) from e
|
||||
|
||||
|
||||
def error_window(exc: Exception | str) -> None:
|
||||
trace: str = "\n".join(traceback.format_exception(exc)) if isinstance(exc, Exception) else exc
|
||||
webview.create_window(
|
||||
f"{APP_NAME} - Error",
|
||||
html=f"<h1 style='color:red;'>An error occurred</h1><pre>{trace}</pre>",
|
||||
width=600,
|
||||
height=400,
|
||||
resizable=True,
|
||||
)
|
||||
webview.start(
|
||||
gui="edgechromium" if os.name == "nt" else "qt",
|
||||
debug=False,
|
||||
storage_path=str(Path(os.getenv("YTP_TEMP_PATH", os.getcwd())) / "webview"),
|
||||
private_mode=False,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def set_env():
|
||||
import platformdirs
|
||||
defaults = {
|
||||
"YTP_CONFIG_PATH": lambda: platformdirs.user_config_dir(APP_NAME.lower(), "arabcoders", ensure_exists=True),
|
||||
"YTP_TEMP_PATH": lambda: platformdirs.user_cache_dir(APP_NAME.lower(), "arabcoders", ensure_exists=True),
|
||||
"YTP_DOWNLOAD_PATH": lambda: platformdirs.user_downloads_dir(),
|
||||
"YTP_ACCESS_LOG": "false",
|
||||
"YTP_BROWSER_ENABLED": "true",
|
||||
"YTP_BROWSER_CONTROL_ENABLED": "true",
|
||||
}
|
||||
|
||||
dct = {}
|
||||
for key, value in defaults.items():
|
||||
if os.getenv(key) is not None:
|
||||
continue
|
||||
|
||||
if not os.getenv("YTP_CONFIG_PATH"):
|
||||
dct["YTP_CONFIG_PATH"] = platformdirs.user_config_dir(APP_NAME.lower(), "arabcoders", ensure_exists=True)
|
||||
|
||||
if not os.getenv("YTP_TEMP_PATH"):
|
||||
dct["YTP_TEMP_PATH"] = platformdirs.user_cache_dir(APP_NAME.lower(), "arabcoders", ensure_exists=True)
|
||||
|
||||
if not os.getenv("YTP_DOWNLOAD_PATH"):
|
||||
dct["YTP_DOWNLOAD_PATH"] = platformdirs.user_downloads_dir()
|
||||
|
||||
if os.getenv("YTP_ACCESS_LOG", None) is None:
|
||||
dct["YTP_ACCESS_LOG"] = "false"
|
||||
|
||||
if dct:
|
||||
os.environ.update(dct)
|
||||
os.environ[key] = value() if callable(value) else value
|
||||
|
||||
|
||||
def app_start(host: str, port: int) -> None:
|
||||
import asyncio
|
||||
|
||||
from main import Main
|
||||
|
||||
def run_backend(host: str, port: int, ready_event: threading.Event, error_queue: queue.Queue):
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
Main(is_native=True).start(host, port, cb=lambda: ready.set())
|
||||
from app.main import Main
|
||||
|
||||
Main(is_native=True).start(host, port, cb=lambda: ready_event.set())
|
||||
except Exception as e:
|
||||
exception_holder.put(e)
|
||||
ready.set()
|
||||
logging.exception(e)
|
||||
error_queue.put(e)
|
||||
ready_event.set()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
host = "127.0.0.1"
|
||||
set_env()
|
||||
|
||||
cfg_path: Path = Path(os.getenv("YTP_CONFIG_PATH")) / "webview.json"
|
||||
|
||||
port = None
|
||||
win_conf: dict[str, int] = {}
|
||||
|
||||
if cfg_path.exists():
|
||||
data = json.loads(cfg_path.read_text())
|
||||
port = data.get("port")
|
||||
|
|
@ -85,18 +128,21 @@ if __name__ == "__main__":
|
|||
port = s.getsockname()[1]
|
||||
cfg_path.write_text(json.dumps({"port": port}))
|
||||
|
||||
threading.Thread(target=app_start, args=(host, port), daemon=True).start()
|
||||
ready = threading.Event()
|
||||
errors: queue.Queue = queue.Queue()
|
||||
threading.Thread(target=run_backend, args=(host, port, ready, errors), daemon=False).start()
|
||||
|
||||
ready.wait()
|
||||
ready.wait(timeout=5)
|
||||
|
||||
if not exception_holder.empty():
|
||||
raise exception_holder.get()
|
||||
if not errors.empty():
|
||||
error_window(errors.get())
|
||||
return
|
||||
|
||||
from app.library.version import APP_VERSION
|
||||
|
||||
gui = "edgechromium" if os.name == "nt" else "qt"
|
||||
create_kwargs = {**win_conf, "resizable": True}
|
||||
|
||||
webview.settings["ALLOW_DOWNLOADS"] = True
|
||||
webview.settings["OPEN_DEVTOOLS_IN_DEBUG"] = False
|
||||
window = webview.create_window(APP_NAME, f"http://{host}:{port}", **create_kwargs)
|
||||
window = webview.create_window(f"{APP_NAME} - {APP_VERSION}", f"http://{host}:{port}", **create_kwargs)
|
||||
|
||||
def save_geometry():
|
||||
cfg = {
|
||||
|
|
@ -110,13 +156,22 @@ if __name__ == "__main__":
|
|||
|
||||
window.events.resized += lambda *_: save_geometry()
|
||||
window.events.moved += lambda *_: save_geometry()
|
||||
window.events.closing += lambda *_: os._exit(0)
|
||||
|
||||
gui = os.getenv("YTP_WV_GUI", None)
|
||||
gui = "edgechromium" if os.name == "nt" else "qt"
|
||||
|
||||
webview.settings["ALLOW_DOWNLOADS"] = True
|
||||
webview.settings["OPEN_DEVTOOLS_IN_DEBUG"] = False
|
||||
webview.start(
|
||||
gui=gui,
|
||||
debug=True,
|
||||
storage_path=str(Path(os.getenv("YTP_TEMP_PATH", os.getcwd())) / "webview"),
|
||||
private_mode=False,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
error_window(e)
|
||||
os._exit(1)
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ def preload_static(root_path: Path, config: Config) -> None:
|
|||
# uri_path: str = f"/{str(file.as_posix()).replace(f'{static_dir.as_posix()!s}/', '')}"
|
||||
contentType = EXT_TO_MIME.get(file.suffix)
|
||||
if not contentType:
|
||||
contentType = MIME.from_file(file)
|
||||
contentType = MIME.from_file(str(file))
|
||||
|
||||
STATIC_FILES[uri_path] = {
|
||||
"uri": uri_path,
|
||||
|
|
|
|||
|
|
@ -159,6 +159,8 @@ ignore = [
|
|||
"ERA001",
|
||||
"S101",
|
||||
"LOG015",
|
||||
"PLC0415",
|
||||
"S603",
|
||||
]
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@
|
|||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger is-fullwidth is-small" @click="removeItem(item)"
|
||||
v-tooltip="'Remove video'">
|
||||
v-tooltip="config.app.remove_files ? 'Remove video' : 'Clear video'">
|
||||
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -340,7 +340,7 @@
|
|||
<a class="button is-danger is-fullwidth" @click="removeItem(item)">
|
||||
<span class="icon-text is-block">
|
||||
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
|
||||
<span>Remove</span>
|
||||
<span>{{ config.app.remove_files ? 'Remove' : 'Clear' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@
|
|||
<span>Opts</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="column is-12" v-if="!hasFormatInConfig && get_preset(form.preset)?.description">
|
||||
<div class="column is-12"
|
||||
v-if="!config.app.basic_mode && !hasFormatInConfig && get_preset(form.preset)?.description">
|
||||
<div class="is-overflow-auto" style="max-height: 150px;">
|
||||
<div class="is-ellipsis is-clickable" @click="expand_description">
|
||||
<span class="icon"><i class="fa-solid fa-info" /></span> {{ get_preset(form.preset)?.description }}
|
||||
|
|
|
|||
Loading…
Reference in a new issue