Compare commits
24
Commits
34e8221772
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7107ff2a6 | ||
|
|
f9639318e8 | ||
|
|
64186d7064 | ||
|
|
c5f100d747 | ||
|
|
735757d58d | ||
|
|
64b593ac30 | ||
|
|
68230235d1 | ||
|
|
c0c6cdd156 | ||
|
|
c41ad77181 | ||
|
|
55a12c339c | ||
|
|
10de3ffd94 | ||
|
|
0fdb758835 | ||
|
|
5fd8cec0c2 | ||
|
|
47f1d1311f | ||
|
|
770390ff9b | ||
|
|
3daad96357 | ||
|
|
9befd9c28d | ||
|
|
e3af9736b4 | ||
|
|
c75814e837 | ||
|
|
d180d43e18 | ||
|
|
3196d3d761 | ||
|
|
bdf8d27398 | ||
|
|
c30b54f4b1 | ||
|
|
c0ac650625 |
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Navigate to the script's directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Check for virtual environment and activate if found
|
||||||
|
if [ -d "venv" ]; then
|
||||||
|
source venv/bin/activate
|
||||||
|
elif [ -d ".venv" ]; then
|
||||||
|
source .venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting DownloaderWebApp..."
|
||||||
|
python3 main.py &
|
||||||
|
PID=$!
|
||||||
|
echo $PID > app.pid
|
||||||
|
echo "Application started with PID: $PID"
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Navigate to the script's directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [ -f "app.pid" ]; then
|
||||||
|
PID=$(cat app.pid)
|
||||||
|
if ps -p $PID > /dev/null; then
|
||||||
|
echo "Stopping app (PID $PID)..."
|
||||||
|
kill $PID
|
||||||
|
rm app.pid
|
||||||
|
echo "Stopped."
|
||||||
|
else
|
||||||
|
echo "PID file found ($PID) but process is not running. Removing stale PID file."
|
||||||
|
rm app.pid
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No PID file found. Trying to find process by name..."
|
||||||
|
pkill -f "python3 main.py"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Stopped via process name."
|
||||||
|
else
|
||||||
|
echo "No running process found."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
10546
|
1075850
|
||||||
10547
|
1075852
|
||||||
|
1075853
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
|
import cloudscraper
|
||||||
|
import difflib
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from collections import defaultdict
|
||||||
from .samba_manager import SambaManager
|
from .samba_manager import SambaManager
|
||||||
from .config import Settings
|
from .config import settings
|
||||||
from smb.smb_structs import OperationFailure
|
from smb.smb_structs import OperationFailure
|
||||||
|
|
||||||
# --- Dedicated logger for ComicsManager ---
|
# --- Dedicated logger for ComicsManager ---
|
||||||
@@ -11,117 +17,681 @@ comics_logger = logging.getLogger('comics_manager')
|
|||||||
comics_logger.setLevel(logging.INFO)
|
comics_logger.setLevel(logging.INFO)
|
||||||
comics_logger.propagate = False
|
comics_logger.propagate = False
|
||||||
if not comics_logger.handlers:
|
if not comics_logger.handlers:
|
||||||
|
os.makedirs("logs", exist_ok=True)
|
||||||
comics_log_handler = logging.FileHandler("logs/comics_organization.log", mode='a')
|
comics_log_handler = logging.FileHandler("logs/comics_organization.log", mode='a')
|
||||||
comics_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
comics_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||||
comics_logger.addHandler(comics_log_handler)
|
comics_logger.addHandler(comics_log_handler)
|
||||||
comics_logger.addHandler(logging.StreamHandler())
|
comics_logger.addHandler(logging.StreamHandler())
|
||||||
|
|
||||||
class ComicsManager:
|
class ComicsManager:
|
||||||
|
def __init__(self):
|
||||||
|
# Staging settings (on 'isolation' share)
|
||||||
|
self.comics_root = "/comics/pendingorganization"
|
||||||
|
|
||||||
|
# Library defaults
|
||||||
|
self.library_share_default = "isolation"
|
||||||
|
self.library_path_default = "/comics/manga"
|
||||||
|
|
||||||
|
self.scraper = cloudscraper.create_scraper()
|
||||||
|
|
||||||
@staticmethod
|
def _get_series_info(self, filename):
|
||||||
def _get_series_name(filename):
|
|
||||||
base_name, _ = os.path.splitext(filename)
|
base_name, _ = os.path.splitext(filename)
|
||||||
pattern = re.compile(r'[-_\s]*(v(ol)?|c(h)?|chapter|issue|ep|episode)[-_\s]*\d+.*|[-_\s]+\d+$', re.IGNORECASE)
|
artist = "Unknown"
|
||||||
match = pattern.search(base_name)
|
doujin_id = None
|
||||||
|
|
||||||
|
id_match = re.search(r'[[\\](](\\d{5,7})[[\\])]', base_name)
|
||||||
|
if id_match:
|
||||||
|
doujin_id = id_match.group(1)
|
||||||
|
|
||||||
|
tags = re.findall(r'[[\\](](.*?)[[\\])]', base_name)
|
||||||
|
tags = [t for t in tags if t != doujin_id]
|
||||||
|
if tags:
|
||||||
|
artist = tags[0].strip()
|
||||||
|
|
||||||
|
name = re.sub(r'[[\\](].*?[[\\])]', '', base_name).strip()
|
||||||
|
pattern = re.compile(r'[-_\\s]*(v(ol)?\\. ?|c(h)?\\. ?|chapter|issue|ep(isode)?)\\s*\\d+.*$', re.IGNORECASE)
|
||||||
|
match = pattern.search(name)
|
||||||
if match:
|
if match:
|
||||||
return base_name[:match.start()].strip('-_ ')
|
name = name[:match.start()]
|
||||||
return base_name.strip('-_ ')
|
else:
|
||||||
|
name = re.sub(r'[-_\\s]+\\d+\\s*$', '', name)
|
||||||
|
|
||||||
def cleanup_toberead(self, samba_manager: SambaManager):
|
name = name.strip(' -_')
|
||||||
comics_logger.info("Starting cleanup of 'toberead' directory...")
|
if not name and tags:
|
||||||
comics_root = "/comics/toberead"
|
name = tags[0]
|
||||||
try:
|
elif not name:
|
||||||
items_in_toberead = samba_manager.list_path(comics_root)
|
name = base_name.strip()
|
||||||
if "error" in items_in_toberead:
|
|
||||||
raise Exception(f"Failed to list files in 'toberead': {items_in_toberead['error']}")
|
|
||||||
|
|
||||||
for item in items_in_toberead:
|
|
||||||
item_path = item["path"]
|
|
||||||
item_name = item["name"]
|
|
||||||
if item["is_directory"]:
|
|
||||||
comics_logger.info(f"Deleting directory '{item_name}' and all its contents.")
|
|
||||||
try:
|
|
||||||
samba_manager.delete_directory_recursive(item_path)
|
|
||||||
comics_logger.info(f"Successfully deleted directory: {item_name}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to delete directory {item_name}: {e}")
|
|
||||||
else:
|
|
||||||
comics_logger.info(f"Deleting file '{item_name}'.")
|
|
||||||
try:
|
|
||||||
samba_manager.delete_file(item_path)
|
|
||||||
comics_logger.info(f"Successfully deleted file: {item_name}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to delete file {item_name}: {e}")
|
|
||||||
|
|
||||||
comics_logger.info("Cleanup of 'toberead' finished.")
|
name = name.replace(' ', '_')
|
||||||
|
artist = artist.replace(' ', '_')
|
||||||
|
|
||||||
|
return name, artist, doujin_id
|
||||||
|
|
||||||
|
def _fetch_metadata_from_url(self, target_url):
|
||||||
|
try:
|
||||||
|
comics_logger.info(f"Fetching metadata from: {target_url}")
|
||||||
|
response = self.scraper.get(target_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
soup = BeautifulSoup(response.content, 'html.parser')
|
||||||
|
data = {}
|
||||||
|
title_info = soup.select_one('#info')
|
||||||
|
if title_info:
|
||||||
|
data['title'] = title_info.select_one('h1.title').text.strip() if title_info.select_one('h1.title') else ""
|
||||||
|
data['original_title'] = title_info.select_one('h2.title').text.strip() if title_info.select_one('h2.title') else ""
|
||||||
|
|
||||||
|
tag_containers = soup.select('.tag-container')
|
||||||
|
for container in tag_containers:
|
||||||
|
label = container.text.split(':')[0].strip().lower() if ':' in container.text else ""
|
||||||
|
tags = [t.select_one('.name').text for t in container.select('.tag')]
|
||||||
|
if 'tags' in label: data['tags'] = tags
|
||||||
|
elif 'artists' in label: data['artist'] = tags
|
||||||
|
elif 'groups' in label: data['circle'] = tags
|
||||||
|
elif 'parodies' in label: data['parody'] = tags
|
||||||
|
|
||||||
|
time_tag = soup.select_one('#info time')
|
||||||
|
if time_tag and time_tag.get('datetime'):
|
||||||
|
year_match = re.search(r'(\\d{4})', time_tag['datetime'])
|
||||||
|
if year_match: data['year'] = year_match.group(1)
|
||||||
|
|
||||||
|
data['url'] = target_url
|
||||||
|
data['id'] = target_url.split('/g/')[1].strip('/')
|
||||||
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
comics_logger.error(f"An error occurred during 'toberead' cleanup: {e}", exc_info=True)
|
comics_logger.error(f"Error parsing metadata: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _lookup_doujin_metadata(self, title, artist="Unknown", doujin_id=None):
|
||||||
|
base_url = "https://nhentai.net"
|
||||||
|
if doujin_id:
|
||||||
|
return self._fetch_metadata_from_url(f"{base_url}/g/{doujin_id}/")
|
||||||
|
|
||||||
|
clean_title = title.replace('_', ' ').strip()
|
||||||
|
clean_artist = artist.replace('_', ' ').strip() if artist != "Unknown" else ""
|
||||||
|
queries = []
|
||||||
|
if clean_artist: queries.append(f"{clean_artist} {clean_title}")
|
||||||
|
queries.append(clean_title)
|
||||||
|
|
||||||
|
for query in queries:
|
||||||
|
try:
|
||||||
|
search_url = f"{base_url}/search/?q={query.replace(' ', '+')}"
|
||||||
|
comics_logger.info(f"Searching metadata for: {query}")
|
||||||
|
response = self.scraper.get(search_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
soup = BeautifulSoup(response.content, 'html.parser')
|
||||||
|
results = soup.select('.gallery a.cover')
|
||||||
|
if results:
|
||||||
|
target_url = f"{base_url}{results[0]['href']}"
|
||||||
|
data = self._fetch_metadata_from_url(target_url)
|
||||||
|
if data: return data
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Search failed for query '{query}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _download_directory(self, samba_manager, remote_path, local_path):
|
||||||
|
os.makedirs(local_path, exist_ok=True)
|
||||||
|
items = samba_manager.list_path(remote_path)
|
||||||
|
if isinstance(items, dict) and "error" in items: raise Exception(items["error"])
|
||||||
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..']: continue
|
||||||
|
local_item_path = os.path.join(local_path, item['name'])
|
||||||
|
if item['is_directory']: self._download_directory(samba_manager, item['path'], local_item_path)
|
||||||
|
else:
|
||||||
|
with open(local_item_path, 'wb') as f:
|
||||||
|
samba_manager.download_file(item['path'], f)
|
||||||
|
|
||||||
|
def _extract_archive(self, archive_path, extract_path):
|
||||||
|
if zipfile.is_zipfile(archive_path):
|
||||||
|
with zipfile.ZipFile(archive_path, 'r') as zf:
|
||||||
|
zf.extractall(extract_path)
|
||||||
|
else: raise Exception("Unsupported archive format")
|
||||||
|
|
||||||
|
def _create_cbz(self, source_folder, output_path):
|
||||||
|
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
for root, dirs, files in os.walk(source_folder):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
arcname = os.path.relpath(file_path, source_folder)
|
||||||
|
zf.write(file_path, arcname)
|
||||||
|
|
||||||
|
def _create_metadata_file(self, series_name, author="Unknown", artist="Unknown", api_data=None):
|
||||||
|
content = f"Title: {series_name}\n"
|
||||||
|
if api_data:
|
||||||
|
if api_data.get('title'): content = f"Title: {api_data['title']}\n"
|
||||||
|
if api_data.get('original_title'): content += f"Original Title: {api_data['original_title']}\n"
|
||||||
|
artists = ", ".join(api_data.get('artist', [])) or artist
|
||||||
|
circles = ", ".join(api_data.get('circle', [])) or "Unknown"
|
||||||
|
parodies = ", ".join(api_data.get('parody', [])) or "Original"
|
||||||
|
tags = ", ".join(api_data.get('tags', []))
|
||||||
|
content += f"Artist: {artists}\nCircle: {circles}\nParody: {parodies}\nTags: {tags}\nURL: {api_data.get('url', 'N/A')}\n"
|
||||||
|
else:
|
||||||
|
content += f"Artist: {artist}\nAuthor: {author}\n"
|
||||||
|
content += f"Processed by: ServerManagerWebApp\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
def _lookup_mangadex_metadata(self, title):
|
||||||
|
"""
|
||||||
|
Queries Mangadex API.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
url = "https://api.mangadex.org/manga"
|
||||||
|
clean_title = title.replace('_', ' ').strip()
|
||||||
|
comics_logger.info(f"Searching Mangadex for: {clean_title}")
|
||||||
|
|
||||||
|
# Mangadex requires separate calls for author/artist usually, but we'll start with basic info
|
||||||
|
params = {
|
||||||
|
"title": clean_title,
|
||||||
|
"limit": 1,
|
||||||
|
"includes[]": ["author", "artist", "cover_art"]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.scraper.get(url, params=params, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get('data'):
|
||||||
|
manga = data['data'][0]
|
||||||
|
attr = manga['attributes']
|
||||||
|
|
||||||
|
# Extract authors/artists
|
||||||
|
authors = []
|
||||||
|
artists = []
|
||||||
|
for rel in manga['relationships']:
|
||||||
|
if rel['type'] == 'author': authors.append(rel.get('attributes', {}).get('name'))
|
||||||
|
if rel['type'] == 'artist': artists.append(rel.get('attributes', {}).get('name'))
|
||||||
|
|
||||||
|
# Tags
|
||||||
|
tags = [t['attributes']['name']['en'] for t in attr.get('tags', [])]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"title": attr['title'].get('en') or list(attr['title'].values())[0],
|
||||||
|
"original_title": attr.get('altTitles', [{}])[0].get('en', "") if attr.get('altTitles') else "",
|
||||||
|
"year": attr.get('year'),
|
||||||
|
"url": f"https://mangadex.org/title/{manga['id']}",
|
||||||
|
"id": manga['id'],
|
||||||
|
"tags": tags,
|
||||||
|
"author": authors,
|
||||||
|
"artist": artists,
|
||||||
|
"source": "Mangadex"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Mangadex lookup failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _lookup_hentai2read_metadata(self, title):
|
||||||
|
"""
|
||||||
|
Scrapes Hentai2Read.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
base_url = "https://hentai2read.com"
|
||||||
|
clean_title = title.replace('_', '+').strip()
|
||||||
|
search_url = f"{base_url}/search/?cmd={clean_title}"
|
||||||
|
comics_logger.info(f"Searching Hentai2Read for: {clean_title}")
|
||||||
|
|
||||||
|
response = self.scraper.get(search_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
soup = BeautifulSoup(response.content, 'html.parser')
|
||||||
|
# Hentai2Read search results structure
|
||||||
|
result_link = soup.select_one('.book-grid-item a')
|
||||||
|
|
||||||
|
if result_link:
|
||||||
|
target_url = result_link['href']
|
||||||
|
comics_logger.info(f"Fetching Hentai2Read details: {target_url}")
|
||||||
|
|
||||||
|
resp = self.scraper.get(target_url)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
soup = BeautifulSoup(resp.content, 'html.parser')
|
||||||
|
data = {"source": "Hentai2Read", "url": target_url}
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title_tag = soup.select_one('h3.block-title a')
|
||||||
|
if title_tag: data['title'] = title_tag.text.strip()
|
||||||
|
|
||||||
|
# Info list
|
||||||
|
info_items = soup.select('ul.list-simple-mini li')
|
||||||
|
for item in info_items:
|
||||||
|
text = item.text.strip()
|
||||||
|
if "Author" in text:
|
||||||
|
data['author'] = [a.strip() for a in text.replace("Author", "").strip(" :" ).split(',')]
|
||||||
|
elif "Artist" in text:
|
||||||
|
data['artist'] = [a.strip() for a in text.replace("Artist", "").strip(" :" ).split(',')]
|
||||||
|
elif "Parody" in text:
|
||||||
|
data['parody'] = [a.strip() for a in text.replace("Parody", "").strip(" :" ).split(',')]
|
||||||
|
elif "Storyline" in text or "Content" in text: # Tags
|
||||||
|
data['tags'] = [a.text.strip() for a in item.select('a')]
|
||||||
|
elif "Release" in text:
|
||||||
|
year_match = re.search(r'\\d{4}', text)
|
||||||
|
if year_match: data['year'] = year_match.group(0)
|
||||||
|
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Hentai2Read lookup failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _lookup_mangaupdates_metadata(self, title):
|
||||||
|
"""
|
||||||
|
Queries MangaUpdates API for metadata (good for Manhwa/Webtoons).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
url = "https://api.mangaupdates.com/v1/series/search"
|
||||||
|
# Replace underscores with spaces for better search
|
||||||
|
clean_title = title.replace('_', ' ').strip()
|
||||||
|
comics_logger.info(f"Searching MangaUpdates for: {clean_title}")
|
||||||
|
|
||||||
|
response = self.scraper.post(url, json={"search": clean_title}, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get('results'):
|
||||||
|
series = data['results'][0]['record']
|
||||||
|
return {
|
||||||
|
"title": series.get('title'),
|
||||||
|
"year": series.get('year'),
|
||||||
|
"url": series.get('url'),
|
||||||
|
"id": series.get('series_id'),
|
||||||
|
"tags": [g.get('genre') for g in series.get('genres', [])] if series.get('genres') else [],
|
||||||
|
"source": "MangaUpdates"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"MangaUpdates lookup failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _process_item(self, samba_manager, item, is_archive=False):
|
||||||
|
item_path = item['path']
|
||||||
|
item_name = item['name']
|
||||||
|
name_part, artist_part, doujin_id = self._get_series_info(item_name)
|
||||||
|
|
||||||
|
api_data = None
|
||||||
|
|
||||||
|
# 1. Try Mangadex (Standard/Manhwa)
|
||||||
|
if not api_data:
|
||||||
|
api_data = self._lookup_mangadex_metadata(name_part)
|
||||||
|
|
||||||
|
# 2. Try nhentai (Doujinshi)
|
||||||
|
if not api_data:
|
||||||
|
# Need to re-add "source": "nhentai" to the existing method logic or wrapper
|
||||||
|
data = self._lookup_doujin_metadata(name_part, artist_part, doujin_id)
|
||||||
|
if data:
|
||||||
|
data['source'] = "nhentai"
|
||||||
|
api_data = data
|
||||||
|
|
||||||
|
# 3. Try Hentai2Read (Fallback Doujin)
|
||||||
|
if not api_data:
|
||||||
|
api_data = self._lookup_hentai2read_metadata(name_part)
|
||||||
|
|
||||||
|
# 4. Try MangaUpdates (Final Fallback)
|
||||||
|
if not api_data:
|
||||||
|
api_data = self._lookup_mangaupdates_metadata(name_part)
|
||||||
|
|
||||||
|
final_series_name = name_part
|
||||||
|
if api_data:
|
||||||
|
if api_data.get('title'):
|
||||||
|
title_clean = re.sub(r'[<>:"/\\|?*]', '', api_data['title']).strip()
|
||||||
|
final_series_name = title_clean.replace(' ', '_')
|
||||||
|
if api_data.get('year'): final_series_name = f"{final_series_name}_({api_data['year']})"
|
||||||
|
elif api_data.get('id'): final_series_name = f"{final_series_name}_({api_data['id']})"
|
||||||
|
|
||||||
|
final_series_name = final_series_name.replace(' ', '_')
|
||||||
|
series_folder_path = f"{self.comics_root}/{final_series_name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
samba_manager.create_directory(series_folder_path)
|
||||||
|
except OperationFailure: pass
|
||||||
|
|
||||||
|
if item_name == final_series_name: return
|
||||||
|
|
||||||
|
comics_logger.info(f"Processing: {item_name} -> {final_series_name}")
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
extraction_path = os.path.join(temp_dir, "extracted")
|
||||||
|
os.makedirs(extraction_path, exist_ok=True)
|
||||||
|
if is_archive:
|
||||||
|
local_archive = os.path.join(temp_dir, item_name)
|
||||||
|
with open(local_archive, 'wb') as f: samba_manager.download_file(item_path, f)
|
||||||
|
try: self._extract_archive(local_archive, extraction_path)
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Failed to extract {item_name}: {e}")
|
||||||
|
return
|
||||||
|
else: self._download_directory(samba_manager, item_path, extraction_path)
|
||||||
|
|
||||||
|
has_images = False
|
||||||
|
for root, _, files in os.walk(extraction_path):
|
||||||
|
if any(f.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')) for f in files):
|
||||||
|
has_images = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_images:
|
||||||
|
cbz_name = f"{final_series_name}.cbz"
|
||||||
|
temp_cbz = os.path.join(temp_dir, cbz_name)
|
||||||
|
self._create_cbz(extraction_path, temp_cbz)
|
||||||
|
dest_cbz_path = f"{series_folder_path}/{cbz_name}"
|
||||||
|
|
||||||
|
with open(temp_cbz, 'rb') as f:
|
||||||
|
res = samba_manager.upload_file(dest_cbz_path, f)
|
||||||
|
if isinstance(res, dict) and "error" in res: raise Exception(f"Upload failed: {res['error']}")
|
||||||
|
|
||||||
|
meta_content = self._create_metadata_file(final_series_name, artist=artist_part, api_data=api_data)
|
||||||
|
meta_path = f"{series_folder_path}/{final_series_name}_info.txt"
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp_meta:
|
||||||
|
tmp_meta.write(meta_content)
|
||||||
|
tmp_meta.flush()
|
||||||
|
tmp_meta.seek(0)
|
||||||
|
with open(tmp_meta.name, 'rb') as f: samba_manager.upload_file(meta_path, f)
|
||||||
|
os.unlink(tmp_meta.name)
|
||||||
|
|
||||||
|
if is_archive: samba_manager.delete_file(item_path)
|
||||||
|
else: samba_manager.delete_directory_recursive(item_path)
|
||||||
|
else: comics_logger.warning(f"No images found in {item_name}, skipping.")
|
||||||
|
|
||||||
def organize_comics(self, samba_manager: SambaManager):
|
def organize_comics(self, samba_manager: SambaManager):
|
||||||
comics_logger.info("Starting comics organization process...")
|
comics_logger.info(f"Starting organization in {self.comics_root}...")
|
||||||
comics_root = "/comics/toberead"
|
self._recursive_scan_and_process(samba_manager, self.comics_root)
|
||||||
comics_dest_root = "/comics/manga"
|
|
||||||
|
def _recursive_scan_and_process(self, samba_manager, current_path):
|
||||||
try:
|
try:
|
||||||
items_in_toberead = samba_manager.list_path(comics_root)
|
items = samba_manager.list_path(current_path)
|
||||||
if "error" in items_in_toberead:
|
if isinstance(items, dict) and "error" in items: return
|
||||||
raise Exception(f"Failed to list files in 'toberead': {items_in_toberead['error']}")
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..']: continue
|
||||||
|
if item['is_directory']:
|
||||||
|
sub_items = samba_manager.list_path(item['path'])
|
||||||
|
if any(sub['name'].lower().endswith(('.jpg', '.jpeg', '.png', '.webp')) for sub in sub_items):
|
||||||
|
self._process_item(samba_manager, item, is_archive=False)
|
||||||
|
else: self._recursive_scan_and_process(samba_manager, item['path'])
|
||||||
|
elif item['name'].lower().endswith(('.zip', '.cbz')):
|
||||||
|
self._process_item(samba_manager, item, is_archive=True)
|
||||||
|
except Exception as e: comics_logger.error(f"Error scanning {current_path}: {e}")
|
||||||
|
|
||||||
series_chapters = defaultdict(list)
|
def get_pending_comics(self, samba_manager: SambaManager):
|
||||||
for item in items_in_toberead:
|
try:
|
||||||
if not item["is_directory"]:
|
items = samba_manager.list_path(self.comics_root)
|
||||||
series_name = self._get_series_name(item["name"])
|
if isinstance(items, dict) and "error" in items: return []
|
||||||
series_chapters[series_name].append(item)
|
return [{"name": i['name'], "path": i['path']} for i in items if i['is_directory'] and i['name'] not in ['.', '..']]
|
||||||
|
except Exception: return []
|
||||||
|
|
||||||
|
def move_series(self, src_samba: SambaManager, series_names, dest_share=None, dest_path=None):
|
||||||
|
"""Moves folders from isolation/staging to library share."""
|
||||||
|
share = dest_share if dest_share else self.library_share_default
|
||||||
|
path = dest_path if dest_path else self.library_path_default
|
||||||
|
|
||||||
|
# Connect to destination share
|
||||||
|
dest_samba = SambaManager(src_samba.server_ip, share, src_samba.username, src_samba.password)
|
||||||
|
results = {"success": [], "failed": []}
|
||||||
|
|
||||||
|
for series in series_names:
|
||||||
|
src_folder = f"{self.comics_root}/{series}"
|
||||||
|
target_folder = f"{path}/{series}"
|
||||||
|
|
||||||
comics_logger.info(f"Found {len(series_chapters)} series to process.")
|
try:
|
||||||
|
# Ensure target directory exists
|
||||||
|
dest_samba.create_directory(target_folder)
|
||||||
|
|
||||||
|
# List files in source (on isolation share)
|
||||||
|
items = src_samba.list_path(src_folder)
|
||||||
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..']: continue
|
||||||
|
|
||||||
|
# Cross-share move: Download -> Upload -> Delete
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
local_file = os.path.join(temp_dir, item['name'])
|
||||||
|
with open(local_file, 'wb') as f: src_samba.download_file(item['path'], f)
|
||||||
|
with open(local_file, 'rb') as f: dest_samba.upload_file(f"{target_folder}/{item['name']}", f)
|
||||||
|
src_samba.delete_file(item['path'])
|
||||||
|
|
||||||
|
src_samba.delete_directory(src_folder)
|
||||||
|
results["success"].append(series)
|
||||||
|
comics_logger.info(f"Moved {series} to {share}:{target_folder}")
|
||||||
|
except Exception as e:
|
||||||
|
results["failed"].append({"name": series, "error": str(e)})
|
||||||
|
comics_logger.error(f"Failed to move {series}: {e}")
|
||||||
|
|
||||||
|
dest_samba.close()
|
||||||
|
return results
|
||||||
|
|
||||||
for series_name, chapters in series_chapters.items():
|
def update_existing_metadata(self, samba_manager, target_path="/comics/manga", force=False):
|
||||||
comics_logger.info(f"Processing series: {series_name}")
|
"""
|
||||||
final_series_folder_path = f"{comics_dest_root}/{series_name}".replace("\\", "/")
|
Scans an existing library directory for series folders and updates/creates metadata files.
|
||||||
|
"""
|
||||||
|
comics_logger.info(f"Starting metadata update in {target_path} (Force: {force})...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
items = samba_manager.list_path(target_path)
|
||||||
|
if isinstance(items, dict) and "error" in items:
|
||||||
|
comics_logger.error(f"Error listing {target_path}: {items['error']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..']: continue
|
||||||
|
if not item['is_directory']: continue
|
||||||
|
|
||||||
|
series_name = item['name']
|
||||||
|
series_path = item['path']
|
||||||
|
|
||||||
|
# Check for existing metadata
|
||||||
|
meta_filename = f"{series_name}_info.txt"
|
||||||
|
meta_path = f"{series_path}/{meta_filename}"
|
||||||
|
|
||||||
|
# Check if meta exists
|
||||||
|
series_contents = samba_manager.list_path(series_path)
|
||||||
|
has_meta = False
|
||||||
|
if isinstance(series_contents, list):
|
||||||
|
for sub in series_contents:
|
||||||
|
if sub['name'] == meta_filename:
|
||||||
|
has_meta = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_meta and not force:
|
||||||
|
continue
|
||||||
|
|
||||||
|
comics_logger.info(f"Updating metadata for: {series_name}")
|
||||||
|
|
||||||
|
# Parse series info from FOLDER NAME
|
||||||
|
clean_name = series_name.replace('_', ' ')
|
||||||
|
clean_name = re.sub(r'\s*\\(\\d+\\)$', '', clean_name).strip()
|
||||||
|
|
||||||
|
artist = "Unknown"
|
||||||
|
artist_match = re.match(r'^\\[(.*?)\\]', clean_name)
|
||||||
|
if artist_match:
|
||||||
|
artist = artist_match.group(1)
|
||||||
|
clean_name = clean_name[artist_match.end():].strip()
|
||||||
|
|
||||||
|
doujin_id = None
|
||||||
|
id_match = re.search(r'\\(\\d{5,7}\\)$', series_name)
|
||||||
|
if id_match:
|
||||||
|
doujin_id = id_match.group(1)
|
||||||
|
|
||||||
|
# 1. Try Mangadex
|
||||||
|
api_data = self._lookup_mangadex_metadata(clean_name)
|
||||||
|
|
||||||
|
# 2. Try nhentai
|
||||||
|
if not api_data:
|
||||||
|
data = self._lookup_doujin_metadata(clean_name, artist, doujin_id)
|
||||||
|
if data:
|
||||||
|
data['source'] = "nhentai"
|
||||||
|
api_data = data
|
||||||
|
|
||||||
|
# 3. Try Hentai2Read
|
||||||
|
if not api_data:
|
||||||
|
api_data = self._lookup_hentai2read_metadata(clean_name)
|
||||||
|
|
||||||
|
# 4. Try MangaUpdates
|
||||||
|
if not api_data:
|
||||||
|
api_data = self._lookup_mangaupdates_metadata(clean_name)
|
||||||
|
|
||||||
|
meta_content = self._create_metadata_file(clean_name, artist=artist, api_data=api_data)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp_meta:
|
||||||
|
tmp_meta.write(meta_content)
|
||||||
|
tmp_meta.flush()
|
||||||
|
tmp_meta.seek(0)
|
||||||
|
with open(tmp_meta.name, 'rb') as f:
|
||||||
|
samba_manager.upload_file(meta_path, f)
|
||||||
|
os.unlink(tmp_meta.name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Error updating metadata in {target_path}: {e}")
|
||||||
|
|
||||||
|
def _collect_all_folders(self, samba_manager, path):
|
||||||
|
folders = []
|
||||||
|
try:
|
||||||
|
items = samba_manager.list_path(path)
|
||||||
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..']: continue
|
||||||
|
if item['is_directory']:
|
||||||
|
folders.append({'name': item['name'], 'path': item['path']})
|
||||||
|
# Recurse
|
||||||
|
folders.extend(self._collect_all_folders(samba_manager, item['path']))
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Error listing path {path}: {e}")
|
||||||
|
return folders
|
||||||
|
|
||||||
|
def find_similar_folders(self, samba_manager: SambaManager, root_path="/comics/manga", threshold=0.9):
|
||||||
|
"""
|
||||||
|
Scans for folders with similar names.
|
||||||
|
"""
|
||||||
|
comics_logger.info(f"Scanning for duplicate folders in {root_path}...")
|
||||||
|
all_dirs = self._collect_all_folders(samba_manager, root_path)
|
||||||
|
comics_logger.info(f"Found {len(all_dirs)} directories. Comparing...")
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
processed_indices = set()
|
||||||
|
|
||||||
|
for i in range(len(all_dirs)):
|
||||||
|
if i in processed_indices: continue
|
||||||
|
|
||||||
|
current_group = [all_dirs[i]]
|
||||||
|
|
||||||
|
for j in range(i + 1, len(all_dirs)):
|
||||||
|
if j in processed_indices: continue
|
||||||
|
|
||||||
|
name1 = all_dirs[i]['name'].lower().replace('_', ' ')
|
||||||
|
name2 = all_dirs[j]['name'].lower().replace('_', ' ')
|
||||||
|
|
||||||
|
ratio = difflib.SequenceMatcher(None, name1, name2).ratio()
|
||||||
|
|
||||||
|
if ratio >= threshold:
|
||||||
|
current_group.append(all_dirs[j])
|
||||||
|
processed_indices.add(j)
|
||||||
|
|
||||||
|
if len(current_group) > 1:
|
||||||
|
groups.append({
|
||||||
|
"name": all_dirs[i]['name'],
|
||||||
|
"folders": current_group
|
||||||
|
})
|
||||||
|
processed_indices.add(i)
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def delete_folder(self, samba_manager: SambaManager, folder_path):
|
||||||
|
"""
|
||||||
|
Deletes a specific folder.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
comics_logger.info(f"Deleting duplicate folder: {folder_path}")
|
||||||
|
samba_manager.delete_directory_recursive(folder_path)
|
||||||
|
return {"success": True}
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Failed to delete folder {folder_path}: {e}")
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
def _get_artist_from_info(self, samba_manager, series_path, series_name):
|
||||||
|
info_filename = f"{series_name}_info.txt"
|
||||||
|
info_path = f"{series_path}/{info_filename}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as tmp:
|
||||||
|
samba_manager.download_file(info_path, tmp)
|
||||||
|
tmp.seek(0)
|
||||||
|
content = tmp.read().decode('utf-8', errors='ignore')
|
||||||
|
|
||||||
|
# Parse content
|
||||||
|
artist = "Unknown"
|
||||||
|
author = "Unknown"
|
||||||
|
|
||||||
|
for line in content.splitlines():
|
||||||
|
if line.startswith("Artist:"):
|
||||||
|
val = line.split(":", 1)[1].strip()
|
||||||
|
if val and val.lower() != "unknown":
|
||||||
|
artist = val.split(',')[0].strip() # Take first artist if multiple
|
||||||
|
elif line.startswith("Author:"):
|
||||||
|
val = line.split(":", 1)[1].strip()
|
||||||
|
if val and val.lower() != "unknown":
|
||||||
|
author = val.split(',')[0].strip()
|
||||||
|
|
||||||
|
if artist != "Unknown": return artist
|
||||||
|
if author != "Unknown": return author
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# File might not exist or other error
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
if 'tmp' in locals() and os.path.exists(tmp.name):
|
||||||
|
os.unlink(tmp.name)
|
||||||
|
|
||||||
|
return "_Unknown"
|
||||||
|
|
||||||
|
def sort_by_artist(self, samba_manager, root_path="/comics/manga"):
|
||||||
|
comics_logger.info(f"Sorting by Artist in {root_path}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
items = samba_manager.list_path(root_path)
|
||||||
|
if isinstance(items, dict) and "error" in items:
|
||||||
|
comics_logger.error(f"Error listing {root_path}: {items['error']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if item['name'] in ['.', '..', '_Unknown']: continue
|
||||||
|
if not item['is_directory']: continue
|
||||||
|
|
||||||
|
series_name = item['name']
|
||||||
|
series_path = item['path']
|
||||||
|
|
||||||
|
# Check if this is a Series Folder
|
||||||
|
# Criteria: Contains .cbz, .zip, or _info.txt
|
||||||
|
try:
|
||||||
|
sub_items = samba_manager.list_path(series_path)
|
||||||
|
if isinstance(sub_items, dict) and "error" in sub_items: continue
|
||||||
|
|
||||||
|
is_series = False
|
||||||
|
for sub in sub_items:
|
||||||
|
if sub['name'].lower().endswith(('.cbz', '.zip', '_info.txt')):
|
||||||
|
is_series = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not is_series:
|
||||||
|
comics_logger.info(f"Skipping potential Artist folder or empty folder: {series_name}")
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Attempt to get artist
|
||||||
|
artist = self._get_artist_from_info(samba_manager, series_path, series_name)
|
||||||
|
|
||||||
|
# Sanitize artist name for folder
|
||||||
|
clean_artist = re.sub(r'[<>:"/\\|?*]', '', artist).strip().replace(' ', '_')
|
||||||
|
if not clean_artist: clean_artist = "_Unknown"
|
||||||
|
|
||||||
|
# Target path: /comics/manga/Artist/Series
|
||||||
|
artist_folder = f"{root_path}/{clean_artist}"
|
||||||
|
target_path = f"{artist_folder}/{series_name}"
|
||||||
|
|
||||||
|
# Skip if already in place
|
||||||
|
if series_name == clean_artist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if we are moving into itself (e.g. Root/Artist -> Root/Artist/Artist)
|
||||||
|
# This happens if 'SeriesName' == 'ArtistName' and it was already sorted?
|
||||||
|
# But we checked is_series. An Artist folder usually doesn't have cbz inside directly.
|
||||||
|
|
||||||
|
comics_logger.info(f"Moving '{series_name}' to Artist folder '{clean_artist}'")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
samba_manager.create_directory(final_series_folder_path)
|
# Create Artist folder
|
||||||
except OperationFailure as e:
|
|
||||||
if "FILE_OBJECT_NAME_COLLISION" not in str(e):
|
|
||||||
comics_logger.error(f"Could not create directory {final_series_folder_path}: {e}")
|
|
||||||
|
|
||||||
for chapter_item in chapters:
|
|
||||||
original_path = chapter_item["path"].replace("\\", "/")
|
|
||||||
original_filename = chapter_item["name"]
|
|
||||||
|
|
||||||
path_to_move = ""
|
|
||||||
filename_to_move = ""
|
|
||||||
|
|
||||||
if original_filename.lower().endswith('.zip'):
|
|
||||||
cbz_filename = os.path.splitext(original_filename)[0] + '.cbz'
|
|
||||||
temp_cbz_path = f"{comics_root}/{cbz_filename}".replace("\\", "/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
samba_manager.rename_file(original_path, temp_cbz_path)
|
|
||||||
path_to_move = temp_cbz_path
|
|
||||||
filename_to_move = cbz_filename
|
|
||||||
comics_logger.info(f"Renamed {original_filename} to {cbz_filename}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to rename ZIP {original_filename}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif original_filename.lower().endswith('.cbz'):
|
|
||||||
path_to_move = original_path
|
|
||||||
filename_to_move = original_filename
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
final_cbz_path = f"{final_series_folder_path}/{filename_to_move}".replace("\\", "/")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
samba_manager.rename_file(path_to_move, final_cbz_path)
|
samba_manager.create_directory(artist_folder)
|
||||||
comics_logger.info(f"Moved {filename_to_move} to {final_series_folder_path}")
|
except OperationFailure: pass # Exists
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to move {filename_to_move} to {final_series_folder_path}: {e}")
|
# Move Series folder
|
||||||
|
samba_manager.rename_file(series_path, target_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
comics_logger.error(f"Failed to move {series_name}: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
comics_logger.error(f"An error occurred during comics organization: {e}", exc_info=True)
|
comics_logger.error(f"Sort by artist failed: {e}")
|
||||||
|
|
||||||
comics_logger.info("Comics organization process finished.")
|
|
||||||
|
|
||||||
return
|
|
||||||
@@ -2,6 +2,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ logging.basicConfig(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SETTINGS_FILE = "resources/config/app_settings.json"
|
||||||
|
|
||||||
def check_gpu_support():
|
def check_gpu_support():
|
||||||
try:
|
try:
|
||||||
logger.debug("Checking ffmpeg for CUDA support...")
|
logger.debug("Checking ffmpeg for CUDA support...")
|
||||||
@@ -29,12 +33,48 @@ def check_gpu_support():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
samba_server_ip: str
|
samba_server_ip: str = "127.0.0.1"
|
||||||
samba_username: str
|
samba_username: str = "guest"
|
||||||
samba_password: str
|
samba_password: str = ""
|
||||||
|
comicvine_api_key: str | None = None
|
||||||
|
qbittorrent_url: str | None = None
|
||||||
|
qbittorrent_username: str | None = None
|
||||||
|
qbittorrent_password: str | None = None
|
||||||
gpu_enabled: bool = check_gpu_support()
|
gpu_enabled: bool = check_gpu_support()
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
default_comics_path: str = "/comics/manga"
|
||||||
|
default_videos_path: str = "/videos"
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env")
|
# Stash Integration
|
||||||
|
stash_enabled: bool = False
|
||||||
|
stash_db_path: str = "stash.sqlite"
|
||||||
|
stash_remote_db_path: str = "/appdata/stashapp/config/stash-go.sqlite"
|
||||||
|
stash_generated_path: str = "/media/stashapp/generated"
|
||||||
|
stash_remote_base: str = "/media/videos"
|
||||||
|
stash_container_base: str = "/data"
|
||||||
|
stash_share: str = "main"
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||||
|
|
||||||
|
def load_from_json(self):
|
||||||
|
if os.path.exists(SETTINGS_FILE):
|
||||||
|
try:
|
||||||
|
with open(SETTINGS_FILE, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for key, value in data.items():
|
||||||
|
if hasattr(self, key):
|
||||||
|
setattr(self, key, value)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load settings: {e}")
|
||||||
|
|
||||||
|
def save_to_json(self):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
|
||||||
|
with open(SETTINGS_FILE, 'w') as f:
|
||||||
|
json.dump(self.model_dump(), f, indent=4)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to save settings: {e}")
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
settings.load_from_json()
|
||||||
|
|||||||
@@ -2,54 +2,96 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import itertools
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import imagehash
|
import imagehash
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from .samba_manager import SambaManager
|
from .samba_manager import SambaManager
|
||||||
from .config import Settings
|
from .config import Settings
|
||||||
|
from .stash_service import StashService
|
||||||
from . import models
|
from . import models
|
||||||
import itertools
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class CancellationException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CancellableWriter:
|
||||||
|
def __init__(self, file_obj, check_cancel_func):
|
||||||
|
self.file_obj = file_obj
|
||||||
|
self.check_cancel_func = check_cancel_func
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
if self.check_cancel_func():
|
||||||
|
raise CancellationException("Scan canceled by user")
|
||||||
|
return self.file_obj.write(data)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
return self.file_obj.close()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
return self.file_obj.flush()
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
return self.file_obj.tell()
|
||||||
|
|
||||||
|
def seek(self, offset, whence=0):
|
||||||
|
return self.file_obj.seek(offset, whence)
|
||||||
|
|
||||||
class DuplicatesManager:
|
class DuplicatesManager:
|
||||||
def __init__(self, settings: Settings, db: Session):
|
def __init__(self, settings: Settings, db: Session):
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.db = db
|
self.db = db
|
||||||
self.videos_root = "/videos"
|
self.stash_service = StashService(settings)
|
||||||
|
self.exclusions_file = "resources/config/exclusions.json"
|
||||||
|
self._load_exclusions()
|
||||||
|
|
||||||
|
def _load_exclusions(self):
|
||||||
|
if os.path.exists(self.exclusions_file):
|
||||||
|
try:
|
||||||
|
with open(self.exclusions_file, 'r') as f:
|
||||||
|
self.exclusions = json.load(f)
|
||||||
|
except:
|
||||||
|
self.exclusions = []
|
||||||
|
else:
|
||||||
|
self.exclusions = []
|
||||||
|
|
||||||
|
def _save_exclusions(self):
|
||||||
|
os.makedirs(os.path.dirname(self.exclusions_file), exist_ok=True)
|
||||||
|
with open(self.exclusions_file, 'w') as f:
|
||||||
|
json.dump(self.exclusions, f)
|
||||||
|
|
||||||
|
def add_exclusion(self, path):
|
||||||
|
if path not in self.exclusions:
|
||||||
|
self.exclusions.append(path)
|
||||||
|
self._save_exclusions()
|
||||||
|
|
||||||
|
def remove_exclusion(self, path):
|
||||||
|
if path in self.exclusions:
|
||||||
|
self.exclusions.remove(path)
|
||||||
|
self._save_exclusions()
|
||||||
|
|
||||||
|
def _is_excluded(self, path):
|
||||||
|
for excl in self.exclusions:
|
||||||
|
if path.startswith(excl):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _get_video_duration(self, filepath):
|
def _get_video_duration(self, filepath):
|
||||||
logger.debug(f"Running ffprobe for duration of {filepath}")
|
|
||||||
try:
|
try:
|
||||||
command = [
|
command = [
|
||||||
"ffprobe",
|
"ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||||
"-v",
|
"-of", "default=noprint_wrappers=1:nokey=1", filepath
|
||||||
"error",
|
|
||||||
"-show_entries",
|
|
||||||
"format=duration",
|
|
||||||
"-of",
|
|
||||||
"default=noprint_wrappers=1:nokey=1",
|
|
||||||
filepath,
|
|
||||||
]
|
]
|
||||||
logger.debug(f"ffprobe command: {' '.join(command)}")
|
result = subprocess.run(command, capture_output=True, text=True, check=True)
|
||||||
result = subprocess.run(
|
|
||||||
command,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
logger.debug(f"ffprobe stdout: {result.stdout.strip()}")
|
|
||||||
logger.debug(f"ffprobe stderr: {result.stderr.strip()}")
|
|
||||||
return float(result.stdout)
|
return float(result.stdout)
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
except Exception as e:
|
||||||
logger.error(f"ffprobe failed for {filepath}: {e}")
|
logger.error(f"ffprobe failed for {filepath}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_frame_hash(self, filepath):
|
def _get_frame_hash(self, filepath, algorithm='phash'):
|
||||||
logger.debug(f"Running ffmpeg for frame hash of {filepath}")
|
|
||||||
tmp_frame_path = ""
|
tmp_frame_path = ""
|
||||||
try:
|
try:
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_frame:
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_frame:
|
||||||
@@ -57,264 +99,390 @@ class DuplicatesManager:
|
|||||||
command = ["ffmpeg"]
|
command = ["ffmpeg"]
|
||||||
if self.settings.gpu_enabled:
|
if self.settings.gpu_enabled:
|
||||||
command.extend(["-hwaccel", "cuda"])
|
command.extend(["-hwaccel", "cuda"])
|
||||||
|
|
||||||
command.extend([
|
|
||||||
"-i",
|
|
||||||
filepath,
|
|
||||||
"-ss",
|
|
||||||
"00:00:10",
|
|
||||||
"-vframes",
|
|
||||||
"1",
|
|
||||||
"-y",
|
|
||||||
tmp_frame_path,
|
|
||||||
])
|
|
||||||
|
|
||||||
logger.debug(f"ffmpeg command: {' '.join(command)}")
|
# Extract frame at 10s or 10%? Fixed 10s for now.
|
||||||
result = subprocess.run(
|
command.extend(["-i", filepath, "-ss", "00:00:10", "-vframes", "1", "-y", tmp_frame_path])
|
||||||
command,
|
|
||||||
capture_output=True,
|
subprocess.run(command, capture_output=True, check=True)
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
logger.debug(f"ffmpeg stdout: {result.stdout.strip()}")
|
|
||||||
logger.debug(f"ffmpeg stderr: {result.stderr.strip()}")
|
|
||||||
|
|
||||||
if os.path.exists(tmp_frame_path):
|
if os.path.exists(tmp_frame_path) and os.path.getsize(tmp_frame_path) > 0:
|
||||||
logger.debug(f"Temporary frame file exists: {tmp_frame_path}, size: {os.path.getsize(tmp_frame_path)} bytes")
|
img = Image.open(tmp_frame_path)
|
||||||
phash = imagehash.phash(Image.open(tmp_frame_path))
|
if algorithm == 'ahash': h = imagehash.average_hash(img)
|
||||||
return str(phash)
|
elif algorithm == 'dhash': h = imagehash.dhash(img)
|
||||||
else:
|
else: h = imagehash.phash(img)
|
||||||
logger.warning(f"Temporary frame file was not created: {tmp_frame_path}")
|
return str(h)
|
||||||
return None
|
except Exception as e:
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
logger.error(f"Hash generation failed for {filepath}: {e}")
|
||||||
logger.error(f"ffmpeg failed for {filepath}: {e}")
|
|
||||||
return None
|
|
||||||
except Image.UnidentifiedImageError as e:
|
|
||||||
logger.error(f"PIL.UnidentifiedImageError for {filepath} with temp file {tmp_frame_path}: {e}")
|
|
||||||
return None
|
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(tmp_frame_path):
|
if os.path.exists(tmp_frame_path):
|
||||||
os.remove(tmp_frame_path)
|
os.remove(tmp_frame_path)
|
||||||
|
return None
|
||||||
|
|
||||||
def _is_video_file(self, filename):
|
def generate_contact_sheet(self, video_path):
|
||||||
video_extensions = ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.webm']
|
"""
|
||||||
return any(filename.lower().endswith(ext) for ext in video_extensions)
|
Generates a 3x3 contact sheet for the video.
|
||||||
|
Returns (relative_path, phash_str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
duration = self._get_video_duration(video_path)
|
||||||
|
if not duration or duration < 10: return None, None
|
||||||
|
|
||||||
|
# Extract 9 frames at intervals
|
||||||
|
interval = duration / 10
|
||||||
|
timestamps = [interval * i for i in range(1, 10)]
|
||||||
|
|
||||||
|
# We use a temp dir to store frames, then stitch
|
||||||
|
# ffmpeg tile filter is good but seeking is faster for sparse frames on large files?
|
||||||
|
# Actually, `ffmpeg -i ... -vf fps=... tile=...` reads the whole file which is slow over network/SMB.
|
||||||
|
# Best to seek.
|
||||||
|
|
||||||
|
# Since we have the file locally in tmp_path (downloaded), seeking is fast.
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
with tempfile.TemporaryDirectory() as temp_frames_dir:
|
||||||
|
for idx, ts in enumerate(timestamps):
|
||||||
|
out_frame = os.path.join(temp_frames_dir, f"frame_{idx}.jpg")
|
||||||
|
# fast seek
|
||||||
|
subprocess.run(
|
||||||
|
["ffmpeg", "-ss", str(ts), "-i", video_path, "-vframes", "1", "-q:v", "5", "-vf", "scale=320:-1", "-y", out_frame],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False
|
||||||
|
)
|
||||||
|
if os.path.exists(out_frame):
|
||||||
|
frames.append(Image.open(out_frame))
|
||||||
|
|
||||||
|
if len(frames) < 4: return None, None # Need at least some frames
|
||||||
|
|
||||||
|
# Stitch 3x3 (or adaptive)
|
||||||
|
# Create blank image
|
||||||
|
w, h = frames[0].size
|
||||||
|
grid_w = w * 3
|
||||||
|
grid_h = h * 3
|
||||||
|
contact_sheet = Image.new('RGB', (grid_w, grid_h))
|
||||||
|
|
||||||
|
for idx, frame in enumerate(frames):
|
||||||
|
if idx >= 9: break
|
||||||
|
x = (idx % 3) * w
|
||||||
|
y = (idx // 3) * h
|
||||||
|
contact_sheet.paste(frame, (x, y))
|
||||||
|
|
||||||
|
# Save
|
||||||
|
cache_dir = "resources/cache/thumbnails"
|
||||||
|
os.makedirs(cache_dir, exist_ok=True)
|
||||||
|
# Use hash of path to ensure uniqueness/retrievability
|
||||||
|
filename_hash = imagehash.hex_to_hash(os.path.basename(video_path)) # Just use random or md5
|
||||||
|
import hashlib
|
||||||
|
file_hash = hashlib.md5(video_path.encode()).hexdigest()
|
||||||
|
out_name = f"{file_hash}.jpg"
|
||||||
|
out_path = os.path.join(cache_dir, out_name)
|
||||||
|
|
||||||
|
contact_sheet.save(out_path, "JPEG", quality=80)
|
||||||
|
|
||||||
|
# Calculate Hash of the SHEET
|
||||||
|
sheet_hash = imagehash.phash(contact_sheet)
|
||||||
|
|
||||||
|
return out_name, str(sheet_hash)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Contact sheet generation failed: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def _process_video_file(self, samba_manager, filepath, filename, size, algorithm='phash', scan_type='fast', log_func=None, cancel_check_func=None):
|
||||||
|
if cancel_check_func and cancel_check_func(): return
|
||||||
|
|
||||||
|
if self._is_excluded(filepath):
|
||||||
|
return
|
||||||
|
|
||||||
def _process_video_file(self, samba_manager: SambaManager, filepath, filename, size):
|
|
||||||
logger.info(f"Processing video: {filepath} ({filename})")
|
|
||||||
existing_video = self.db.query(models.VideoFile).filter_by(filepath=filepath).first()
|
existing_video = self.db.query(models.VideoFile).filter_by(filepath=filepath).first()
|
||||||
|
|
||||||
|
# If in scene mode, check if we already have the scene data
|
||||||
if existing_video:
|
if existing_video:
|
||||||
if existing_video.size == size:
|
if scan_type == 'scene' and not existing_video.contact_sheet_path:
|
||||||
logger.info(f"Skipping already processed and unaltered video: {filepath}")
|
if log_func: log_func(f"Updating {filename} with contact sheet")
|
||||||
return
|
# Continue to processing
|
||||||
|
elif existing_video.size == size:
|
||||||
|
return # Skip if unchanged
|
||||||
else:
|
else:
|
||||||
logger.info(f"File {filepath} has altered size ({existing_video.size} -> {size}). Re-processing.")
|
|
||||||
self.db.delete(existing_video)
|
self.db.delete(existing_video)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
existing_video = None
|
||||||
|
|
||||||
|
if log_func: log_func(f"Processing: {filename} (Mode: {scan_type})")
|
||||||
|
|
||||||
|
# --- Stash Integration ---
|
||||||
|
if self.settings.stash_enabled:
|
||||||
|
if cancel_check_func and cancel_check_func(): return
|
||||||
|
stash_phash, stash_oshash, scene_id, stash_duration = self.stash_service.get_file_metadata(filepath)
|
||||||
|
if stash_phash:
|
||||||
|
if log_func: log_func(f"Found Stash metadata for {filename}")
|
||||||
|
|
||||||
|
sheet_path = None
|
||||||
|
if scan_type == 'scene' and stash_oshash:
|
||||||
|
if cancel_check_func and cancel_check_func(): return
|
||||||
|
remote_sprite = self.stash_service.get_sprite_path(stash_oshash)
|
||||||
|
local_sheet_name = f"stash_{stash_oshash}.jpg"
|
||||||
|
local_sheet_path = os.path.join("resources/cache/thumbnails", local_sheet_name)
|
||||||
|
if not os.path.exists(local_sheet_path):
|
||||||
|
try:
|
||||||
|
# Ensure directory exists
|
||||||
|
os.makedirs(os.path.dirname(local_sheet_path), exist_ok=True)
|
||||||
|
|
||||||
|
# Use stash_share for sprite download
|
||||||
|
stash_samba = samba_manager
|
||||||
|
if samba_manager.share_name != self.settings.stash_share:
|
||||||
|
stash_samba = SambaManager(
|
||||||
|
self.settings.samba_server_ip,
|
||||||
|
self.settings.stash_share,
|
||||||
|
self.settings.samba_username,
|
||||||
|
self.settings.samba_password
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(local_sheet_path, "wb") as f:
|
||||||
|
stash_samba.download_file(remote_sprite, f)
|
||||||
|
sheet_path = local_sheet_name
|
||||||
|
finally:
|
||||||
|
if stash_samba != samba_manager:
|
||||||
|
stash_samba.close()
|
||||||
|
except Exception as e:
|
||||||
|
if log_func: log_func(f"Failed to download Stash sprite for {filename}: {e}")
|
||||||
|
else:
|
||||||
|
sheet_path = local_sheet_name
|
||||||
|
|
||||||
|
# Save to DB
|
||||||
|
video = models.VideoFile(
|
||||||
|
filepath=filepath, filename=filename, size=size,
|
||||||
|
duration=stash_duration or 0, phash=stash_phash,
|
||||||
|
contact_sheet_path=sheet_path,
|
||||||
|
scene_phash=None
|
||||||
|
)
|
||||||
|
self.db.add(video)
|
||||||
|
self.db.commit()
|
||||||
|
return
|
||||||
|
# --- End Stash Integration ---
|
||||||
|
|
||||||
|
if cancel_check_func and cancel_check_func(): return
|
||||||
|
|
||||||
|
# Download to temp
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as tmp_video:
|
with tempfile.NamedTemporaryFile(delete=False) as tmp_video:
|
||||||
try:
|
try:
|
||||||
samba_manager.download_file(filepath, tmp_video)
|
if log_func: log_func(f"Downloading {filename} ({size/1024/1024:.2f} MB)...")
|
||||||
|
|
||||||
|
# Wrap for cancellation during download
|
||||||
|
writer = tmp_video
|
||||||
|
if cancel_check_func:
|
||||||
|
writer = CancellableWriter(tmp_video, cancel_check_func)
|
||||||
|
|
||||||
|
samba_manager.download_file(filepath, writer)
|
||||||
|
tmp_path = tmp_video.name
|
||||||
|
except CancellationException:
|
||||||
|
if log_func: log_func(f"Download aborted for {filename}")
|
||||||
|
tmp_video.close()
|
||||||
|
os.remove(tmp_video.name)
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to download {filepath}: {e}")
|
if log_func: log_func(f"Download failed: {filepath} - {e}")
|
||||||
|
tmp_video.close()
|
||||||
|
os.remove(tmp_video.name)
|
||||||
return
|
return
|
||||||
|
|
||||||
duration = self._get_video_duration(tmp_video.name)
|
|
||||||
phash = self._get_frame_hash(tmp_video.name)
|
|
||||||
|
|
||||||
os.remove(tmp_video.name)
|
|
||||||
|
|
||||||
if duration is not None and phash is not None:
|
|
||||||
video_file = models.VideoFile(
|
|
||||||
filepath=filepath,
|
|
||||||
filename=filename,
|
|
||||||
size=size,
|
|
||||||
duration=duration,
|
|
||||||
phash=phash,
|
|
||||||
)
|
|
||||||
self.db.add(video_file)
|
|
||||||
self.db.commit()
|
|
||||||
logger.info(f"Processed video: {filepath}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"Could not get duration or hash for {filepath}")
|
|
||||||
|
|
||||||
def scan_videos(self, samba_manager: SambaManager):
|
|
||||||
state_file = "logs/duplicates_scan.state"
|
|
||||||
progress_file = "logs/duplicates_scan.progress"
|
|
||||||
pause_file = "logs/duplicates_scan.pause"
|
|
||||||
|
|
||||||
dirs_to_scan = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if os.path.exists(progress_file):
|
if cancel_check_func and cancel_check_func(): return
|
||||||
with open(progress_file, 'r') as f:
|
|
||||||
last_processed_dir = f.read().strip()
|
|
||||||
logger.info(f"Resuming scan from last in-progress directory: {last_processed_dir}")
|
|
||||||
dirs_to_scan.append(last_processed_dir)
|
|
||||||
|
|
||||||
if os.path.exists(state_file):
|
|
||||||
with open(state_file, 'r') as f:
|
|
||||||
dirs_to_scan.extend([line.strip() for line in f if line.strip()])
|
|
||||||
logger.info(f"Loaded {len(dirs_to_scan)} directories from state file.")
|
|
||||||
|
|
||||||
if not dirs_to_scan:
|
duration = self._get_video_duration(tmp_path)
|
||||||
dirs_to_scan = [self.videos_root]
|
phash = self._get_frame_hash(tmp_path, algorithm)
|
||||||
|
|
||||||
|
if cancel_check_func and cancel_check_func(): return
|
||||||
|
|
||||||
logger.info(f"Starting scan for videos in {self.videos_root} on share 'isolation'")
|
sheet_path = None
|
||||||
|
scene_hash = None
|
||||||
|
|
||||||
|
if scan_type == 'scene':
|
||||||
|
sheet_path, scene_hash = self.generate_contact_sheet(tmp_path)
|
||||||
|
|
||||||
|
if duration is not None: # phash might be None if image generation failed
|
||||||
|
if not existing_video:
|
||||||
|
video = models.VideoFile(
|
||||||
|
filepath=filepath, filename=filename, size=size,
|
||||||
|
duration=duration, phash=phash or "",
|
||||||
|
contact_sheet_path=sheet_path,
|
||||||
|
scene_phash=scene_hash
|
||||||
|
)
|
||||||
|
self.db.add(video)
|
||||||
|
else:
|
||||||
|
# Update existing
|
||||||
|
existing_video.duration = duration
|
||||||
|
existing_video.phash = phash or ""
|
||||||
|
if sheet_path: existing_video.contact_sheet_path = sheet_path
|
||||||
|
if scene_hash: existing_video.scene_phash = scene_hash
|
||||||
|
|
||||||
|
self.db.commit()
|
||||||
|
finally:
|
||||||
|
if os.path.exists(tmp_path):
|
||||||
|
try: os.remove(tmp_path)
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
def scan_videos(self, samba_manager, root_paths=["/videos"], algorithm='phash', scan_type='fast'):
|
||||||
|
state_file = "logs/duplicates_scan.state"
|
||||||
|
progress_file = "logs/duplicates_scan.json"
|
||||||
|
cancel_file = "logs/duplicates_scan.cancel"
|
||||||
|
log_file = "logs/duplicates_scan.log"
|
||||||
|
|
||||||
|
# Helper to log to file and console
|
||||||
|
def log(msg):
|
||||||
|
try:
|
||||||
|
with open(log_file, "a") as f:
|
||||||
|
f.write(f"{msg}\n")
|
||||||
|
except: pass
|
||||||
|
logger.info(msg)
|
||||||
|
|
||||||
|
is_cancelled = False
|
||||||
|
# Helper to check cancellation
|
||||||
|
def check_cancel():
|
||||||
|
nonlocal is_cancelled
|
||||||
|
if is_cancelled: return True
|
||||||
|
|
||||||
|
if os.path.exists(cancel_file):
|
||||||
|
log("Scan canceled by user.")
|
||||||
|
try:
|
||||||
|
os.remove(cancel_file)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
with open(progress_file, 'w') as f:
|
||||||
|
json.dump({"status": "canceled", "processed": processed_files}, f)
|
||||||
|
is_cancelled = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Clear log file
|
||||||
|
with open(log_file, "w") as f:
|
||||||
|
f.write("Scan started...\n")
|
||||||
|
|
||||||
|
# Ensure cancel file is gone before starting
|
||||||
|
if os.path.exists(cancel_file):
|
||||||
|
try: os.remove(cancel_file)
|
||||||
|
except OSError: pass
|
||||||
|
|
||||||
|
dirs_to_scan = list(root_paths)
|
||||||
|
processed_files = 0
|
||||||
|
|
||||||
|
try:
|
||||||
while dirs_to_scan:
|
while dirs_to_scan:
|
||||||
while os.path.exists(pause_file):
|
if check_cancel(): return
|
||||||
logger.info("Scan is paused. Waiting for resume signal...")
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
current_path = dirs_to_scan.pop(0)
|
current_path = dirs_to_scan.pop(0)
|
||||||
|
|
||||||
with open(progress_file, 'w') as f:
|
with open(progress_file, 'w') as f:
|
||||||
f.write(current_path)
|
json.dump({"status": "scanning", "current": current_path, "processed": processed_files}, f)
|
||||||
|
|
||||||
logger.info(f"Scanning directory: {current_path}")
|
try:
|
||||||
files_and_dirs = samba_manager.list_path(current_path)
|
log(f"Scanning directory: {current_path}")
|
||||||
|
items = samba_manager.list_path(current_path)
|
||||||
if "error" in files_and_dirs:
|
except Exception as e:
|
||||||
logger.error(f"Failed to list path {current_path}: {files_and_dirs['error']}")
|
log(f"Error listing {current_path}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
subdirs = []
|
for item in items:
|
||||||
for item in files_and_dirs:
|
if check_cancel(): return
|
||||||
while os.path.exists(pause_file):
|
|
||||||
logger.info("Scan is paused. Waiting for resume signal...")
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
if item["is_directory"]:
|
if item['name'] in ['.', '..']: continue
|
||||||
subdirs.append(item["path"])
|
|
||||||
elif self._is_video_file(item["name"]):
|
if item['is_directory']:
|
||||||
self._process_video_file(samba_manager, item["path"], item["name"], item["size"])
|
if not self._is_excluded(item['path']):
|
||||||
|
dirs_to_scan.append(item['path'])
|
||||||
|
elif any(item['name'].lower().endswith(ext) for ext in ['.mp4', '.mkv', '.avi', '.mov', '.wmv']):
|
||||||
|
self._process_video_file(
|
||||||
|
samba_manager, item['path'], item['name'], item['size'],
|
||||||
|
algorithm, scan_type,
|
||||||
|
log_func=log, cancel_check_func=check_cancel
|
||||||
|
)
|
||||||
|
processed_files += 1
|
||||||
|
|
||||||
|
with open(progress_file, 'w') as f:
|
||||||
|
json.dump({"status": "scanning", "current": item['path'], "processed": processed_files}, f)
|
||||||
|
|
||||||
|
log("Scan completed.")
|
||||||
|
with open(progress_file, 'w') as f:
|
||||||
|
json.dump({"status": "completed", "processed": processed_files}, f)
|
||||||
|
|
||||||
dirs_to_scan = subdirs + dirs_to_scan
|
|
||||||
|
|
||||||
if os.path.exists(progress_file):
|
|
||||||
os.remove(progress_file)
|
|
||||||
|
|
||||||
with open(state_file, 'w') as f:
|
|
||||||
for d in dirs_to_scan:
|
|
||||||
f.write(d + '\n')
|
|
||||||
|
|
||||||
if os.path.exists(state_file):
|
|
||||||
os.remove(state_file)
|
|
||||||
|
|
||||||
logger.info("Video scan complete.")
|
|
||||||
return {"status": "Scan complete"}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"An error occurred during video scan: {e}", exc_info=True)
|
log(f"Scan failed: {e}")
|
||||||
return {"status": "Scan failed", "error": str(e)}
|
with open(progress_file, 'w') as f:
|
||||||
|
json.dump({"status": "failed", "error": str(e)}, f)
|
||||||
|
|
||||||
def _hamming_distance(self, s1, s2):
|
def find_duplicates(self, threshold=0.95, method='fast'):
|
||||||
return sum(c1 != c2 for c1, c2 in zip(s1, s2))
|
|
||||||
|
|
||||||
def _calculate_similarity(self, file1: models.VideoFile, file2: models.VideoFile):
|
|
||||||
size_similarity = 1 - (abs(file1.size - file2.size) / max(file1.size, file2.size))
|
|
||||||
duration_similarity = 1 - (abs(file1.duration - file2.duration) / max(file1.duration, file2.duration))
|
|
||||||
hash_similarity = 1 - (self._hamming_distance(file1.phash, file2.phash) / len(file1.phash))
|
|
||||||
return (size_similarity * 0.2) + (duration_similarity * 0.3) + (hash_similarity * 0.5)
|
|
||||||
|
|
||||||
def find_duplicates(self, threshold=0.95):
|
|
||||||
report = models.DuplicateReport(status="running")
|
report = models.DuplicateReport(status="running")
|
||||||
self.db.add(report)
|
self.db.add(report)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
videos = self.db.query(models.VideoFile).all()
|
videos = self.db.query(models.VideoFile).all()
|
||||||
groups = []
|
groups = []
|
||||||
processed_videos = set()
|
processed_ids = set()
|
||||||
|
|
||||||
for video1, video2 in itertools.combinations(videos, 2):
|
for i in range(len(videos)):
|
||||||
if video1.id in processed_videos or video2.id in processed_videos:
|
if videos[i].id in processed_ids: continue
|
||||||
continue
|
|
||||||
|
group = [videos[i]]
|
||||||
score = self._calculate_similarity(video1, video2)
|
scores = []
|
||||||
if score >= threshold:
|
|
||||||
existing_group = None
|
for j in range(i + 1, len(videos)):
|
||||||
for group in groups:
|
if videos[j].id in processed_ids: continue
|
||||||
if video1.id in group["video_ids"] or video2.id in group["video_ids"]:
|
|
||||||
existing_group = group
|
|
||||||
break
|
|
||||||
|
|
||||||
if existing_group:
|
v1, v2 = videos[i], videos[j]
|
||||||
existing_group["video_ids"].add(video1.id)
|
|
||||||
existing_group["video_ids"].add(video2.id)
|
score = 0
|
||||||
existing_group["scores"].append(score)
|
if method == 'scene' and v1.scene_phash and v2.scene_phash:
|
||||||
|
# Compare Scene Hashes
|
||||||
|
dist = imagehash.hex_to_hash(v1.scene_phash) - imagehash.hex_to_hash(v2.scene_phash)
|
||||||
|
score = max(0, 1.0 - (dist / 64.0)) # 64 is typical max distance for 8x8 hash
|
||||||
else:
|
else:
|
||||||
groups.append({"video_ids": {video1.id, video2.id}, "scores": [score]})
|
# Standard Comparison
|
||||||
|
dist = imagehash.hex_to_hash(v1.phash) - imagehash.hex_to_hash(v2.phash) if v1.phash and v2.phash else 64
|
||||||
|
hash_sim = max(0, 1.0 - (dist / 64.0))
|
||||||
|
dur_sim = 1.0 - (abs(v1.duration - v2.duration) / max(v1.duration, v2.duration)) if max(v1.duration, v2.duration) > 0 else 1.0
|
||||||
|
size_sim = 1.0 - (abs(v1.size - v2.size) / max(v1.size, v2.size)) if max(v1.size, v2.size) > 0 else 1.0
|
||||||
|
score = (hash_sim * 0.6) + (dur_sim * 0.3) + (size_sim * 0.1)
|
||||||
|
|
||||||
processed_videos.add(video1.id)
|
if score >= threshold:
|
||||||
processed_videos.add(video2.id)
|
group.append(v2)
|
||||||
|
scores.append(score)
|
||||||
for group_data in groups:
|
processed_ids.add(v2.id)
|
||||||
avg_score = sum(group_data["scores"]) / len(group_data["scores"])
|
|
||||||
db_group = models.DuplicateFileGroup(report_id=report.id, score=avg_score)
|
if len(group) > 1:
|
||||||
self.db.add(db_group)
|
processed_ids.add(videos[i].id)
|
||||||
self.db.commit()
|
avg_score = sum(scores) / len(scores)
|
||||||
|
|
||||||
for video_id in group_data["video_ids"]:
|
db_group = models.DuplicateFileGroup(report_id=report.id, score=avg_score)
|
||||||
db_file = models.DuplicateFile(group_id=db_group.id, video_file_id=video_id)
|
self.db.add(db_group)
|
||||||
self.db.add(db_file)
|
self.db.commit()
|
||||||
|
|
||||||
|
for v in group:
|
||||||
|
db_file = models.DuplicateFile(group_id=db_group.id, video_file_id=v.id)
|
||||||
|
self.db.add(db_file)
|
||||||
|
|
||||||
report.status = "completed"
|
report.status = "completed"
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
return {"report_id": report.id}
|
||||||
return {"report_id": report.id, "status": "completed"}
|
|
||||||
|
|
||||||
def get_reports(self):
|
def get_reports(self):
|
||||||
return self.db.query(models.DuplicateReport).all()
|
return self.db.query(models.DuplicateReport).order_by(models.DuplicateReport.created_at.desc()).all()
|
||||||
|
|
||||||
def get_duplicate_report(self, report_id: int):
|
def get_report(self, report_id):
|
||||||
report = self.db.query(models.DuplicateReport).filter(models.DuplicateReport.id == report_id).first()
|
report = self.db.query(models.DuplicateReport).filter_by(id=report_id).first()
|
||||||
if not report:
|
if not report: return None
|
||||||
return {"error": "Report not found"}
|
|
||||||
|
|
||||||
groups = []
|
|
||||||
for group in report.groups:
|
|
||||||
files = []
|
|
||||||
for duplicate_file in group.files:
|
|
||||||
files.append(duplicate_file.video_file)
|
|
||||||
groups.append({
|
|
||||||
"group_id": group.id,
|
|
||||||
"score": group.score,
|
|
||||||
"files": files,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
res = {
|
||||||
"report_id": report.id,
|
"id": report.id,
|
||||||
"created_at": report.created_at,
|
|
||||||
"status": report.status,
|
"status": report.status,
|
||||||
"groups": groups,
|
"date": report.created_at,
|
||||||
|
"groups": []
|
||||||
}
|
}
|
||||||
|
for g in report.groups:
|
||||||
def delete_files(self, filepaths: list[str]):
|
files = [{
|
||||||
samba_manager = SambaManager(
|
"id": f.video_file.id,
|
||||||
self.settings.samba_server_ip,
|
"path": f.video_file.filepath,
|
||||||
"isolation", # Assuming all duplicates are in the isolation share
|
"size": f.video_file.size,
|
||||||
self.settings.samba_username,
|
"duration": f.video_file.duration,
|
||||||
self.settings.samba_password,
|
"contact_sheet": f.video_file.contact_sheet_path
|
||||||
)
|
} for f in g.files]
|
||||||
try:
|
res["groups"].append({"id": g.id, "score": g.score, "files": files})
|
||||||
for filepath in filepaths:
|
return res
|
||||||
# Delete from Samba
|
|
||||||
samba_manager.delete_file(filepath)
|
|
||||||
|
|
||||||
# Delete from database
|
|
||||||
video_file = self.db.query(models.VideoFile).filter_by(filepath=filepath).first()
|
|
||||||
if video_file:
|
|
||||||
# Delete associations in DuplicateFile
|
|
||||||
self.db.query(models.DuplicateFile).filter_by(video_file_id=video_file.id).delete()
|
|
||||||
self.db.delete(video_file)
|
|
||||||
|
|
||||||
self.db.commit()
|
|
||||||
return {"status": "success"}
|
|
||||||
except Exception as e:
|
|
||||||
self.db.rollback()
|
|
||||||
logger.error(f"Error deleting files: {e}", exc_info=True)
|
|
||||||
return {"status": "error", "message": str(e)}
|
|
||||||
finally:
|
|
||||||
samba_manager.close()
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,20 +1,39 @@
|
|||||||
from fastapi import FastAPI, Depends
|
import os
|
||||||
|
import json
|
||||||
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from .database import SessionLocal, engine, get_db
|
from .database import SessionLocal, engine, get_db
|
||||||
from .routers import samba, comics, duplicates, downloader
|
from .routers import samba, comics, duplicates, downloader, qbittorrent, system, tasks, settings, stash, scheduler as scheduler_router
|
||||||
|
from .scheduler import start_scheduler, scheduler
|
||||||
|
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI()
|
def reset_stuck_scans():
|
||||||
|
progress_file = "logs/duplicates_scan.json"
|
||||||
|
if os.path.exists(progress_file):
|
||||||
|
try:
|
||||||
|
with open(progress_file, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
if data.get("status") == "scanning":
|
||||||
|
with open(progress_file, 'w') as f:
|
||||||
|
json.dump({"status": "failed", "error": "Scan interrupted by server restart"}, f)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
origins = [
|
@asynccontextmanager
|
||||||
"http://localhost:5173", # Assuming frontend runs on this port during development
|
async def lifespan(app: FastAPI):
|
||||||
"http://127.0.0.1:5173",
|
reset_stuck_scans()
|
||||||
# Add your production frontend URL(s) here when deploying
|
start_scheduler()
|
||||||
]
|
yield
|
||||||
|
scheduler.shutdown()
|
||||||
|
|
||||||
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
|
origins = ["*"]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@@ -28,6 +47,12 @@ app.include_router(samba.router, prefix="/samba", tags=["samba"])
|
|||||||
app.include_router(comics.router, prefix="/comics", tags=["comics"])
|
app.include_router(comics.router, prefix="/comics", tags=["comics"])
|
||||||
app.include_router(duplicates.router, prefix="/duplicates", tags=["duplicates"])
|
app.include_router(duplicates.router, prefix="/duplicates", tags=["duplicates"])
|
||||||
app.include_router(downloader.router, prefix="/downloader", tags=["downloader"])
|
app.include_router(downloader.router, prefix="/downloader", tags=["downloader"])
|
||||||
|
app.include_router(qbittorrent.router, prefix="/qbittorrent", tags=["qbittorrent"])
|
||||||
|
app.include_router(system.router, prefix="/system", tags=["system"])
|
||||||
|
app.include_router(tasks.router, prefix="/tasks", tags=["tasks"])
|
||||||
|
app.include_router(settings.router, prefix="/settings", tags=["settings"])
|
||||||
|
app.include_router(stash.router, prefix="/stash", tags=["stash"])
|
||||||
|
app.include_router(scheduler_router.router, prefix="/scheduler", tags=["scheduler"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class VideoFile(Base):
|
|||||||
size = Column(Integer)
|
size = Column(Integer)
|
||||||
duration = Column(Float)
|
duration = Column(Float)
|
||||||
phash = Column(String)
|
phash = Column(String)
|
||||||
|
contact_sheet_path = Column(String, nullable=True)
|
||||||
|
scene_phash = Column(String, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class DuplicateFileGroup(Base):
|
class DuplicateFileGroup(Base):
|
||||||
@@ -42,3 +44,15 @@ class DuplicateFile(Base):
|
|||||||
video_file_id = Column(Integer, ForeignKey("video_files.id"))
|
video_file_id = Column(Integer, ForeignKey("video_files.id"))
|
||||||
group = relationship("DuplicateFileGroup", back_populates="files")
|
group = relationship("DuplicateFileGroup", back_populates="files")
|
||||||
video_file = relationship("VideoFile")
|
video_file = relationship("VideoFile")
|
||||||
|
|
||||||
|
|
||||||
|
class TaskHistory(Base):
|
||||||
|
__tablename__ = "task_history"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
task_id = Column(String, index=True) # Huey Task ID
|
||||||
|
name = Column(String)
|
||||||
|
status = Column(String) # running, success, failed
|
||||||
|
start_time = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
end_time = Column(DateTime, nullable=True)
|
||||||
|
details = Column(String, nullable=True)
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import qbittorrentapi
|
||||||
|
import logging
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class QbittorrentManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.url = settings.qbittorrent_url
|
||||||
|
self.username = settings.qbittorrent_username
|
||||||
|
self.password = settings.qbittorrent_password
|
||||||
|
self.client = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
if not self.url:
|
||||||
|
raise Exception("qBittorrent URL not configured")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.client = qbittorrentapi.Client(
|
||||||
|
host=self.url,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
VERIFY_WEBUI_CERTIFICATE=False
|
||||||
|
)
|
||||||
|
self.client.auth_log_in()
|
||||||
|
except qbittorrentapi.LoginFailed as e:
|
||||||
|
logger.error(f"qBittorrent Login Failed: {e}")
|
||||||
|
raise Exception("qBittorrent Login Failed")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"qBittorrent Connection Failed: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_torrents(self):
|
||||||
|
self.connect()
|
||||||
|
# Return list of dicts with relevant info
|
||||||
|
torrents = self.client.torrents_info()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"hash": t.hash,
|
||||||
|
"name": t.name,
|
||||||
|
"state": t.state,
|
||||||
|
"save_path": t.save_path,
|
||||||
|
"progress": t.progress,
|
||||||
|
"size": t.size,
|
||||||
|
"dlspeed": t.dlspeed,
|
||||||
|
"upspeed": t.upspeed,
|
||||||
|
"eta": t.eta
|
||||||
|
}
|
||||||
|
for t in torrents
|
||||||
|
]
|
||||||
|
|
||||||
|
def pause_torrents(self, hashes):
|
||||||
|
self.connect()
|
||||||
|
self.client.torrents_pause(torrent_hashes=hashes)
|
||||||
|
|
||||||
|
def resume_torrents(self, hashes):
|
||||||
|
self.connect()
|
||||||
|
self.client.torrents_resume(torrent_hashes=hashes)
|
||||||
|
|
||||||
|
def delete_torrents(self, hashes, delete_files=False):
|
||||||
|
self.connect()
|
||||||
|
self.client.torrents_delete(torrent_hashes=hashes, delete_files=delete_files)
|
||||||
|
|
||||||
|
def recheck_torrents(self, hashes):
|
||||||
|
self.connect()
|
||||||
|
self.client.torrents_recheck(torrent_hashes=hashes)
|
||||||
|
|
||||||
|
def set_location(self, hashes, location):
|
||||||
|
self.connect()
|
||||||
|
self.client.torrents_set_location(location=location, torrent_hashes=hashes)
|
||||||
@@ -1,40 +1,115 @@
|
|||||||
from fastapi import APIRouter, BackgroundTasks
|
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
from ..comics_manager import ComicsManager
|
from ..comics_manager import ComicsManager
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
from ..samba_manager import SambaManager
|
from ..samba_manager import SambaManager
|
||||||
|
from ..tasks import task_organize_comics, task_move_comics, task_update_metadata, task_sort_by_artist
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
def run_organize_comics_background():
|
class MoveRequest(BaseModel):
|
||||||
app_settings = settings
|
series_names: List[str]
|
||||||
manager = ComicsManager()
|
destination_share: Optional[str] = "comics"
|
||||||
samba_manager = SambaManager(
|
destination_path: Optional[str] = "/manga"
|
||||||
app_settings.samba_server_ip,
|
|
||||||
"isolation",
|
class MetadataUpdateRequest(BaseModel):
|
||||||
app_settings.samba_username,
|
target_path: Optional[str] = "/comics/manga"
|
||||||
app_settings.samba_password,
|
force_update: Optional[bool] = False
|
||||||
|
|
||||||
|
class FolderScanRequest(BaseModel):
|
||||||
|
root_path: Optional[str] = "/comics/manga"
|
||||||
|
threshold: float = 0.9
|
||||||
|
|
||||||
|
class DeleteFolderRequest(BaseModel):
|
||||||
|
folder_path: str
|
||||||
|
|
||||||
|
class SortRequest(BaseModel):
|
||||||
|
root_path: Optional[str] = "/comics/manga"
|
||||||
|
|
||||||
|
def get_samba_manager(share="isolation"):
|
||||||
|
return SambaManager(
|
||||||
|
settings.samba_server_ip,
|
||||||
|
share,
|
||||||
|
settings.samba_username,
|
||||||
|
settings.samba_password,
|
||||||
)
|
)
|
||||||
manager.organize_comics(samba_manager)
|
|
||||||
samba_manager.close()
|
|
||||||
|
|
||||||
@router.post("/organize")
|
@router.post("/organize")
|
||||||
def organize_comics(background_tasks: BackgroundTasks):
|
def organize_comics():
|
||||||
background_tasks.add_task(run_organize_comics_background)
|
task = task_organize_comics()
|
||||||
return {"message": "Comics organization started in the background."}
|
return {"message": f"Organization queued (Task ID: {task.id}). Check logs."}
|
||||||
|
|
||||||
def run_cleanup_toberead_background():
|
@router.get("/pending")
|
||||||
app_settings = settings
|
def get_pending_comics():
|
||||||
manager = ComicsManager()
|
manager = ComicsManager()
|
||||||
samba_manager = SambaManager(
|
samba_manager = get_samba_manager("isolation")
|
||||||
app_settings.samba_server_ip,
|
try:
|
||||||
"isolation",
|
return manager.get_pending_comics(samba_manager)
|
||||||
app_settings.samba_username,
|
finally:
|
||||||
app_settings.samba_password,
|
samba_manager.close()
|
||||||
)
|
|
||||||
manager.cleanup_toberead(samba_manager)
|
|
||||||
samba_manager.close()
|
|
||||||
|
|
||||||
@router.delete("/cleanup_toberead")
|
@router.post("/move")
|
||||||
def cleanup_toberead(background_tasks: BackgroundTasks):
|
def move_comics(request: MoveRequest):
|
||||||
background_tasks.add_task(run_cleanup_toberead_background)
|
task = task_move_comics(request.series_names, request.destination_share, request.destination_path)
|
||||||
return {"message": "Comics cleanup started in the background."}
|
return {"message": f"Move queued (Task ID: {task.id}). Check logs."}
|
||||||
|
|
||||||
|
@router.post("/update_metadata")
|
||||||
|
def update_metadata(request: MetadataUpdateRequest):
|
||||||
|
task = task_update_metadata(request.target_path, request.force_update)
|
||||||
|
return {"message": f"Metadata update queued (Task ID: {task.id}). Check logs."}
|
||||||
|
|
||||||
|
@router.post("/folders/scan")
|
||||||
|
def scan_folders(request: FolderScanRequest):
|
||||||
|
# This remains synchronous to return results immediately to UI
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
groups = manager.find_similar_folders(samba_manager, request.root_path, request.threshold)
|
||||||
|
return groups
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@router.post("/folders/delete")
|
||||||
|
def delete_folder(request: DeleteFolderRequest):
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
return manager.delete_folder(samba_manager, request.folder_path)
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@router.post("/sort_by_artist")
|
||||||
|
def sort_by_artist(request: SortRequest):
|
||||||
|
task = task_sort_by_artist(request.root_path)
|
||||||
|
return {"message": f"Sort by Artist queued (Task ID: {task.id}). Check logs."}
|
||||||
|
|
||||||
|
@router.websocket("/ws/logs")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
file_path = "logs/comics_organization.log"
|
||||||
|
|
||||||
|
# Ensure file exists
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||||
|
with open(file_path, "w") as f: f.write("")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
# Send last 20 lines for context
|
||||||
|
lines = f.readlines()
|
||||||
|
for line in lines[-20:]:
|
||||||
|
await websocket.send_text(line)
|
||||||
|
|
||||||
|
# Follow the file
|
||||||
|
f.seek(0, 2)
|
||||||
|
while True:
|
||||||
|
line = f.readline()
|
||||||
|
if line:
|
||||||
|
await websocket.send_text(line)
|
||||||
|
else:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
from fastapi import APIRouter, Depends, BackgroundTasks
|
from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from ..duplicates_manager import DuplicatesManager
|
from ..duplicates_manager import DuplicatesManager
|
||||||
from ..config import settings, Settings
|
from ..config import settings, Settings
|
||||||
from ..database import get_db, SessionLocal
|
from ..database import get_db, SessionLocal
|
||||||
from ..samba_manager import SambaManager
|
from ..samba_manager import SambaManager
|
||||||
|
from .. import models
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
class DeleteFilesRequest(BaseModel):
|
class ScanConfig(BaseModel):
|
||||||
filepaths: List[str]
|
paths: List[str] = ["/videos"]
|
||||||
|
algorithm: str = "phash"
|
||||||
|
scan_type: str = "fast" # fast or scene
|
||||||
|
|
||||||
def run_scan_videos_background():
|
class DeleteRequest(BaseModel):
|
||||||
|
ids: List[int]
|
||||||
|
|
||||||
|
class ExclusionRequest(BaseModel):
|
||||||
|
path: str
|
||||||
|
|
||||||
|
def run_scan_videos_background(paths, algorithm, scan_type):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
app_settings = Settings()
|
app_settings = Settings()
|
||||||
manager = DuplicatesManager(app_settings, db)
|
manager = DuplicatesManager(app_settings, db)
|
||||||
@@ -24,42 +35,72 @@ def run_scan_videos_background():
|
|||||||
app_settings.samba_password,
|
app_settings.samba_password,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
manager.scan_videos(samba_manager)
|
manager.scan_videos(samba_manager, paths, algorithm, scan_type)
|
||||||
finally:
|
finally:
|
||||||
samba_manager.close()
|
samba_manager.close()
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@router.post("/scan")
|
@router.post("/scan/start")
|
||||||
def scan_videos(background_tasks: BackgroundTasks):
|
def start_scan(config: ScanConfig, background_tasks: BackgroundTasks):
|
||||||
background_tasks.add_task(run_scan_videos_background)
|
background_tasks.add_task(run_scan_videos_background, config.paths, config.algorithm, config.scan_type)
|
||||||
return {"message": "Video scan started in the background."}
|
return {"message": "Scan started"}
|
||||||
|
|
||||||
@router.post("/scan/pause")
|
@router.post("/scan/cancel")
|
||||||
def pause_scan():
|
def cancel_scan():
|
||||||
with open("logs/duplicates_scan.pause", 'w') as f:
|
try:
|
||||||
pass
|
os.makedirs("logs", exist_ok=True)
|
||||||
return {"message": "Video scan paused."}
|
with open("logs/duplicates_scan.cancel", 'w') as f:
|
||||||
|
f.write("cancel")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to cancel scan: {e}")
|
||||||
|
return {"message": "Cancellation requested"}
|
||||||
|
|
||||||
@router.post("/scan/resume")
|
@router.get("/scan/log")
|
||||||
def resume_scan():
|
def get_scan_log(limit: int = 100):
|
||||||
if os.path.exists("logs/duplicates_scan.pause"):
|
log_file = "logs/duplicates_scan.log"
|
||||||
os.remove("logs/duplicates_scan.pause")
|
if not os.path.exists(log_file):
|
||||||
return {"message": "Video scan resumed."}
|
return {"lines": []}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Simple read and tail (not efficient for huge files but fine here)
|
||||||
|
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
return {"lines": lines[-limit:]}
|
||||||
|
except Exception as e:
|
||||||
|
return {"lines": [f"Error reading log: {e}"]}
|
||||||
|
|
||||||
@router.get("/scan/status")
|
@router.get("/scan/progress")
|
||||||
def get_scan_status():
|
def get_scan_progress():
|
||||||
if os.path.exists("logs/duplicates_scan.pause"):
|
path = "logs/duplicates_scan.json"
|
||||||
return {"status": "Paused"}
|
if os.path.exists(path):
|
||||||
if os.path.exists("logs/duplicates_scan.progress") or os.path.exists("logs/duplicates_scan.state"):
|
try:
|
||||||
return {"status": "Scanning"}
|
with open(path, 'r') as f:
|
||||||
return {"status": "Idle"}
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {"status": "error", "message": "Read failed"}
|
||||||
|
return {"status": "idle"}
|
||||||
|
|
||||||
|
@router.get("/exclusions")
|
||||||
|
def get_exclusions(db: Session = Depends(get_db)):
|
||||||
|
manager = DuplicatesManager(settings, db)
|
||||||
|
return manager.exclusions
|
||||||
|
|
||||||
|
@router.post("/exclusions")
|
||||||
|
def add_exclusion(req: ExclusionRequest, db: Session = Depends(get_db)):
|
||||||
|
manager = DuplicatesManager(settings, db)
|
||||||
|
manager.add_exclusion(req.path)
|
||||||
|
return {"message": "Added"}
|
||||||
|
|
||||||
|
@router.delete("/exclusions")
|
||||||
|
def remove_exclusion(req: ExclusionRequest, db: Session = Depends(get_db)):
|
||||||
|
manager = DuplicatesManager(settings, db)
|
||||||
|
manager.remove_exclusion(req.path)
|
||||||
|
return {"message": "Removed"}
|
||||||
|
|
||||||
@router.post("/find")
|
@router.post("/find")
|
||||||
def find_duplicates(threshold: float = 0.95, db: Session = Depends(get_db)):
|
def find_duplicates(threshold: float = 0.95, method: str = "fast", db: Session = Depends(get_db)):
|
||||||
manager = DuplicatesManager(settings, db)
|
manager = DuplicatesManager(settings, db)
|
||||||
result = manager.find_duplicates(threshold)
|
return manager.find_duplicates(threshold, method)
|
||||||
return result
|
|
||||||
|
|
||||||
@router.get("/reports")
|
@router.get("/reports")
|
||||||
def get_reports(db: Session = Depends(get_db)):
|
def get_reports(db: Session = Depends(get_db)):
|
||||||
@@ -69,9 +110,24 @@ def get_reports(db: Session = Depends(get_db)):
|
|||||||
@router.get("/report/{report_id}")
|
@router.get("/report/{report_id}")
|
||||||
def get_report(report_id: int, db: Session = Depends(get_db)):
|
def get_report(report_id: int, db: Session = Depends(get_db)):
|
||||||
manager = DuplicatesManager(settings, db)
|
manager = DuplicatesManager(settings, db)
|
||||||
return manager.get_duplicate_report(report_id)
|
res = manager.get_report(report_id)
|
||||||
|
if not res: raise HTTPException(status_code=404)
|
||||||
|
return res
|
||||||
|
|
||||||
@router.delete("/files")
|
@router.delete("/files")
|
||||||
def delete_files(request: DeleteFilesRequest, db: Session = Depends(get_db)):
|
def delete_files(request: DeleteRequest, db: Session = Depends(get_db)):
|
||||||
manager = DuplicatesManager(settings, db)
|
manager = DuplicatesManager(settings, db)
|
||||||
return manager.delete_files(request.filepaths)
|
deleted = manager.delete_files(request.ids)
|
||||||
|
return {"deleted_ids": deleted}
|
||||||
|
|
||||||
|
@router.get("/thumbnails/{video_id}")
|
||||||
|
def get_thumbnail(video_id: int, db: Session = Depends(get_db)):
|
||||||
|
video = db.query(models.VideoFile).get(video_id)
|
||||||
|
if not video or not video.contact_sheet_path:
|
||||||
|
raise HTTPException(status_code=404, detail="Thumbnail not found")
|
||||||
|
|
||||||
|
path = os.path.join("resources/cache/thumbnails", video.contact_sheet_path)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise HTTPException(status_code=404, detail="Thumbnail file missing")
|
||||||
|
|
||||||
|
return FileResponse(path)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List
|
||||||
|
from ..qbittorrent_manager import QbittorrentManager
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
manager = QbittorrentManager()
|
||||||
|
|
||||||
|
class TorrentActionRequest(BaseModel):
|
||||||
|
hashes: List[str]
|
||||||
|
|
||||||
|
class DeleteRequest(TorrentActionRequest):
|
||||||
|
delete_files: bool = False
|
||||||
|
|
||||||
|
class LocationRequest(TorrentActionRequest):
|
||||||
|
location: str
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_torrents():
|
||||||
|
try:
|
||||||
|
return manager.get_torrents()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/pause")
|
||||||
|
def pause_torrents(request: TorrentActionRequest):
|
||||||
|
try:
|
||||||
|
manager.pause_torrents(request.hashes)
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/resume")
|
||||||
|
def resume_torrents(request: TorrentActionRequest):
|
||||||
|
try:
|
||||||
|
manager.resume_torrents(request.hashes)
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/delete")
|
||||||
|
def delete_torrents(request: DeleteRequest):
|
||||||
|
try:
|
||||||
|
manager.delete_torrents(request.hashes, request.delete_files)
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/recheck")
|
||||||
|
def recheck_torrents(request: TorrentActionRequest):
|
||||||
|
try:
|
||||||
|
manager.recheck_torrents(request.hashes)
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/location")
|
||||||
|
def set_location(request: LocationRequest):
|
||||||
|
try:
|
||||||
|
manager.set_location(request.hashes, request.location)
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
@@ -215,3 +215,16 @@ def rename_file(share_name: str, request: RenameRequest):
|
|||||||
result = manager.rename_file(request.old_path, request.new_path)
|
result = manager.rename_file(request.old_path, request.new_path)
|
||||||
manager.close()
|
manager.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@router.get("/search/{share_name}")
|
||||||
|
def search_files(share_name: str, query: str, path: str = "/"):
|
||||||
|
manager = SambaManager(
|
||||||
|
settings.samba_server_ip,
|
||||||
|
share_name,
|
||||||
|
username=settings.samba_username,
|
||||||
|
password=settings.samba_password,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return manager.search_files(query, path)
|
||||||
|
finally:
|
||||||
|
manager.close()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from ..scheduler import add_job, get_jobs, remove_job
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
class JobCreate(BaseModel):
|
||||||
|
task_name: str
|
||||||
|
cron_expression: str
|
||||||
|
args: Optional[List[Any]] = []
|
||||||
|
kwargs: Optional[Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_jobs():
|
||||||
|
return get_jobs()
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
def create_job(job: JobCreate):
|
||||||
|
try:
|
||||||
|
job_id = add_job(job.task_name, job.cron_expression, job.args, job.kwargs)
|
||||||
|
return {"id": job_id, "message": "Job scheduled"}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.delete("/{job_id}")
|
||||||
|
def delete_job(job_id: str):
|
||||||
|
try:
|
||||||
|
remove_job(job_id)
|
||||||
|
return {"message": "Job deleted"}
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=404, detail="Job not found")
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
from ..config import settings
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
class SettingsUpdate(BaseModel):
|
||||||
|
samba_server_ip: str
|
||||||
|
samba_username: str
|
||||||
|
samba_password: str
|
||||||
|
qbittorrent_url: Optional[str]
|
||||||
|
qbittorrent_username: Optional[str]
|
||||||
|
qbittorrent_password: Optional[str]
|
||||||
|
default_comics_path: str
|
||||||
|
default_videos_path: str
|
||||||
|
stash_enabled: Optional[bool]
|
||||||
|
stash_db_path: Optional[str]
|
||||||
|
stash_generated_path: Optional[str]
|
||||||
|
stash_remote_base: Optional[str]
|
||||||
|
stash_container_base: Optional[str]
|
||||||
|
stash_share: Optional[str]
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def get_settings():
|
||||||
|
return settings.model_dump()
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
def update_settings(update: SettingsUpdate):
|
||||||
|
for key, value in update.model_dump().items():
|
||||||
|
if hasattr(settings, key):
|
||||||
|
setattr(settings, key, value)
|
||||||
|
settings.save_to_json()
|
||||||
|
return {"message": "Settings updated"}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from ..config import settings, Settings
|
||||||
|
from ..samba_manager import SambaManager
|
||||||
|
from ..database import get_db
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def sync_stash_db_task():
|
||||||
|
samba_manager = SambaManager(
|
||||||
|
settings.samba_server_ip,
|
||||||
|
settings.stash_share,
|
||||||
|
settings.samba_username,
|
||||||
|
settings.samba_password,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
logger.info(f"Syncing Stash DB from {settings.stash_remote_db_path}")
|
||||||
|
with open(settings.stash_db_path, "wb") as f:
|
||||||
|
samba_manager.download_file(settings.stash_remote_db_path, f)
|
||||||
|
logger.info("Stash DB synced successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Stash DB sync failed: {e}")
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@router.post("/sync")
|
||||||
|
def sync_stash(background_tasks: BackgroundTasks):
|
||||||
|
background_tasks.add_task(sync_stash_db_task)
|
||||||
|
return {"message": "Stash sync started"}
|
||||||
|
|
||||||
|
@router.get("/status")
|
||||||
|
def get_stash_status():
|
||||||
|
exists = os.path.exists(settings.stash_db_path)
|
||||||
|
size = os.path.getsize(settings.stash_db_path) if exists else 0
|
||||||
|
mtime = os.path.getmtime(settings.stash_db_path) if exists else 0
|
||||||
|
return {
|
||||||
|
"enabled": settings.stash_enabled,
|
||||||
|
"db_exists": exists,
|
||||||
|
"db_size": size,
|
||||||
|
"db_mtime": mtime
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
import psutil
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
def get_size(bytes, suffix="B"):
|
||||||
|
"""
|
||||||
|
Scale bytes to its proper format
|
||||||
|
e.g:
|
||||||
|
1253656 => '1.20MB'
|
||||||
|
1253656678 => '1.17GB'
|
||||||
|
"""
|
||||||
|
factor = 1024
|
||||||
|
for unit in ["", "K", "M", "G", "T", "P"]:
|
||||||
|
if bytes < factor:
|
||||||
|
return f"{bytes:.2f}{unit}{suffix}"
|
||||||
|
bytes /= factor
|
||||||
|
|
||||||
|
@router.get("/stats")
|
||||||
|
def get_system_stats():
|
||||||
|
# CPU
|
||||||
|
cpu_usage = psutil.cpu_percent(interval=1)
|
||||||
|
|
||||||
|
# Memory
|
||||||
|
svmem = psutil.virtual_memory()
|
||||||
|
memory_stats = {
|
||||||
|
"total": get_size(svmem.total),
|
||||||
|
"available": get_size(svmem.available),
|
||||||
|
"percent": svmem.percent,
|
||||||
|
"used": get_size(svmem.used)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disk (Root)
|
||||||
|
try:
|
||||||
|
partition = psutil.disk_usage("/")
|
||||||
|
disk_stats = {
|
||||||
|
"total": get_size(partition.total),
|
||||||
|
"free": get_size(partition.free),
|
||||||
|
"percent": partition.percent,
|
||||||
|
"used": get_size(partition.used)
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
disk_stats = {"error": "Unavailable"}
|
||||||
|
|
||||||
|
# Uptime
|
||||||
|
boot_time = psutil.boot_time()
|
||||||
|
uptime_seconds = time.time() - boot_time
|
||||||
|
uptime_string = time.strftime("%H:%M:%S", time.gmtime(uptime_seconds))
|
||||||
|
if uptime_seconds > 86400:
|
||||||
|
days = int(uptime_seconds // 86400)
|
||||||
|
uptime_string = f"{days} days, " + uptime_string
|
||||||
|
|
||||||
|
return {
|
||||||
|
"cpu": cpu_usage,
|
||||||
|
"memory": memory_stats,
|
||||||
|
"disk": disk_stats,
|
||||||
|
"uptime": uptime_string
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from ..database import get_db
|
||||||
|
from ..models import TaskHistory
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def get_tasks(limit: int = 50, db: Session = Depends(get_db)):
|
||||||
|
return db.query(TaskHistory).order_by(TaskHistory.start_time.desc()).limit(limit).all()
|
||||||
@@ -35,13 +35,54 @@ class SambaManager:
|
|||||||
"name": f.filename,
|
"name": f.filename,
|
||||||
"is_directory": f.isDirectory,
|
"is_directory": f.isDirectory,
|
||||||
"size": f.file_size,
|
"size": f.file_size,
|
||||||
"path": full_item_path
|
"path": full_item_path,
|
||||||
|
"last_modified": f.last_write_time
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error listing path '{current_dir_path}' on share '{self.share_name}': {e}", exc_info=True)
|
logger.error(f"Error listing path '{current_dir_path}' on share '{self.share_name}': {e}", exc_info=True)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def search_files(self, query, start_path="/"):
|
||||||
|
"""
|
||||||
|
Recursively searches for files matching query (partial, case-insensitive).
|
||||||
|
"""
|
||||||
|
matches = []
|
||||||
|
try:
|
||||||
|
stack = [start_path]
|
||||||
|
while stack:
|
||||||
|
current_path = stack.pop()
|
||||||
|
try:
|
||||||
|
files = self.conn.listPath(self.share_name, current_path)
|
||||||
|
except Exception:
|
||||||
|
continue # Skip unreadable dirs
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
if f.filename in ['.', '..']: continue
|
||||||
|
|
||||||
|
normalized_current_path = current_path
|
||||||
|
if normalized_current_path != '/' and not normalized_current_path.endswith('/'):
|
||||||
|
normalized_current_path += '/'
|
||||||
|
|
||||||
|
full_item_path = f"{normalized_current_path}{f.filename}".replace("\\", "/")
|
||||||
|
|
||||||
|
if query.lower() in f.filename.lower():
|
||||||
|
matches.append({
|
||||||
|
"name": f.filename,
|
||||||
|
"is_directory": f.isDirectory,
|
||||||
|
"size": f.file_size,
|
||||||
|
"path": full_item_path,
|
||||||
|
"last_modified": f.last_write_time
|
||||||
|
})
|
||||||
|
|
||||||
|
if f.isDirectory:
|
||||||
|
stack.append(full_item_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching path '{start_path}': {e}")
|
||||||
|
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
def delete_file(self, path):
|
def delete_file(self, path):
|
||||||
try:
|
try:
|
||||||
@@ -63,6 +104,8 @@ class SambaManager:
|
|||||||
self.conn.retrieveFile(self.share_name, path, file_obj)
|
self.conn.retrieveFile(self.share_name, path, file_obj)
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if "Scan canceled by user" in str(e):
|
||||||
|
raise e
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
def download_file_range(self, path, file_obj, offset, max_length):
|
def download_file_range(self, path, file_obj, offset, max_length):
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||||
|
from .database import engine
|
||||||
|
from .tasks import task_organize_comics, task_update_metadata, task_sort_by_artist
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Map readable names to Huey task functions
|
||||||
|
TASK_MAP = {
|
||||||
|
"organize_comics": task_organize_comics,
|
||||||
|
"update_metadata": task_update_metadata,
|
||||||
|
"sort_by_artist": task_sort_by_artist
|
||||||
|
}
|
||||||
|
|
||||||
|
jobstores = {
|
||||||
|
'default': SQLAlchemyJobStore(engine=engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler = BackgroundScheduler(jobstores=jobstores)
|
||||||
|
|
||||||
|
def start_scheduler():
|
||||||
|
try:
|
||||||
|
scheduler.start()
|
||||||
|
logger.info("Scheduler started.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to start scheduler: {e}")
|
||||||
|
|
||||||
|
def add_job(task_name, cron_str, args=None, kwargs=None):
|
||||||
|
if task_name not in TASK_MAP:
|
||||||
|
raise ValueError(f"Unknown task: {task_name}")
|
||||||
|
|
||||||
|
func = TASK_MAP[task_name]
|
||||||
|
|
||||||
|
# Parse cron string (e.g. "* * * * *") -> minute, hour, day, month, day_of_week
|
||||||
|
# Simple implementation: expect "min hour day month day_of_week"
|
||||||
|
try:
|
||||||
|
parts = cron_str.split()
|
||||||
|
if len(parts) != 5:
|
||||||
|
raise ValueError("Invalid cron format. Expected 5 fields.")
|
||||||
|
|
||||||
|
trigger_args = {
|
||||||
|
'minute': parts[0],
|
||||||
|
'hour': parts[1],
|
||||||
|
'day': parts[2],
|
||||||
|
'month': parts[3],
|
||||||
|
'day_of_week': parts[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
job = scheduler.add_job(func, 'cron', args=args, kwargs=kwargs, **trigger_args)
|
||||||
|
return job.id
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to add job: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_jobs():
|
||||||
|
jobs = []
|
||||||
|
for job in scheduler.get_jobs():
|
||||||
|
jobs.append({
|
||||||
|
"id": job.id,
|
||||||
|
"name": job.name,
|
||||||
|
"next_run": str(job.next_run_time),
|
||||||
|
"trigger": str(job.trigger)
|
||||||
|
})
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
def remove_job(job_id):
|
||||||
|
scheduler.remove_job(job_id)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from .config import Settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class StashService:
|
||||||
|
def __init__(self, settings: Settings):
|
||||||
|
self.settings = settings
|
||||||
|
self.db_path = settings.stash_db_path
|
||||||
|
|
||||||
|
def get_db_connection(self):
|
||||||
|
if not os.path.exists(self.db_path):
|
||||||
|
logger.warning(f"Stash database not found at {self.db_path}")
|
||||||
|
return None
|
||||||
|
return sqlite3.connect(self.db_path)
|
||||||
|
|
||||||
|
def translate_to_stash_path(self, local_path):
|
||||||
|
"""
|
||||||
|
Translates a local/SMB path to Stash internal container path.
|
||||||
|
Example: /media/videos/Girl/Scene.mp4 -> /data/Girl/Scene.mp4
|
||||||
|
"""
|
||||||
|
remote_base = self.settings.stash_remote_base.rstrip('/')
|
||||||
|
container_base = self.settings.stash_container_base.rstrip('/')
|
||||||
|
|
||||||
|
if local_path.startswith(remote_base):
|
||||||
|
return local_path.replace(remote_base, container_base, 1)
|
||||||
|
|
||||||
|
# If it doesn't start with remote_base, maybe it's already relative or formatted differently?
|
||||||
|
# Stash also uses basenames in 'files' table.
|
||||||
|
return local_path
|
||||||
|
|
||||||
|
def get_file_metadata(self, local_path):
|
||||||
|
"""
|
||||||
|
Returns (phash, oshash, scene_id, duration) from Stash DB for a given file.
|
||||||
|
"""
|
||||||
|
stash_path = self.translate_to_stash_path(local_path)
|
||||||
|
conn = self.get_db_connection()
|
||||||
|
if not conn:
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT id FROM files WHERE path = ?", (stash_path,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
file_id = row[0]
|
||||||
|
|
||||||
|
# Get fingerprints
|
||||||
|
cursor.execute("SELECT type, fingerprint FROM files_fingerprints WHERE file_id = ?", (file_id,))
|
||||||
|
fingerprints = cursor.fetchall()
|
||||||
|
|
||||||
|
phash = None
|
||||||
|
oshash = None
|
||||||
|
for f_type, f_val in fingerprints:
|
||||||
|
if f_type == 'phash':
|
||||||
|
phash = hex(int(f_val) & 0xffffffffffffffff)[2:].zfill(16)
|
||||||
|
elif f_type == 'oshash':
|
||||||
|
oshash = f_val
|
||||||
|
|
||||||
|
# Get scene_id
|
||||||
|
cursor.execute("SELECT scene_id FROM scenes_files WHERE file_id = ?", (file_id,))
|
||||||
|
scene_row = cursor.fetchone()
|
||||||
|
scene_id = scene_row[0] if scene_row else None
|
||||||
|
|
||||||
|
# Get duration
|
||||||
|
cursor.execute("SELECT duration FROM video_files WHERE file_id = ?", (file_id,))
|
||||||
|
dur_row = cursor.fetchone()
|
||||||
|
duration = dur_row[0] if dur_row else None
|
||||||
|
|
||||||
|
return phash, oshash, scene_id, duration
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to query Stash metadata for {local_path}: {e}")
|
||||||
|
return None, None, None, None
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def get_sprite_path(self, oshash):
|
||||||
|
"""
|
||||||
|
Returns the remote SMB path for the sprite.
|
||||||
|
Example: /media/stashapp/generated/vtt/{oshash}_sprite.jpg
|
||||||
|
"""
|
||||||
|
if not oshash: return None
|
||||||
|
return f"{self.settings.stash_generated_path}/vtt/{oshash}_sprite.jpg"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from .database import SessionLocal
|
||||||
|
from .models import TaskHistory
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
def record_task_start(task_id, name, details=None):
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
task = TaskHistory(task_id=str(task_id), name=name, status="running", details=str(details) if details else None)
|
||||||
|
db.add(task)
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error recording task start: {e}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def record_task_end(task_id, status, details=None):
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
task = db.query(TaskHistory).filter_by(task_id=str(task_id)).first()
|
||||||
|
if task:
|
||||||
|
task.status = status
|
||||||
|
task.end_time = datetime.datetime.utcnow()
|
||||||
|
if details:
|
||||||
|
# Append details if existing? Or overwrite? Overwrite for now or simpler append
|
||||||
|
task.details = str(details)
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error recording task end: {e}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
from huey import SqliteHuey
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from .comics_manager import ComicsManager
|
||||||
|
from .samba_manager import SambaManager
|
||||||
|
from .config import settings
|
||||||
|
from .task_tracker import record_task_start, record_task_end
|
||||||
|
|
||||||
|
# Ensure dir exists
|
||||||
|
os.makedirs("resources/config", exist_ok=True)
|
||||||
|
|
||||||
|
# Configure Huey with SQLite backend for persistence
|
||||||
|
huey = SqliteHuey(filename="resources/config/tasks.db")
|
||||||
|
|
||||||
|
logger = logging.getLogger('huey')
|
||||||
|
|
||||||
|
def get_samba_manager(share="isolation"):
|
||||||
|
return SambaManager(
|
||||||
|
settings.samba_server_ip,
|
||||||
|
share,
|
||||||
|
settings.samba_username,
|
||||||
|
settings.samba_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
@huey.task(context=True)
|
||||||
|
def task_organize_comics(task=None):
|
||||||
|
record_task_start(task.id, "Organize Comics")
|
||||||
|
logger.info(f"Task {task.id}: Organize Comics started")
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
manager.organize_comics(samba_manager)
|
||||||
|
record_task_end(task.id, "success")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Task Organize Comics failed: {e}")
|
||||||
|
record_task_end(task.id, "failed", str(e))
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@huey.task(context=True)
|
||||||
|
def task_move_comics(series_names, share, path, task=None):
|
||||||
|
record_task_start(task.id, "Move Comics", f"Count: {len(series_names)} -> {share}:{path}")
|
||||||
|
logger.info(f"Task {task.id}: Move Comics started")
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
manager.move_series(samba_manager, series_names, share, path)
|
||||||
|
record_task_end(task.id, "success")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Task Move Comics failed: {e}")
|
||||||
|
record_task_end(task.id, "failed", str(e))
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@huey.task(context=True)
|
||||||
|
def task_update_metadata(target_path, force, task=None):
|
||||||
|
record_task_start(task.id, "Update Metadata", f"Path: {target_path}")
|
||||||
|
logger.info(f"Task {task.id}: Update Metadata started")
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
manager.update_existing_metadata(samba_manager, target_path, force)
|
||||||
|
record_task_end(task.id, "success")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Task Update Metadata failed: {e}")
|
||||||
|
record_task_end(task.id, "failed", str(e))
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
|
|
||||||
|
@huey.task(context=True)
|
||||||
|
def task_sort_by_artist(root_path, task=None):
|
||||||
|
record_task_start(task.id, "Sort by Artist", f"Path: {root_path}")
|
||||||
|
logger.info(f"Task {task.id}: Sort by Artist started")
|
||||||
|
manager = ComicsManager()
|
||||||
|
samba_manager = get_samba_manager("isolation")
|
||||||
|
try:
|
||||||
|
manager.sort_by_artist(samba_manager, root_path)
|
||||||
|
record_task_end(task.id, "success")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Task Sort by Artist failed: {e}")
|
||||||
|
record_task_end(task.id, "failed", str(e))
|
||||||
|
finally:
|
||||||
|
samba_manager.close()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
{"status": "completed", "processed": 6970}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
from app.database import engine
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
def migrate():
|
||||||
|
with engine.connect() as conn:
|
||||||
|
try:
|
||||||
|
conn.execute(text("ALTER TABLE video_files ADD COLUMN contact_sheet_path VARCHAR"))
|
||||||
|
print("Added contact_sheet_path")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Skipped contact_sheet_path (probably exists): {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.execute(text("ALTER TABLE video_files ADD COLUMN scene_phash VARCHAR"))
|
||||||
|
print("Added scene_phash")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Skipped scene_phash (probably exists): {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate()
|
||||||
@@ -9,4 +9,6 @@ pydantic-settings
|
|||||||
requests
|
requests
|
||||||
pillow
|
pillow
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
cloudscraper
|
cloudscraper
|
||||||
|
huey
|
||||||
|
qbittorrent-api
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,322 +0,0 @@
|
|||||||
import os
|
|
||||||
import zipfile
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
from PIL import Image
|
|
||||||
from pdf2image import convert_from_path
|
|
||||||
from datetime import datetime
|
|
||||||
from collections import defaultdict
|
|
||||||
import uuid # Library for generating unique IDs
|
|
||||||
|
|
||||||
# --- Utility Functions ---
|
|
||||||
|
|
||||||
def setup_error_log():
|
|
||||||
"""
|
|
||||||
Creates a uniquely named error log file.
|
|
||||||
"""
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
||||||
log_filename = f"errorlog_{timestamp}.log"
|
|
||||||
return log_filename
|
|
||||||
|
|
||||||
def log_error(log_file, message):
|
|
||||||
"""
|
|
||||||
Appends an error message to the log file.
|
|
||||||
"""
|
|
||||||
with open(log_file, 'a') as f:
|
|
||||||
f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}\n")
|
|
||||||
|
|
||||||
def convert_webp_to_jpg(file_path, log_file):
|
|
||||||
"""
|
|
||||||
Converts a .webp image file to .jpg format.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
image = Image.open(file_path)
|
|
||||||
if image.mode == 'RGBA':
|
|
||||||
image = image.convert('RGB')
|
|
||||||
jpg_path = os.path.splitext(file_path)[0] + '.jpg'
|
|
||||||
image.save(jpg_path, 'JPEG')
|
|
||||||
os.remove(file_path)
|
|
||||||
return jpg_path
|
|
||||||
except Exception as e:
|
|
||||||
log_error(log_file, f"Error converting WEBP: {file_path} - {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def convert_pdf_to_jpgs(file_path, output_folder, log_file):
|
|
||||||
"""
|
|
||||||
Converts a PDF file into a series of JPG images.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
pages = convert_from_path(file_path)
|
|
||||||
output_paths = []
|
|
||||||
for i, page in enumerate(pages):
|
|
||||||
jpg_path = os.path.join(output_folder, f"page_{i+1:03d}.jpg")
|
|
||||||
page.save(jpg_path, 'JPEG')
|
|
||||||
output_paths.append(jpg_path)
|
|
||||||
os.remove(file_path)
|
|
||||||
return output_paths
|
|
||||||
except Exception as e:
|
|
||||||
log_error(log_file, f"Error converting PDF: {file_path} - {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def create_cbz_archive(folder_path, output_path, log_file):
|
|
||||||
"""
|
|
||||||
Creates a .cbz archive from all image files in a folder.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with zipfile.ZipFile(output_path, 'w') as cbz_file:
|
|
||||||
for root, _, files in os.walk(folder_path):
|
|
||||||
# Sort files to ensure correct page order in the CBZ file
|
|
||||||
sorted_files = sorted(files, key=lambda f: re.split(r'(\d+)', f))
|
|
||||||
for filename in sorted_files:
|
|
||||||
if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
|
|
||||||
file_path = os.path.join(root, filename)
|
|
||||||
# Archive name should be relative to the content folder
|
|
||||||
arcname = os.path.relpath(file_path, folder_path)
|
|
||||||
cbz_file.write(file_path, arcname)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
log_error(log_file, f"Error creating CBZ for {folder_path} - {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clean_up_temp_folders(folder_path):
|
|
||||||
"""
|
|
||||||
Removes a folder and its contents.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if os.path.exists(folder_path):
|
|
||||||
shutil.rmtree(folder_path)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error cleaning up {folder_path}: {e}")
|
|
||||||
|
|
||||||
# --- Grouping Function ---
|
|
||||||
|
|
||||||
def get_series_name(file_or_folder_name):
|
|
||||||
"""
|
|
||||||
Uses regex to extract the common series name from a chapter folder/file name.
|
|
||||||
"""
|
|
||||||
base_name = os.path.splitext(file_or_folder_name)[0]
|
|
||||||
|
|
||||||
# Pattern to match and strip common chapter/volume suffixes at the end of a string.
|
|
||||||
pattern = re.compile(r'[-_]?((ch(apter)?|v(ol)?)[-_]?)?\d{1,4}(\.\d{1,2})?$', re.IGNORECASE)
|
|
||||||
|
|
||||||
# Find the match from the end
|
|
||||||
match = pattern.search(base_name)
|
|
||||||
if match:
|
|
||||||
# Return the string slice before the matched pattern
|
|
||||||
return base_name[:match.start()].strip('-_ ')
|
|
||||||
|
|
||||||
# Fallback: if no clear chapter pattern is found, return the original name
|
|
||||||
return base_name
|
|
||||||
|
|
||||||
# --- File/Folder Processing Function ---
|
|
||||||
|
|
||||||
def process_chapter(chapter_path, final_series_folder, temp_root_folder, log_file):
|
|
||||||
"""
|
|
||||||
Copies, converts, creates CBZ, and cleans up for a single chapter folder/archive.
|
|
||||||
Uses a unique temporary sub-folder for its operations.
|
|
||||||
"""
|
|
||||||
chapter_name = os.path.basename(chapter_path)
|
|
||||||
|
|
||||||
# Create a UNIQUE temporary folder for THIS chapter's processing
|
|
||||||
unique_id = str(uuid.uuid4())[:8] # Short unique identifier
|
|
||||||
temp_chapter_folder = os.path.join(temp_root_folder, f"proc_{unique_id}_{chapter_name}")
|
|
||||||
os.makedirs(temp_chapter_folder, exist_ok=True)
|
|
||||||
|
|
||||||
is_archive = os.path.isfile(chapter_path) and chapter_path.lower().endswith(('.zip', '.cbz'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
if is_archive:
|
|
||||||
print(f" -> Extracting Archive: {chapter_name}")
|
|
||||||
with zipfile.ZipFile(chapter_path, 'r') as zip_ref:
|
|
||||||
zip_ref.extractall(temp_chapter_folder)
|
|
||||||
else:
|
|
||||||
print(f" -> Processing Folder: {chapter_name}")
|
|
||||||
# Copy original folder contents to the temporary folder
|
|
||||||
for item in os.listdir(chapter_path):
|
|
||||||
s = os.path.join(chapter_path, item)
|
|
||||||
d = os.path.join(temp_chapter_folder, item)
|
|
||||||
if os.path.isdir(s):
|
|
||||||
# Use a recursive copy for subdirectories
|
|
||||||
shutil.copytree(s, d)
|
|
||||||
else:
|
|
||||||
# Use a simple copy for files
|
|
||||||
shutil.copy2(s, d)
|
|
||||||
|
|
||||||
# B. Convert files inside the temporary folder
|
|
||||||
for root, _, files in os.walk(temp_chapter_folder):
|
|
||||||
for file_path in [os.path.join(root, f) for f in files]:
|
|
||||||
if file_path.lower().endswith('.webp'):
|
|
||||||
print(f" - Converting WEBP: {os.path.basename(file_path)}")
|
|
||||||
convert_webp_to_jpg(file_path, log_file)
|
|
||||||
elif file_path.lower().endswith('.pdf'):
|
|
||||||
print(f" - Converting PDF: {os.path.basename(file_path)}")
|
|
||||||
# Conversion places JPGs in the same folder as the PDF
|
|
||||||
convert_pdf_to_jpgs(file_path, root, log_file)
|
|
||||||
|
|
||||||
# C. Create CBZ archive in the final series destination
|
|
||||||
cbz_name = os.path.splitext(chapter_name)[0] + '.cbz'
|
|
||||||
output_cbz_path = os.path.join(final_series_folder, cbz_name)
|
|
||||||
|
|
||||||
if create_cbz_archive(temp_chapter_folder, output_cbz_path, log_file):
|
|
||||||
print(f" - Successfully created {cbz_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log_error(log_file, f"Processing failed for {chapter_name} (Path: {chapter_path}): {e}")
|
|
||||||
print(f" - ERROR: Processing failed. Check log for details.")
|
|
||||||
return # Exit the function on error
|
|
||||||
finally:
|
|
||||||
# D. Clean up the temporary processing folder for this chapter
|
|
||||||
clean_up_temp_folders(temp_chapter_folder)
|
|
||||||
|
|
||||||
# --- New Move Function ---
|
|
||||||
|
|
||||||
def move_converted_files(source_dir, destination_root, log_file):
|
|
||||||
"""
|
|
||||||
Moves all contents (series folders) from source_dir (converted)
|
|
||||||
to destination_root (/mnt/isolation/comics).
|
|
||||||
"""
|
|
||||||
print("\n--- Starting Final Move Operation ---")
|
|
||||||
|
|
||||||
# The parent of /mnt/isolation/comics/toberead/converted is /mnt/isolation/comics/toberead
|
|
||||||
# We want to move to /mnt/isolation/comics
|
|
||||||
|
|
||||||
# 1. Identify the true final destination path
|
|
||||||
# Get the parent of the source_dir (which is /mnt/isolation/comics/toberead)
|
|
||||||
parent_of_source = os.path.dirname(source_dir)
|
|
||||||
# Get the parent of that (which is /mnt/isolation/comics)
|
|
||||||
final_destination = os.path.dirname(parent_of_source)
|
|
||||||
|
|
||||||
if destination_root != final_destination:
|
|
||||||
log_error(log_file, f"Configuration Error: Move destination root mismatch. Expected {final_destination}, got {destination_root}")
|
|
||||||
print(f"Error: Final destination paths do not match. Aborting move. Check log.")
|
|
||||||
return
|
|
||||||
|
|
||||||
total_moved = 0
|
|
||||||
|
|
||||||
# Iterate through each series folder in the 'converted' directory
|
|
||||||
for series_name in os.listdir(source_dir):
|
|
||||||
series_source_path = os.path.join(source_dir, series_name)
|
|
||||||
series_dest_path = os.path.join(destination_root, series_name)
|
|
||||||
|
|
||||||
if os.path.isdir(series_source_path):
|
|
||||||
try:
|
|
||||||
# Move the entire series folder (containing the CBZ files)
|
|
||||||
if not os.path.exists(series_dest_path):
|
|
||||||
shutil.move(series_source_path, series_dest_path)
|
|
||||||
print(f" -> Moved series folder: {series_name}")
|
|
||||||
total_moved += 1
|
|
||||||
else:
|
|
||||||
# If the series folder already exists in the destination, move its contents (the CBZ files)
|
|
||||||
for item in os.listdir(series_source_path):
|
|
||||||
item_s = os.path.join(series_source_path, item)
|
|
||||||
item_d = os.path.join(series_dest_path, item)
|
|
||||||
|
|
||||||
if item.lower().endswith('.cbz') and not os.path.exists(item_d):
|
|
||||||
shutil.move(item_s, item_d)
|
|
||||||
print(f" -> Moved CBZ: {item}")
|
|
||||||
total_moved += 1
|
|
||||||
|
|
||||||
# Remove the now-empty source folder
|
|
||||||
clean_up_temp_folders(series_source_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log_error(log_file, f"Error moving series {series_name}: {e}")
|
|
||||||
print(f"Error moving {series_name}. Check log for details.")
|
|
||||||
|
|
||||||
print(f"\nMove Complete. Total items moved: {total_moved}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main function to run the comic organization script.
|
|
||||||
"""
|
|
||||||
# --- Configuration ---
|
|
||||||
source_directory = "/mnt/isolation/comics/toberead"
|
|
||||||
target_directory = os.path.join(source_directory, "converted")
|
|
||||||
temp_root_folder = os.path.join(source_directory, ".komga_processing_temp")
|
|
||||||
|
|
||||||
# The final destination root for the move operation (parent of source_directory)
|
|
||||||
destination_root = os.path.dirname(source_directory)
|
|
||||||
# ---------------------
|
|
||||||
|
|
||||||
error_log_file = setup_error_log()
|
|
||||||
print(f"Starting script. Source: {source_directory}")
|
|
||||||
print(f"Output will be in: {target_directory}")
|
|
||||||
print(f"Final destination root: {destination_root}")
|
|
||||||
print(f"Errors will be logged to: {error_log_file}")
|
|
||||||
|
|
||||||
os.makedirs(target_directory, exist_ok=True)
|
|
||||||
|
|
||||||
# Clean up the main temporary directory before starting
|
|
||||||
clean_up_temp_folders(temp_root_folder)
|
|
||||||
os.makedirs(temp_root_folder, exist_ok=True)
|
|
||||||
|
|
||||||
# 1. Group Chapter Folders and Archive Files by Series Name
|
|
||||||
|
|
||||||
series_chapters = defaultdict(list)
|
|
||||||
|
|
||||||
try:
|
|
||||||
items = os.listdir(source_directory)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Error: Source directory not found: {source_directory}. Aborting.")
|
|
||||||
return
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
item_path = os.path.join(source_directory, item)
|
|
||||||
|
|
||||||
# Skip the output and temporary directories
|
|
||||||
if item_path.startswith(target_directory) or item_path.startswith(temp_root_folder):
|
|
||||||
continue
|
|
||||||
|
|
||||||
is_relevant_folder = os.path.isdir(item_path) and any(f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp', '.pdf'))
|
|
||||||
for f in os.listdir(item_path))
|
|
||||||
is_relevant_file = os.path.isfile(item_path) and item.lower().endswith(('.zip', '.cbz'))
|
|
||||||
|
|
||||||
if is_relevant_folder or is_relevant_file:
|
|
||||||
series_name = get_series_name(item)
|
|
||||||
series_chapters[series_name].append(item_path)
|
|
||||||
|
|
||||||
print(f"\nFound {len(series_chapters)} series to process.")
|
|
||||||
|
|
||||||
# 2. Process Each Series Group
|
|
||||||
|
|
||||||
for series_name, chapter_paths in series_chapters.items():
|
|
||||||
print(f"\n--- Processing Series: {series_name} ({len(chapter_paths)} Chapters/Archives) ---")
|
|
||||||
|
|
||||||
# Create the final parent folder for the series in the target directory
|
|
||||||
final_series_folder = os.path.join(target_directory, series_name)
|
|
||||||
os.makedirs(final_series_folder, exist_ok=True)
|
|
||||||
|
|
||||||
for chapter_path in chapter_paths:
|
|
||||||
chapter_name = os.path.basename(chapter_path)
|
|
||||||
|
|
||||||
if chapter_name.lower().endswith('.cbz') and os.path.isfile(chapter_path):
|
|
||||||
# Move existing CBZ files directly to the final destination
|
|
||||||
new_path = os.path.join(final_series_folder, chapter_name)
|
|
||||||
if not os.path.exists(new_path):
|
|
||||||
shutil.move(chapter_path, new_path)
|
|
||||||
print(f" -> Moved existing CBZ: {chapter_name}")
|
|
||||||
else:
|
|
||||||
print(f" -> Skipping CBZ (already exists in destination): {chapter_name}")
|
|
||||||
else:
|
|
||||||
# Process folders and ZIP files
|
|
||||||
process_chapter(chapter_path, final_series_folder, temp_root_folder, error_log_file)
|
|
||||||
|
|
||||||
# 3. Final Cleanup and Conditional Move
|
|
||||||
|
|
||||||
# Final cleanup of the main temp folder
|
|
||||||
clean_up_temp_folders(temp_root_folder)
|
|
||||||
|
|
||||||
print("\n--- Processing Finished ---")
|
|
||||||
|
|
||||||
# --- USER PROMPT FOR FINAL MOVE ---
|
|
||||||
prompt = f"Do you want to move all CBZ files from '{target_directory}' to the root directory '{destination_root}'? (yes/no): "
|
|
||||||
user_input = input(prompt).strip().lower()
|
|
||||||
|
|
||||||
if user_input == 'yes':
|
|
||||||
move_converted_files(target_directory, destination_root, error_log_file)
|
|
||||||
|
|
||||||
print("\nScript operation complete.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
|
||||||
import logging
|
|
||||||
from .samba_manager import SambaManager
|
|
||||||
from .config import Settings
|
|
||||||
from smb.smb_structs import OperationFailure
|
|
||||||
|
|
||||||
# --- Dedicated logger for ComicsManager ---
|
|
||||||
comics_logger = logging.getLogger('comics_manager')
|
|
||||||
comics_logger.setLevel(logging.INFO)
|
|
||||||
comics_logger.propagate = False
|
|
||||||
if not comics_logger.handlers:
|
|
||||||
comics_log_handler = logging.FileHandler("logs/comics_organization.log", mode='a')
|
|
||||||
comics_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
|
||||||
comics_logger.addHandler(comics_log_handler)
|
|
||||||
comics_logger.addHandler(logging.StreamHandler())
|
|
||||||
|
|
||||||
# --- Disable noisy pysmb logger ---
|
|
||||||
# Temporarily set the pysmb logger levels to CRITICAL to silence its output.
|
|
||||||
# This needs to be set *after* basicConfig if pysmb initializes its loggers earlier.
|
|
||||||
try:
|
|
||||||
logging.getLogger("smb").setLevel(logging.CRITICAL)
|
|
||||||
logging.getLogger("smb.SMBConnection").setLevel(logging.CRITICAL)
|
|
||||||
except Exception:
|
|
||||||
# Handle cases where the logger might not be fully initialized yet
|
|
||||||
pass
|
|
||||||
# ---
|
|
||||||
|
|
||||||
class ComicsManager:
|
|
||||||
def __init__(self, settings: Settings):
|
|
||||||
self.settings = settings
|
|
||||||
self.db = None # ComicsManager doesn't use DB
|
|
||||||
self.samba_manager = SambaManager(
|
|
||||||
settings.samba_server_ip,
|
|
||||||
"isolation",
|
|
||||||
settings.samba_username,
|
|
||||||
settings.samba_password,
|
|
||||||
)
|
|
||||||
self.comics_root = "/comics/toberead"
|
|
||||||
self.comics_dest_root = "/comics/manga"
|
|
||||||
|
|
||||||
def _get_series_name(self, filename):
|
|
||||||
base_name, _ = os.path.splitext(filename)
|
|
||||||
# This regex is more aggressive at cleaning up chapter/volume numbers and other noise.
|
|
||||||
# It looks for common patterns like 'ch.10', 'v02', '123', etc.
|
|
||||||
pattern = re.compile(r'[-_\s]*(v(ol)?|c(h)?|chapter|issue|ep|episode)[-_\s]*\d+.*|[-_\s]+\d+$', re.IGNORECASE)
|
|
||||||
match = pattern.search(base_name)
|
|
||||||
if match:
|
|
||||||
return base_name[:match.start()].strip('-_ ')
|
|
||||||
return base_name.strip('-_ ')
|
|
||||||
|
|
||||||
def organize_comics(self):
|
|
||||||
comics_logger.info("Starting comics organization process...")
|
|
||||||
try:
|
|
||||||
items_in_toberead = self.samba_manager.list_path(self.comics_root)
|
|
||||||
if "error" in items_in_toberead:
|
|
||||||
raise Exception(f"Failed to list files in 'toberead': {items_in_toberead['error']}")
|
|
||||||
|
|
||||||
series_chapters = defaultdict(list)
|
|
||||||
for item in items_in_toberead:
|
|
||||||
if not item["is_directory"]:
|
|
||||||
series_name = self._get_series_name(item["name"])
|
|
||||||
series_chapters[series_name].append(item)
|
|
||||||
|
|
||||||
comics_logger.info(f"Found {len(series_chapters)} series to process.")
|
|
||||||
|
|
||||||
for series_name, chapters in series_chapters.items():
|
|
||||||
comics_logger.info(f"Processing series: {series_name}")
|
|
||||||
# Ensure paths use forward slashes for Samba operations
|
|
||||||
final_series_folder_path = f"{self.comics_dest_root}/{series_name}".replace("\", "/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.samba_manager.create_directory(final_series_folder_path)
|
|
||||||
comics_logger.info(f"Created directory: {final_series_folder_path}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
# Specific check for FILE_OBJECT_NAME_COLLISION if directory already exists
|
|
||||||
if "FILE_OBJECT_NAME_COLLISION" in str(e):
|
|
||||||
comics_logger.info(f"Directory {final_series_folder_path} already exists. Skipping creation.")
|
|
||||||
else:
|
|
||||||
comics_logger.error(f"Could not create directory {final_series_folder_path}: {e}")
|
|
||||||
|
|
||||||
for chapter_item in chapters:
|
|
||||||
# Sanitize original path coming from list_path
|
|
||||||
original_path = chapter_item["path"].replace("\", "/")
|
|
||||||
original_filename = chapter_item["name"]
|
|
||||||
|
|
||||||
path_to_move = ""
|
|
||||||
filename_to_move = ""
|
|
||||||
|
|
||||||
if original_filename.lower().endswith('.zip'):
|
|
||||||
cbz_filename = os.path.splitext(original_filename)[0] + '.cbz'
|
|
||||||
temp_cbz_path = f"{self.comics_root}/{cbz_filename}".replace("\", "/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.samba_manager.rename_file(original_path, temp_cbz_path)
|
|
||||||
path_to_move = temp_cbz_path
|
|
||||||
filename_to_move = cbz_filename
|
|
||||||
comics_logger.info(f"Renamed {original_filename} to {cbz_filename}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to rename ZIP {original_filename} to CBZ: {e}")
|
|
||||||
continue # Skip to next chapter if rename fails
|
|
||||||
|
|
||||||
elif original_filename.lower().endswith('.cbz'):
|
|
||||||
# If it's already a .cbz, it's ready to be moved
|
|
||||||
path_to_move = original_path
|
|
||||||
filename_to_move = original_filename
|
|
||||||
else:
|
|
||||||
comics_logger.warning(f"Skipping non-zip/cbz file: {original_filename}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Final destination path for the CBZ file
|
|
||||||
final_cbz_path = f"{final_series_folder_path}/{filename_to_move}".replace("\", "/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.samba_manager.rename_file(path_to_move, final_cbz_path)
|
|
||||||
comics_logger.info(f"Moved {filename_to_move} to {final_series_folder_path}")
|
|
||||||
except OperationFailure as e:
|
|
||||||
comics_logger.error(f"Failed to move {filename_to_move} to {final_series_folder_path}: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
comics_logger.error(f"An unexpected error occurred during comics organization: {e}", exc_info=True)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
comics_logger.info("Comics organization process finished.")
|
|
||||||
self.samba_manager.close()
|
|
||||||
|
|
||||||
return
|
|
||||||
+337
-277
@@ -1,367 +1,394 @@
|
|||||||
{
|
{
|
||||||
"hash": "58793c98",
|
"hash": "95aedb35",
|
||||||
"configHash": "939bcc17",
|
"configHash": "7be18a1c",
|
||||||
"lockfileHash": "b10062e0",
|
"lockfileHash": "b10062e0",
|
||||||
"browserHash": "e6aae6df",
|
"browserHash": "9f13f699",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"vue": {
|
"vue": {
|
||||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||||
"file": "vue.js",
|
"file": "vue.js",
|
||||||
"fileHash": "c9954258",
|
"fileHash": "8eb41ee8",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vue-router": {
|
"vue-router": {
|
||||||
"src": "../../vue-router/dist/vue-router.mjs",
|
"src": "../../vue-router/dist/vue-router.mjs",
|
||||||
"file": "vue-router.js",
|
"file": "vue-router.js",
|
||||||
"fileHash": "e764d986",
|
"fileHash": "1427789a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify": {
|
"vuetify": {
|
||||||
"src": "../../vuetify/lib/framework.js",
|
"src": "../../vuetify/lib/framework.js",
|
||||||
"file": "vuetify.js",
|
"file": "vuetify.js",
|
||||||
"fileHash": "369c86f0",
|
"fileHash": "8dbe3ca9",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components": {
|
"vuetify/components": {
|
||||||
"src": "../../vuetify/lib/components/index.js",
|
"src": "../../vuetify/lib/components/index.js",
|
||||||
"file": "vuetify_components.js",
|
"file": "vuetify_components.js",
|
||||||
"fileHash": "721553c0",
|
"fileHash": "c679badd",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/directives": {
|
"vuetify/directives": {
|
||||||
"src": "../../vuetify/lib/directives/index.js",
|
"src": "../../vuetify/lib/directives/index.js",
|
||||||
"file": "vuetify_directives.js",
|
"file": "vuetify_directives.js",
|
||||||
"fileHash": "cf4c59e7",
|
"fileHash": "1b05ee8f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/iconsets/mdi": {
|
"vuetify/iconsets/mdi": {
|
||||||
"src": "../../vuetify/lib/iconsets/mdi.js",
|
"src": "../../vuetify/lib/iconsets/mdi.js",
|
||||||
"file": "vuetify_iconsets_mdi.js",
|
"file": "vuetify_iconsets_mdi.js",
|
||||||
"fileHash": "590e0963",
|
"fileHash": "af4fe77a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VApp": {
|
"vuetify/components/VApp": {
|
||||||
"src": "../../vuetify/lib/components/VApp/index.js",
|
"src": "../../vuetify/lib/components/VApp/index.js",
|
||||||
"file": "vuetify_components_VApp.js",
|
"file": "vuetify_components_VApp.js",
|
||||||
"fileHash": "f6cb1574",
|
"fileHash": "67472430",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VAppBar": {
|
"vuetify/components/VAppBar": {
|
||||||
"src": "../../vuetify/lib/components/VAppBar/index.js",
|
"src": "../../vuetify/lib/components/VAppBar/index.js",
|
||||||
"file": "vuetify_components_VAppBar.js",
|
"file": "vuetify_components_VAppBar.js",
|
||||||
"fileHash": "bd33378b",
|
"fileHash": "1c27af94",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VBtn": {
|
"vuetify/components/VBtn": {
|
||||||
"src": "../../vuetify/lib/components/VBtn/index.js",
|
"src": "../../vuetify/lib/components/VBtn/index.js",
|
||||||
"file": "vuetify_components_VBtn.js",
|
"file": "vuetify_components_VBtn.js",
|
||||||
"fileHash": "b2541520",
|
"fileHash": "9ff6101b",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VDivider": {
|
"vuetify/components/VDivider": {
|
||||||
"src": "../../vuetify/lib/components/VDivider/index.js",
|
"src": "../../vuetify/lib/components/VDivider/index.js",
|
||||||
"file": "vuetify_components_VDivider.js",
|
"file": "vuetify_components_VDivider.js",
|
||||||
"fileHash": "1201295e",
|
"fileHash": "d9ee236f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VGrid": {
|
"vuetify/components/VGrid": {
|
||||||
"src": "../../vuetify/lib/components/VGrid/index.js",
|
"src": "../../vuetify/lib/components/VGrid/index.js",
|
||||||
"file": "vuetify_components_VGrid.js",
|
"file": "vuetify_components_VGrid.js",
|
||||||
"fileHash": "dec97038",
|
"fileHash": "74768afe",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VList": {
|
"vuetify/components/VList": {
|
||||||
"src": "../../vuetify/lib/components/VList/index.js",
|
"src": "../../vuetify/lib/components/VList/index.js",
|
||||||
"file": "vuetify_components_VList.js",
|
"file": "vuetify_components_VList.js",
|
||||||
"fileHash": "92076a71",
|
"fileHash": "dc9b9e42",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VMain": {
|
"vuetify/components/VMain": {
|
||||||
"src": "../../vuetify/lib/components/VMain/index.js",
|
"src": "../../vuetify/lib/components/VMain/index.js",
|
||||||
"file": "vuetify_components_VMain.js",
|
"file": "vuetify_components_VMain.js",
|
||||||
"fileHash": "aea7930b",
|
"fileHash": "41fdc34f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VNavigationDrawer": {
|
"vuetify/components/VNavigationDrawer": {
|
||||||
"src": "../../vuetify/lib/components/VNavigationDrawer/index.js",
|
"src": "../../vuetify/lib/components/VNavigationDrawer/index.js",
|
||||||
"file": "vuetify_components_VNavigationDrawer.js",
|
"file": "vuetify_components_VNavigationDrawer.js",
|
||||||
"fileHash": "fa7c5e03",
|
"fileHash": "e3294e44",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VAlert": {
|
|
||||||
"src": "../../vuetify/lib/components/VAlert/index.js",
|
|
||||||
"file": "vuetify_components_VAlert.js",
|
|
||||||
"fileHash": "5dc7580a",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VCard": {
|
|
||||||
"src": "../../vuetify/lib/components/VCard/index.js",
|
|
||||||
"file": "vuetify_components_VCard.js",
|
|
||||||
"fileHash": "90108afc",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VDialog": {
|
|
||||||
"src": "../../vuetify/lib/components/VDialog/index.js",
|
|
||||||
"file": "vuetify_components_VDialog.js",
|
|
||||||
"fileHash": "e56ad233",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VIcon": {
|
|
||||||
"src": "../../vuetify/lib/components/VIcon/index.js",
|
|
||||||
"file": "vuetify_components_VIcon.js",
|
|
||||||
"fileHash": "ccd96f39",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VToolbar": {
|
|
||||||
"src": "../../vuetify/lib/components/VToolbar/index.js",
|
|
||||||
"file": "vuetify_components_VToolbar.js",
|
|
||||||
"fileHash": "f4e18631",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VBreadcrumbs": {
|
"vuetify/components/VBreadcrumbs": {
|
||||||
"src": "../../vuetify/lib/components/VBreadcrumbs/index.js",
|
"src": "../../vuetify/lib/components/VBreadcrumbs/index.js",
|
||||||
"file": "vuetify_components_VBreadcrumbs.js",
|
"file": "vuetify_components_VBreadcrumbs.js",
|
||||||
"fileHash": "b9da5690",
|
"fileHash": "cce8769a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VDataTable": {
|
"vuetify/components/VCard": {
|
||||||
"src": "../../vuetify/lib/components/VDataTable/index.js",
|
"src": "../../vuetify/lib/components/VCard/index.js",
|
||||||
"file": "vuetify_components_VDataTable.js",
|
"file": "vuetify_components_VCard.js",
|
||||||
"fileHash": "d06d0545",
|
"fileHash": "ea6e451a",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VFileInput": {
|
|
||||||
"src": "../../vuetify/lib/components/VFileInput/index.js",
|
|
||||||
"file": "vuetify_components_VFileInput.js",
|
|
||||||
"fileHash": "9b6d1640",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VMenu": {
|
|
||||||
"src": "../../vuetify/lib/components/VMenu/index.js",
|
|
||||||
"file": "vuetify_components_VMenu.js",
|
|
||||||
"fileHash": "6401d9c7",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VProgressLinear": {
|
|
||||||
"src": "../../vuetify/lib/components/VProgressLinear/index.js",
|
|
||||||
"file": "vuetify_components_VProgressLinear.js",
|
|
||||||
"fileHash": "f6998592",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VSelect": {
|
|
||||||
"src": "../../vuetify/lib/components/VSelect/index.js",
|
|
||||||
"file": "vuetify_components_VSelect.js",
|
|
||||||
"fileHash": "fff4e409",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VSnackbar": {
|
|
||||||
"src": "../../vuetify/lib/components/VSnackbar/index.js",
|
|
||||||
"file": "vuetify_components_VSnackbar.js",
|
|
||||||
"fileHash": "e8e1d7d3",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VCheckbox": {
|
|
||||||
"src": "../../vuetify/lib/components/VCheckbox/index.js",
|
|
||||||
"file": "vuetify_components_VCheckbox.js",
|
|
||||||
"fileHash": "6c2a3d1f",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VChip": {
|
"vuetify/components/VChip": {
|
||||||
"src": "../../vuetify/lib/components/VChip/index.js",
|
"src": "../../vuetify/lib/components/VChip/index.js",
|
||||||
"file": "vuetify_components_VChip.js",
|
"file": "vuetify_components_VChip.js",
|
||||||
"fileHash": "149d1552",
|
"fileHash": "3ec2d4cf",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VDataTable": {
|
||||||
|
"src": "../../vuetify/lib/components/VDataTable/index.js",
|
||||||
|
"file": "vuetify_components_VDataTable.js",
|
||||||
|
"fileHash": "1d4ec1bb",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VDialog": {
|
||||||
|
"src": "../../vuetify/lib/components/VDialog/index.js",
|
||||||
|
"file": "vuetify_components_VDialog.js",
|
||||||
|
"fileHash": "3fa692d0",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VFileInput": {
|
||||||
|
"src": "../../vuetify/lib/components/VFileInput/index.js",
|
||||||
|
"file": "vuetify_components_VFileInput.js",
|
||||||
|
"fileHash": "85476177",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VIcon": {
|
||||||
|
"src": "../../vuetify/lib/components/VIcon/index.js",
|
||||||
|
"file": "vuetify_components_VIcon.js",
|
||||||
|
"fileHash": "aa188e74",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VMenu": {
|
||||||
|
"src": "../../vuetify/lib/components/VMenu/index.js",
|
||||||
|
"file": "vuetify_components_VMenu.js",
|
||||||
|
"fileHash": "ca24447e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VProgressLinear": {
|
||||||
|
"src": "../../vuetify/lib/components/VProgressLinear/index.js",
|
||||||
|
"file": "vuetify_components_VProgressLinear.js",
|
||||||
|
"fileHash": "da3b21f8",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VSelect": {
|
||||||
|
"src": "../../vuetify/lib/components/VSelect/index.js",
|
||||||
|
"file": "vuetify_components_VSelect.js",
|
||||||
|
"fileHash": "23a7041e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VSnackbar": {
|
||||||
|
"src": "../../vuetify/lib/components/VSnackbar/index.js",
|
||||||
|
"file": "vuetify_components_VSnackbar.js",
|
||||||
|
"fileHash": "11f33cbb",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VToolbar": {
|
||||||
|
"src": "../../vuetify/lib/components/VToolbar/index.js",
|
||||||
|
"file": "vuetify_components_VToolbar.js",
|
||||||
|
"fileHash": "fb071d70",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VAlert": {
|
||||||
|
"src": "../../vuetify/lib/components/VAlert/index.js",
|
||||||
|
"file": "vuetify_components_VAlert.js",
|
||||||
|
"fileHash": "1e0ea25e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VCheckbox": {
|
||||||
|
"src": "../../vuetify/lib/components/VCheckbox/index.js",
|
||||||
|
"file": "vuetify_components_VCheckbox.js",
|
||||||
|
"fileHash": "32261bb2",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VExpansionPanel": {
|
"vuetify/components/VExpansionPanel": {
|
||||||
"src": "../../vuetify/lib/components/VExpansionPanel/index.js",
|
"src": "../../vuetify/lib/components/VExpansionPanel/index.js",
|
||||||
"file": "vuetify_components_VExpansionPanel.js",
|
"file": "vuetify_components_VExpansionPanel.js",
|
||||||
"fileHash": "e8175e4b",
|
"fileHash": "13bd06bb",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VSlider": {
|
"vuetify/components/VSlider": {
|
||||||
"src": "../../vuetify/lib/components/VSlider/index.js",
|
"src": "../../vuetify/lib/components/VSlider/index.js",
|
||||||
"file": "vuetify_components_VSlider.js",
|
"file": "vuetify_components_VSlider.js",
|
||||||
"fileHash": "70d54e33",
|
"fileHash": "e6b50146",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/transitions": {
|
"vuetify/components/transitions": {
|
||||||
"src": "../../vuetify/lib/components/transitions/index.js",
|
"src": "../../vuetify/lib/components/transitions/index.js",
|
||||||
"file": "vuetify_components_transitions.js",
|
"file": "vuetify_components_transitions.js",
|
||||||
"fileHash": "1ae42e15",
|
"fileHash": "1f8b3903",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VImg": {
|
|
||||||
"src": "../../vuetify/lib/components/VImg/index.js",
|
|
||||||
"file": "vuetify_components_VImg.js",
|
|
||||||
"fileHash": "55902c55",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VProgressCircular": {
|
|
||||||
"src": "../../vuetify/lib/components/VProgressCircular/index.js",
|
|
||||||
"file": "vuetify_components_VProgressCircular.js",
|
|
||||||
"fileHash": "4ac970e2",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VSheet": {
|
|
||||||
"src": "../../vuetify/lib/components/VSheet/index.js",
|
|
||||||
"file": "vuetify_components_VSheet.js",
|
|
||||||
"fileHash": "60500776",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VForm": {
|
"vuetify/components/VForm": {
|
||||||
"src": "../../vuetify/lib/components/VForm/index.js",
|
"src": "../../vuetify/lib/components/VForm/index.js",
|
||||||
"file": "vuetify_components_VForm.js",
|
"file": "vuetify_components_VForm.js",
|
||||||
"fileHash": "6098d2ad",
|
"fileHash": "ae2d51af",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VTextField": {
|
"vuetify/components/VSheet": {
|
||||||
"src": "../../vuetify/lib/components/VTextField/index.js",
|
"src": "../../vuetify/lib/components/VSheet/index.js",
|
||||||
"file": "vuetify_components_VTextField.js",
|
"file": "vuetify_components_VSheet.js",
|
||||||
"fileHash": "6c3b6c9f",
|
"fileHash": "61437271",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vuetify/components/VBtnToggle": {
|
|
||||||
"src": "../../vuetify/lib/components/VBtnToggle/index.js",
|
|
||||||
"file": "vuetify_components_VBtnToggle.js",
|
|
||||||
"fileHash": "92b719ca",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vuetify/components/VTextarea": {
|
"vuetify/components/VTextarea": {
|
||||||
"src": "../../vuetify/lib/components/VTextarea/index.js",
|
"src": "../../vuetify/lib/components/VTextarea/index.js",
|
||||||
"file": "vuetify_components_VTextarea.js",
|
"file": "vuetify_components_VTextarea.js",
|
||||||
"fileHash": "c4710e99",
|
"fileHash": "59ca68d2",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VBtnToggle": {
|
||||||
|
"src": "../../vuetify/lib/components/VBtnToggle/index.js",
|
||||||
|
"file": "vuetify_components_VBtnToggle.js",
|
||||||
|
"fileHash": "15a21fa2",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VProgressCircular": {
|
||||||
|
"src": "../../vuetify/lib/components/VProgressCircular/index.js",
|
||||||
|
"file": "vuetify_components_VProgressCircular.js",
|
||||||
|
"fileHash": "f97f0ffe",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VTextField": {
|
||||||
|
"src": "../../vuetify/lib/components/VTextField/index.js",
|
||||||
|
"file": "vuetify_components_VTextField.js",
|
||||||
|
"fileHash": "ef211ea3",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VTooltip": {
|
||||||
|
"src": "../../vuetify/lib/components/VTooltip/index.js",
|
||||||
|
"file": "vuetify_components_VTooltip.js",
|
||||||
|
"fileHash": "37f415ca",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VBottomNavigation": {
|
||||||
|
"src": "../../vuetify/lib/components/VBottomNavigation/index.js",
|
||||||
|
"file": "vuetify_components_VBottomNavigation.js",
|
||||||
|
"fileHash": "122f68ff",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VOverlay": {
|
||||||
|
"src": "../../vuetify/lib/components/VOverlay/index.js",
|
||||||
|
"file": "vuetify_components_VOverlay.js",
|
||||||
|
"fileHash": "6830175b",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VCombobox": {
|
||||||
|
"src": "../../vuetify/lib/components/VCombobox/index.js",
|
||||||
|
"file": "vuetify_components_VCombobox.js",
|
||||||
|
"fileHash": "144d5e6c",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VAvatar": {
|
||||||
|
"src": "../../vuetify/lib/components/VAvatar/index.js",
|
||||||
|
"file": "vuetify_components_VAvatar.js",
|
||||||
|
"fileHash": "81b98f53",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VImg": {
|
||||||
|
"src": "../../vuetify/lib/components/VImg/index.js",
|
||||||
|
"file": "vuetify_components_VImg.js",
|
||||||
|
"fileHash": "55ad767e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"vuetify/components/VSwitch": {
|
||||||
|
"src": "../../vuetify/lib/components/VSwitch/index.js",
|
||||||
|
"file": "vuetify_components_VSwitch.js",
|
||||||
|
"fileHash": "7937a40d",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chunks": {
|
"chunks": {
|
||||||
"chunk-KPWCGWEX": {
|
"chunk-244ON7Z6": {
|
||||||
"file": "chunk-KPWCGWEX.js"
|
"file": "chunk-244ON7Z6.js"
|
||||||
},
|
},
|
||||||
"chunk-5LZQZ6A5": {
|
"chunk-5LZQZ6A5": {
|
||||||
"file": "chunk-5LZQZ6A5.js"
|
"file": "chunk-5LZQZ6A5.js"
|
||||||
},
|
},
|
||||||
"chunk-F6LZGKE2": {
|
"chunk-T4XKRUDE": {
|
||||||
"file": "chunk-F6LZGKE2.js"
|
"file": "chunk-T4XKRUDE.js"
|
||||||
},
|
},
|
||||||
"chunk-4MMPKXON": {
|
"chunk-4MMPKXON": {
|
||||||
"file": "chunk-4MMPKXON.js"
|
"file": "chunk-4MMPKXON.js"
|
||||||
},
|
},
|
||||||
"chunk-LHMOJDYM": {
|
"chunk-F6LZGKE2": {
|
||||||
"file": "chunk-LHMOJDYM.js"
|
"file": "chunk-F6LZGKE2.js"
|
||||||
},
|
},
|
||||||
"chunk-TSTWIJCO": {
|
"chunk-LFKLAYDH": {
|
||||||
"file": "chunk-TSTWIJCO.js"
|
"file": "chunk-LFKLAYDH.js"
|
||||||
},
|
|
||||||
"chunk-YDVSSFCH": {
|
|
||||||
"file": "chunk-YDVSSFCH.js"
|
|
||||||
},
|
|
||||||
"chunk-RNFCLGZV": {
|
|
||||||
"file": "chunk-RNFCLGZV.js"
|
|
||||||
},
|
|
||||||
"chunk-WNCBJDZZ": {
|
|
||||||
"file": "chunk-WNCBJDZZ.js"
|
|
||||||
},
|
|
||||||
"chunk-34XWDQGB": {
|
|
||||||
"file": "chunk-34XWDQGB.js"
|
|
||||||
},
|
},
|
||||||
"chunk-RH43OVWP": {
|
"chunk-RH43OVWP": {
|
||||||
"file": "chunk-RH43OVWP.js"
|
"file": "chunk-RH43OVWP.js"
|
||||||
},
|
},
|
||||||
"chunk-PEUMXYT2": {
|
"chunk-QMLWKRPM": {
|
||||||
"file": "chunk-PEUMXYT2.js"
|
"file": "chunk-QMLWKRPM.js"
|
||||||
},
|
},
|
||||||
"chunk-4HCXUCLO": {
|
"chunk-SCY7DFFY": {
|
||||||
"file": "chunk-4HCXUCLO.js"
|
"file": "chunk-SCY7DFFY.js"
|
||||||
},
|
},
|
||||||
"chunk-ERI3RTQS": {
|
"chunk-WGWEQK64": {
|
||||||
"file": "chunk-ERI3RTQS.js"
|
"file": "chunk-WGWEQK64.js"
|
||||||
},
|
},
|
||||||
"chunk-VSEC5QGQ": {
|
"chunk-7M5DDQ46": {
|
||||||
"file": "chunk-VSEC5QGQ.js"
|
"file": "chunk-7M5DDQ46.js"
|
||||||
},
|
},
|
||||||
"chunk-BYBKODOB": {
|
"chunk-3NOJ7Z44": {
|
||||||
"file": "chunk-BYBKODOB.js"
|
"file": "chunk-3NOJ7Z44.js"
|
||||||
},
|
},
|
||||||
"chunk-SHBPUR7N": {
|
"chunk-FCTFWI5H": {
|
||||||
"file": "chunk-SHBPUR7N.js"
|
"file": "chunk-FCTFWI5H.js"
|
||||||
},
|
},
|
||||||
"chunk-HGIC64KW": {
|
"chunk-RB6DAYHX": {
|
||||||
"file": "chunk-HGIC64KW.js"
|
"file": "chunk-RB6DAYHX.js"
|
||||||
},
|
},
|
||||||
"chunk-BXEVD6W6": {
|
"chunk-QV3QXWPM": {
|
||||||
"file": "chunk-BXEVD6W6.js"
|
"file": "chunk-QV3QXWPM.js"
|
||||||
},
|
},
|
||||||
"chunk-LPZZJ62X": {
|
"chunk-IZDIYS2S": {
|
||||||
"file": "chunk-LPZZJ62X.js"
|
"file": "chunk-IZDIYS2S.js"
|
||||||
},
|
},
|
||||||
"chunk-UIXKNP5E": {
|
"chunk-6MO6MKTY": {
|
||||||
"file": "chunk-UIXKNP5E.js"
|
"file": "chunk-6MO6MKTY.js"
|
||||||
},
|
|
||||||
"chunk-DRSCVHAQ": {
|
|
||||||
"file": "chunk-DRSCVHAQ.js"
|
|
||||||
},
|
|
||||||
"chunk-TSYQL5U3": {
|
|
||||||
"file": "chunk-TSYQL5U3.js"
|
|
||||||
},
|
|
||||||
"chunk-MVBFAXEB": {
|
|
||||||
"file": "chunk-MVBFAXEB.js"
|
|
||||||
},
|
|
||||||
"chunk-QC7VWQER": {
|
|
||||||
"file": "chunk-QC7VWQER.js"
|
|
||||||
},
|
|
||||||
"chunk-NURQ3473": {
|
|
||||||
"file": "chunk-NURQ3473.js"
|
|
||||||
},
|
|
||||||
"chunk-ET5GCC5W": {
|
|
||||||
"file": "chunk-ET5GCC5W.js"
|
|
||||||
},
|
|
||||||
"chunk-W6DJL42A": {
|
|
||||||
"file": "chunk-W6DJL42A.js"
|
|
||||||
},
|
|
||||||
"chunk-HV7P7R7J": {
|
|
||||||
"file": "chunk-HV7P7R7J.js"
|
|
||||||
},
|
|
||||||
"chunk-MPVSDM4V": {
|
|
||||||
"file": "chunk-MPVSDM4V.js"
|
|
||||||
},
|
|
||||||
"chunk-HOEZWRBG": {
|
|
||||||
"file": "chunk-HOEZWRBG.js"
|
|
||||||
},
|
|
||||||
"chunk-Z3677HMS": {
|
|
||||||
"file": "chunk-Z3677HMS.js"
|
|
||||||
},
|
|
||||||
"chunk-ADXDXPPR": {
|
|
||||||
"file": "chunk-ADXDXPPR.js"
|
|
||||||
},
|
|
||||||
"chunk-7SKEB5LN": {
|
|
||||||
"file": "chunk-7SKEB5LN.js"
|
|
||||||
},
|
|
||||||
"chunk-PCKQGDLK": {
|
|
||||||
"file": "chunk-PCKQGDLK.js"
|
|
||||||
},
|
|
||||||
"chunk-6CRS5HI4": {
|
|
||||||
"file": "chunk-6CRS5HI4.js"
|
|
||||||
},
|
|
||||||
"chunk-X7YBE6PF": {
|
|
||||||
"file": "chunk-X7YBE6PF.js"
|
|
||||||
},
|
|
||||||
"chunk-YJD2PNP6": {
|
|
||||||
"file": "chunk-YJD2PNP6.js"
|
|
||||||
},
|
|
||||||
"chunk-I4SZJCEY": {
|
|
||||||
"file": "chunk-I4SZJCEY.js"
|
|
||||||
},
|
|
||||||
"chunk-OI3EYDCB": {
|
|
||||||
"file": "chunk-OI3EYDCB.js"
|
|
||||||
},
|
},
|
||||||
"chunk-IZQZFEAL": {
|
"chunk-IZQZFEAL": {
|
||||||
"file": "chunk-IZQZFEAL.js"
|
"file": "chunk-IZQZFEAL.js"
|
||||||
},
|
},
|
||||||
"chunk-J7Q2NMV7": {
|
"chunk-6HLLVLOR": {
|
||||||
"file": "chunk-J7Q2NMV7.js"
|
"file": "chunk-6HLLVLOR.js"
|
||||||
},
|
},
|
||||||
"chunk-4VABFUPG": {
|
"chunk-RWSHRKUK": {
|
||||||
"file": "chunk-4VABFUPG.js"
|
"file": "chunk-RWSHRKUK.js"
|
||||||
|
},
|
||||||
|
"chunk-EKMPZMZF": {
|
||||||
|
"file": "chunk-EKMPZMZF.js"
|
||||||
|
},
|
||||||
|
"chunk-OI3EYDCB": {
|
||||||
|
"file": "chunk-OI3EYDCB.js"
|
||||||
|
},
|
||||||
|
"chunk-ITO3JWXJ": {
|
||||||
|
"file": "chunk-ITO3JWXJ.js"
|
||||||
|
},
|
||||||
|
"chunk-PLNIG6WP": {
|
||||||
|
"file": "chunk-PLNIG6WP.js"
|
||||||
|
},
|
||||||
|
"chunk-LPDOFVDK": {
|
||||||
|
"file": "chunk-LPDOFVDK.js"
|
||||||
|
},
|
||||||
|
"chunk-CCNWGDBL": {
|
||||||
|
"file": "chunk-CCNWGDBL.js"
|
||||||
|
},
|
||||||
|
"chunk-P6EQK5CJ": {
|
||||||
|
"file": "chunk-P6EQK5CJ.js"
|
||||||
|
},
|
||||||
|
"chunk-H2QFYTJQ": {
|
||||||
|
"file": "chunk-H2QFYTJQ.js"
|
||||||
|
},
|
||||||
|
"chunk-CMTCIV56": {
|
||||||
|
"file": "chunk-CMTCIV56.js"
|
||||||
|
},
|
||||||
|
"chunk-MQSMF5CI": {
|
||||||
|
"file": "chunk-MQSMF5CI.js"
|
||||||
|
},
|
||||||
|
"chunk-SXI5CS7S": {
|
||||||
|
"file": "chunk-SXI5CS7S.js"
|
||||||
|
},
|
||||||
|
"chunk-X3WY3J75": {
|
||||||
|
"file": "chunk-X3WY3J75.js"
|
||||||
|
},
|
||||||
|
"chunk-Q5BHIW5H": {
|
||||||
|
"file": "chunk-Q5BHIW5H.js"
|
||||||
|
},
|
||||||
|
"chunk-ZB5S5BI5": {
|
||||||
|
"file": "chunk-ZB5S5BI5.js"
|
||||||
|
},
|
||||||
|
"chunk-TTIGZHWR": {
|
||||||
|
"file": "chunk-TTIGZHWR.js"
|
||||||
|
},
|
||||||
|
"chunk-PCKQGDLK": {
|
||||||
|
"file": "chunk-PCKQGDLK.js"
|
||||||
|
},
|
||||||
|
"chunk-TN4XZECZ": {
|
||||||
|
"file": "chunk-TN4XZECZ.js"
|
||||||
|
},
|
||||||
|
"chunk-L7BQJLEW": {
|
||||||
|
"file": "chunk-L7BQJLEW.js"
|
||||||
|
},
|
||||||
|
"chunk-KHWE4FHS": {
|
||||||
|
"file": "chunk-KHWE4FHS.js"
|
||||||
|
},
|
||||||
|
"chunk-IK67YVHF": {
|
||||||
|
"file": "chunk-IK67YVHF.js"
|
||||||
},
|
},
|
||||||
"chunk-GAYXO4FM": {
|
"chunk-GAYXO4FM": {
|
||||||
"file": "chunk-GAYXO4FM.js"
|
"file": "chunk-GAYXO4FM.js"
|
||||||
@@ -369,11 +396,83 @@
|
|||||||
"chunk-B4JFJDWM": {
|
"chunk-B4JFJDWM": {
|
||||||
"file": "chunk-B4JFJDWM.js"
|
"file": "chunk-B4JFJDWM.js"
|
||||||
},
|
},
|
||||||
"chunk-U2DSRSME": {
|
"chunk-WGYWYQOA": {
|
||||||
"file": "chunk-U2DSRSME.js"
|
"file": "chunk-WGYWYQOA.js"
|
||||||
},
|
},
|
||||||
"chunk-FVDWV4NO": {
|
"chunk-NJV3GUE4": {
|
||||||
"file": "chunk-FVDWV4NO.js"
|
"file": "chunk-NJV3GUE4.js"
|
||||||
|
},
|
||||||
|
"chunk-KPWCGWEX": {
|
||||||
|
"file": "chunk-KPWCGWEX.js"
|
||||||
|
},
|
||||||
|
"chunk-2AYDXCXT": {
|
||||||
|
"file": "chunk-2AYDXCXT.js"
|
||||||
|
},
|
||||||
|
"chunk-EXUNAYJA": {
|
||||||
|
"file": "chunk-EXUNAYJA.js"
|
||||||
|
},
|
||||||
|
"chunk-CL6G3ICF": {
|
||||||
|
"file": "chunk-CL6G3ICF.js"
|
||||||
|
},
|
||||||
|
"chunk-YGX2Z63P": {
|
||||||
|
"file": "chunk-YGX2Z63P.js"
|
||||||
|
},
|
||||||
|
"chunk-BVUPSGRF": {
|
||||||
|
"file": "chunk-BVUPSGRF.js"
|
||||||
|
},
|
||||||
|
"chunk-5ONGSJQ2": {
|
||||||
|
"file": "chunk-5ONGSJQ2.js"
|
||||||
|
},
|
||||||
|
"chunk-GUNW7S2L": {
|
||||||
|
"file": "chunk-GUNW7S2L.js"
|
||||||
|
},
|
||||||
|
"chunk-CAGZXMOS": {
|
||||||
|
"file": "chunk-CAGZXMOS.js"
|
||||||
|
},
|
||||||
|
"chunk-N4LJHDYD": {
|
||||||
|
"file": "chunk-N4LJHDYD.js"
|
||||||
|
},
|
||||||
|
"chunk-47GEKY7C": {
|
||||||
|
"file": "chunk-47GEKY7C.js"
|
||||||
|
},
|
||||||
|
"chunk-KQXZAEJP": {
|
||||||
|
"file": "chunk-KQXZAEJP.js"
|
||||||
|
},
|
||||||
|
"chunk-MVBFAXEB": {
|
||||||
|
"file": "chunk-MVBFAXEB.js"
|
||||||
|
},
|
||||||
|
"chunk-UQALH6SL": {
|
||||||
|
"file": "chunk-UQALH6SL.js"
|
||||||
|
},
|
||||||
|
"chunk-5AQLTWWS": {
|
||||||
|
"file": "chunk-5AQLTWWS.js"
|
||||||
|
},
|
||||||
|
"chunk-W6DJL42A": {
|
||||||
|
"file": "chunk-W6DJL42A.js"
|
||||||
|
},
|
||||||
|
"chunk-HOEZWRBG": {
|
||||||
|
"file": "chunk-HOEZWRBG.js"
|
||||||
|
},
|
||||||
|
"chunk-ADXDXPPR": {
|
||||||
|
"file": "chunk-ADXDXPPR.js"
|
||||||
|
},
|
||||||
|
"chunk-X7YBE6PF": {
|
||||||
|
"file": "chunk-X7YBE6PF.js"
|
||||||
|
},
|
||||||
|
"chunk-YJD2PNP6": {
|
||||||
|
"file": "chunk-YJD2PNP6.js"
|
||||||
|
},
|
||||||
|
"chunk-GSWLJLRT": {
|
||||||
|
"file": "chunk-GSWLJLRT.js"
|
||||||
|
},
|
||||||
|
"chunk-MH76ZJYE": {
|
||||||
|
"file": "chunk-MH76ZJYE.js"
|
||||||
|
},
|
||||||
|
"chunk-UXZ55AO7": {
|
||||||
|
"file": "chunk-UXZ55AO7.js"
|
||||||
|
},
|
||||||
|
"chunk-6RFSM2OD": {
|
||||||
|
"file": "chunk-6RFSM2OD.js"
|
||||||
},
|
},
|
||||||
"chunk-AJ7MUB7M": {
|
"chunk-AJ7MUB7M": {
|
||||||
"file": "chunk-AJ7MUB7M.js"
|
"file": "chunk-AJ7MUB7M.js"
|
||||||
@@ -384,69 +483,36 @@
|
|||||||
"chunk-TM3MIDYC": {
|
"chunk-TM3MIDYC": {
|
||||||
"file": "chunk-TM3MIDYC.js"
|
"file": "chunk-TM3MIDYC.js"
|
||||||
},
|
},
|
||||||
"chunk-CMZ4UYUX": {
|
"chunk-4XZHKYGH": {
|
||||||
"file": "chunk-CMZ4UYUX.js"
|
"file": "chunk-4XZHKYGH.js"
|
||||||
},
|
},
|
||||||
"chunk-PIT7IT55": {
|
"chunk-PIT7IT55": {
|
||||||
"file": "chunk-PIT7IT55.js"
|
"file": "chunk-PIT7IT55.js"
|
||||||
},
|
},
|
||||||
"chunk-VH7QIDM4": {
|
|
||||||
"file": "chunk-VH7QIDM4.js"
|
|
||||||
},
|
|
||||||
"chunk-AQK3CYVW": {
|
"chunk-AQK3CYVW": {
|
||||||
"file": "chunk-AQK3CYVW.js"
|
"file": "chunk-AQK3CYVW.js"
|
||||||
},
|
},
|
||||||
|
"chunk-PAT7NP2Z": {
|
||||||
|
"file": "chunk-PAT7NP2Z.js"
|
||||||
|
},
|
||||||
"chunk-2HZIACSB": {
|
"chunk-2HZIACSB": {
|
||||||
"file": "chunk-2HZIACSB.js"
|
"file": "chunk-2HZIACSB.js"
|
||||||
},
|
},
|
||||||
"chunk-WGYWYQOA": {
|
"chunk-OJDZBU5D": {
|
||||||
"file": "chunk-WGYWYQOA.js"
|
"file": "chunk-OJDZBU5D.js"
|
||||||
},
|
|
||||||
"chunk-NJV3GUE4": {
|
|
||||||
"file": "chunk-NJV3GUE4.js"
|
|
||||||
},
|
|
||||||
"chunk-6VKWM6MN": {
|
|
||||||
"file": "chunk-6VKWM6MN.js"
|
|
||||||
},
|
|
||||||
"chunk-TS6Y6OVT": {
|
|
||||||
"file": "chunk-TS6Y6OVT.js"
|
|
||||||
},
|
|
||||||
"chunk-YGX2Z63P": {
|
|
||||||
"file": "chunk-YGX2Z63P.js"
|
|
||||||
},
|
|
||||||
"chunk-5BPWKCNA": {
|
|
||||||
"file": "chunk-5BPWKCNA.js"
|
|
||||||
},
|
|
||||||
"chunk-BVUPSGRF": {
|
|
||||||
"file": "chunk-BVUPSGRF.js"
|
|
||||||
},
|
},
|
||||||
"chunk-573D4TYC": {
|
"chunk-573D4TYC": {
|
||||||
"file": "chunk-573D4TYC.js"
|
"file": "chunk-573D4TYC.js"
|
||||||
},
|
},
|
||||||
"chunk-GUNW7S2L": {
|
"chunk-DS4J67XG": {
|
||||||
"file": "chunk-GUNW7S2L.js"
|
"file": "chunk-DS4J67XG.js"
|
||||||
},
|
},
|
||||||
"chunk-5ONGSJQ2": {
|
"chunk-BHV5Z34M": {
|
||||||
"file": "chunk-5ONGSJQ2.js"
|
"file": "chunk-BHV5Z34M.js"
|
||||||
},
|
|
||||||
"chunk-CAGZXMOS": {
|
|
||||||
"file": "chunk-CAGZXMOS.js"
|
|
||||||
},
|
},
|
||||||
"chunk-6EZKHCRW": {
|
"chunk-6EZKHCRW": {
|
||||||
"file": "chunk-6EZKHCRW.js"
|
"file": "chunk-6EZKHCRW.js"
|
||||||
},
|
},
|
||||||
"chunk-N4LJHDYD": {
|
|
||||||
"file": "chunk-N4LJHDYD.js"
|
|
||||||
},
|
|
||||||
"chunk-OJDZBU5D": {
|
|
||||||
"file": "chunk-OJDZBU5D.js"
|
|
||||||
},
|
|
||||||
"chunk-IYFY4IHW": {
|
|
||||||
"file": "chunk-IYFY4IHW.js"
|
|
||||||
},
|
|
||||||
"chunk-KQXZAEJP": {
|
|
||||||
"file": "chunk-KQXZAEJP.js"
|
|
||||||
},
|
|
||||||
"chunk-XDSAO7OT": {
|
"chunk-XDSAO7OT": {
|
||||||
"file": "chunk-XDSAO7OT.js"
|
"file": "chunk-XDSAO7OT.js"
|
||||||
},
|
},
|
||||||
@@ -456,12 +522,6 @@
|
|||||||
"chunk-KA77II4V": {
|
"chunk-KA77II4V": {
|
||||||
"file": "chunk-KA77II4V.js"
|
"file": "chunk-KA77II4V.js"
|
||||||
},
|
},
|
||||||
"chunk-WXMECWZN": {
|
|
||||||
"file": "chunk-WXMECWZN.js"
|
|
||||||
},
|
|
||||||
"chunk-BHV5Z34M": {
|
|
||||||
"file": "chunk-BHV5Z34M.js"
|
|
||||||
},
|
|
||||||
"chunk-3DSR7NCB": {
|
"chunk-3DSR7NCB": {
|
||||||
"file": "chunk-3DSR7NCB.js"
|
"file": "chunk-3DSR7NCB.js"
|
||||||
},
|
},
|
||||||
|
|||||||
+663
@@ -0,0 +1,663 @@
|
|||||||
|
import {
|
||||||
|
highlightResult,
|
||||||
|
makeFilterProps,
|
||||||
|
useFilter
|
||||||
|
} from "./chunk-RB6DAYHX.js";
|
||||||
|
import {
|
||||||
|
VVirtualScroll,
|
||||||
|
makeSelectProps,
|
||||||
|
useMenuActivator,
|
||||||
|
useScrolling
|
||||||
|
} from "./chunk-IZDIYS2S.js";
|
||||||
|
import {
|
||||||
|
VTextField,
|
||||||
|
makeVTextFieldProps
|
||||||
|
} from "./chunk-6MO6MKTY.js";
|
||||||
|
import {
|
||||||
|
VCheckboxBtn
|
||||||
|
} from "./chunk-6HLLVLOR.js";
|
||||||
|
import {
|
||||||
|
VChip
|
||||||
|
} from "./chunk-PLNIG6WP.js";
|
||||||
|
import {
|
||||||
|
VMenu
|
||||||
|
} from "./chunk-LPDOFVDK.js";
|
||||||
|
import {
|
||||||
|
VList,
|
||||||
|
VListItem,
|
||||||
|
VListSubheader,
|
||||||
|
transformItem,
|
||||||
|
useItems
|
||||||
|
} from "./chunk-MQSMF5CI.js";
|
||||||
|
import {
|
||||||
|
VAvatar
|
||||||
|
} from "./chunk-SXI5CS7S.js";
|
||||||
|
import {
|
||||||
|
VDivider
|
||||||
|
} from "./chunk-X3WY3J75.js";
|
||||||
|
import {
|
||||||
|
useInputIcon
|
||||||
|
} from "./chunk-IK67YVHF.js";
|
||||||
|
import {
|
||||||
|
useForm
|
||||||
|
} from "./chunk-GAYXO4FM.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
VIcon
|
||||||
|
} from "./chunk-4XZHKYGH.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
|
import {
|
||||||
|
useTextColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useLocale
|
||||||
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
|
import {
|
||||||
|
IN_BROWSER,
|
||||||
|
checkPrintable,
|
||||||
|
deepEqual,
|
||||||
|
ensureValidVNode,
|
||||||
|
escapeForRegex,
|
||||||
|
genericComponent,
|
||||||
|
isComposingIgnoreKey,
|
||||||
|
noop,
|
||||||
|
omit,
|
||||||
|
propsFactory,
|
||||||
|
useRender,
|
||||||
|
wrapInArray
|
||||||
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
import {
|
||||||
|
Fragment,
|
||||||
|
computed,
|
||||||
|
createBaseVNode,
|
||||||
|
createTextVNode,
|
||||||
|
createVNode,
|
||||||
|
mergeProps,
|
||||||
|
nextTick,
|
||||||
|
normalizeClass,
|
||||||
|
normalizeStyle,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
toRef,
|
||||||
|
watch
|
||||||
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VCombobox/VCombobox.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCombobox/VCombobox.css";
|
||||||
|
var makeVComboboxProps = propsFactory({
|
||||||
|
alwaysFilter: Boolean,
|
||||||
|
autoSelectFirst: {
|
||||||
|
type: [Boolean, String]
|
||||||
|
},
|
||||||
|
clearOnSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
delimiters: Array,
|
||||||
|
...makeFilterProps({
|
||||||
|
filterKeys: ["title"]
|
||||||
|
}),
|
||||||
|
...makeSelectProps({
|
||||||
|
hideNoData: true,
|
||||||
|
returnObject: true
|
||||||
|
}),
|
||||||
|
...omit(makeVTextFieldProps({
|
||||||
|
modelValue: null,
|
||||||
|
role: "combobox"
|
||||||
|
}), ["validationValue", "dirty"])
|
||||||
|
}, "VCombobox");
|
||||||
|
var VCombobox = genericComponent()({
|
||||||
|
name: "VCombobox",
|
||||||
|
props: makeVComboboxProps(),
|
||||||
|
emits: {
|
||||||
|
"update:focused": (focused) => true,
|
||||||
|
"update:modelValue": (value) => true,
|
||||||
|
"update:search": (value) => true,
|
||||||
|
"update:menu": (value) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
emit,
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const {
|
||||||
|
t
|
||||||
|
} = useLocale();
|
||||||
|
const vTextFieldRef = ref();
|
||||||
|
const isFocused = shallowRef(false);
|
||||||
|
const isPristine = shallowRef(true);
|
||||||
|
const listHasFocus = shallowRef(false);
|
||||||
|
const vMenuRef = ref();
|
||||||
|
const vVirtualScrollRef = ref();
|
||||||
|
const selectionIndex = shallowRef(-1);
|
||||||
|
let cleared = false;
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
transformIn,
|
||||||
|
transformOut
|
||||||
|
} = useItems(props);
|
||||||
|
const {
|
||||||
|
textColorClasses,
|
||||||
|
textColorStyles
|
||||||
|
} = useTextColor(() => vTextFieldRef.value?.color);
|
||||||
|
const {
|
||||||
|
InputIcon
|
||||||
|
} = useInputIcon(props);
|
||||||
|
const model = useProxiedModel(props, "modelValue", [], (v) => transformIn(wrapInArray(v)), (v) => {
|
||||||
|
const transformed = transformOut(v);
|
||||||
|
return props.multiple ? transformed : transformed[0] ?? null;
|
||||||
|
});
|
||||||
|
const form = useForm(props);
|
||||||
|
const closableChips = toRef(() => props.closableChips && !form.isReadonly.value && !form.isDisabled.value);
|
||||||
|
const hasChips = computed(() => !!(props.chips || slots.chip));
|
||||||
|
const hasSelectionSlot = computed(() => hasChips.value || !!slots.selection);
|
||||||
|
const _search = shallowRef(!props.multiple && !hasSelectionSlot.value ? model.value[0]?.title ?? "" : "");
|
||||||
|
const _searchLock = shallowRef(null);
|
||||||
|
const search = computed({
|
||||||
|
get: () => {
|
||||||
|
return _search.value;
|
||||||
|
},
|
||||||
|
set: async (val) => {
|
||||||
|
_search.value = val ?? "";
|
||||||
|
if (val === null || val === "" && !props.multiple && !hasSelectionSlot.value) {
|
||||||
|
model.value = [];
|
||||||
|
} else if (!props.multiple && !hasSelectionSlot.value) {
|
||||||
|
model.value = [transformItem(props, val)];
|
||||||
|
nextTick(() => vVirtualScrollRef.value?.scrollToIndex(0));
|
||||||
|
}
|
||||||
|
if (val && props.multiple && props.delimiters?.length) {
|
||||||
|
const values = splitByDelimiters(val);
|
||||||
|
if (values.length > 1) {
|
||||||
|
selectMultiple(values);
|
||||||
|
_search.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!val) selectionIndex.value = -1;
|
||||||
|
isPristine.value = !val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const counterValue = computed(() => {
|
||||||
|
return typeof props.counterValue === "function" ? props.counterValue(model.value) : typeof props.counterValue === "number" ? props.counterValue : props.multiple ? model.value.length : search.value.length;
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
filteredItems,
|
||||||
|
getMatches
|
||||||
|
} = useFilter(props, items, () => _searchLock.value ?? (props.alwaysFilter || !isPristine.value ? search.value : ""));
|
||||||
|
const displayItems = computed(() => {
|
||||||
|
if (props.hideSelected && _searchLock.value === null) {
|
||||||
|
return filteredItems.value.filter((filteredItem) => !model.value.some((s) => s.value === filteredItem.value));
|
||||||
|
}
|
||||||
|
return filteredItems.value;
|
||||||
|
});
|
||||||
|
const menuDisabled = computed(() => props.hideNoData && !displayItems.value.length || form.isReadonly.value || form.isDisabled.value);
|
||||||
|
const _menu = useProxiedModel(props, "menu");
|
||||||
|
const menu = computed({
|
||||||
|
get: () => _menu.value,
|
||||||
|
set: (v) => {
|
||||||
|
if (_menu.value && !v && vMenuRef.value?.ΨopenChildren.size) return;
|
||||||
|
if (v && menuDisabled.value) return;
|
||||||
|
_menu.value = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
menuId,
|
||||||
|
ariaExpanded,
|
||||||
|
ariaControls
|
||||||
|
} = useMenuActivator(props, menu);
|
||||||
|
watch(_search, (value) => {
|
||||||
|
if (cleared) {
|
||||||
|
nextTick(() => cleared = false);
|
||||||
|
} else if (isFocused.value && !menu.value) {
|
||||||
|
menu.value = true;
|
||||||
|
}
|
||||||
|
emit("update:search", value);
|
||||||
|
});
|
||||||
|
watch(model, (value) => {
|
||||||
|
if (!props.multiple && !hasSelectionSlot.value) {
|
||||||
|
_search.value = value[0]?.title ?? "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const selectedValues = computed(() => model.value.map((selection) => selection.value));
|
||||||
|
const firstSelectableItem = computed(() => displayItems.value.find((x) => x.type === "item" && !x.props.disabled));
|
||||||
|
const highlightFirst = computed(() => {
|
||||||
|
const selectFirst = props.autoSelectFirst === true || props.autoSelectFirst === "exact" && search.value === firstSelectableItem.value?.title;
|
||||||
|
return selectFirst && displayItems.value.length > 0 && !isPristine.value && !listHasFocus.value;
|
||||||
|
});
|
||||||
|
const listRef = ref();
|
||||||
|
const listEvents = useScrolling(listRef, vTextFieldRef);
|
||||||
|
function onClear(e) {
|
||||||
|
cleared = true;
|
||||||
|
nextTick(() => cleared = false);
|
||||||
|
if (props.openOnClear) {
|
||||||
|
menu.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onMousedownControl() {
|
||||||
|
if (menuDisabled.value) return;
|
||||||
|
menu.value = true;
|
||||||
|
}
|
||||||
|
function onMousedownMenuIcon(e) {
|
||||||
|
if (menuDisabled.value) return;
|
||||||
|
if (isFocused.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
menu.value = !menu.value;
|
||||||
|
}
|
||||||
|
function onListKeydown(e) {
|
||||||
|
if (checkPrintable(e) || e.key === "Backspace") {
|
||||||
|
vTextFieldRef.value?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onKeydown(e) {
|
||||||
|
if (isComposingIgnoreKey(e) || form.isReadonly.value) return;
|
||||||
|
const selectionStart = vTextFieldRef.value?.selectionStart;
|
||||||
|
const length = model.value.length;
|
||||||
|
if (["Enter", "ArrowDown", "ArrowUp"].includes(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (["Enter", "ArrowDown"].includes(e.key)) {
|
||||||
|
menu.value = true;
|
||||||
|
}
|
||||||
|
if (["Escape"].includes(e.key)) {
|
||||||
|
menu.value = false;
|
||||||
|
}
|
||||||
|
if (highlightFirst.value && ["Enter", "Tab"].includes(e.key) && firstSelectableItem.value && !model.value.some((_ref2) => {
|
||||||
|
let {
|
||||||
|
value
|
||||||
|
} = _ref2;
|
||||||
|
return value === firstSelectableItem.value.value;
|
||||||
|
})) {
|
||||||
|
select(firstSelectableItem.value);
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowDown" && highlightFirst.value) {
|
||||||
|
listRef.value?.focus("next");
|
||||||
|
}
|
||||||
|
if (e.key === "Enter" && search.value) {
|
||||||
|
select(transformItem(props, search.value), true, true);
|
||||||
|
if (hasSelectionSlot.value) _search.value = "";
|
||||||
|
}
|
||||||
|
if (["Backspace", "Delete"].includes(e.key)) {
|
||||||
|
if (!props.multiple && hasSelectionSlot.value && model.value.length > 0 && !search.value) return select(model.value[0], false);
|
||||||
|
if (~selectionIndex.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
const originalSelectionIndex = selectionIndex.value;
|
||||||
|
select(model.value[selectionIndex.value], false);
|
||||||
|
selectionIndex.value = originalSelectionIndex >= length - 1 ? length - 2 : originalSelectionIndex;
|
||||||
|
} else if (e.key === "Backspace" && !search.value) {
|
||||||
|
selectionIndex.value = length - 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!props.multiple) return;
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
if (selectionIndex.value < 0 && selectionStart && selectionStart > 0) return;
|
||||||
|
const prev = selectionIndex.value > -1 ? selectionIndex.value - 1 : length - 1;
|
||||||
|
if (model.value[prev]) {
|
||||||
|
selectionIndex.value = prev;
|
||||||
|
} else {
|
||||||
|
selectionIndex.value = -1;
|
||||||
|
vTextFieldRef.value?.setSelectionRange(search.value.length, search.value.length);
|
||||||
|
}
|
||||||
|
} else if (e.key === "ArrowRight") {
|
||||||
|
if (selectionIndex.value < 0) return;
|
||||||
|
const next = selectionIndex.value + 1;
|
||||||
|
if (model.value[next]) {
|
||||||
|
selectionIndex.value = next;
|
||||||
|
} else {
|
||||||
|
selectionIndex.value = -1;
|
||||||
|
vTextFieldRef.value?.setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
} else if (~selectionIndex.value && checkPrintable(e)) {
|
||||||
|
selectionIndex.value = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onPaste(e) {
|
||||||
|
const clipboardText = e?.clipboardData?.getData("Text") ?? "";
|
||||||
|
const values = splitByDelimiters(clipboardText);
|
||||||
|
if (values.length > 1 && props.multiple) {
|
||||||
|
e.preventDefault();
|
||||||
|
selectMultiple(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onAfterEnter() {
|
||||||
|
if (props.eager) {
|
||||||
|
vVirtualScrollRef.value?.calculateVisibleItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onAfterLeave() {
|
||||||
|
if (isFocused.value) {
|
||||||
|
vTextFieldRef.value?.focus();
|
||||||
|
}
|
||||||
|
isPristine.value = true;
|
||||||
|
_searchLock.value = null;
|
||||||
|
}
|
||||||
|
function select(item) {
|
||||||
|
let set = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : true;
|
||||||
|
let keepMenu = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
|
||||||
|
if (!item || item.props.disabled) return;
|
||||||
|
if (props.multiple) {
|
||||||
|
const index = model.value.findIndex((selection) => (props.valueComparator || deepEqual)(selection.value, item.value));
|
||||||
|
const add = set == null ? !~index : set;
|
||||||
|
if (~index) {
|
||||||
|
const value = add ? [...model.value, item] : [...model.value];
|
||||||
|
value.splice(index, 1);
|
||||||
|
model.value = value;
|
||||||
|
} else if (add) {
|
||||||
|
model.value = [...model.value, item];
|
||||||
|
}
|
||||||
|
if (props.clearOnSelect) {
|
||||||
|
search.value = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const add = set !== false;
|
||||||
|
model.value = add ? [item] : [];
|
||||||
|
if ((!isPristine.value || props.alwaysFilter) && _search.value) {
|
||||||
|
_searchLock.value = _search.value;
|
||||||
|
}
|
||||||
|
_search.value = add && !hasSelectionSlot.value ? item.title : "";
|
||||||
|
nextTick(() => {
|
||||||
|
menu.value = keepMenu;
|
||||||
|
isPristine.value = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function splitByDelimiters(val) {
|
||||||
|
const effectiveDelimiters = ["\n", ...props.delimiters ?? []];
|
||||||
|
const signsToMatch = effectiveDelimiters.map(escapeForRegex).join("|");
|
||||||
|
return val.split(new RegExp(`(?:${signsToMatch})+`));
|
||||||
|
}
|
||||||
|
async function selectMultiple(values) {
|
||||||
|
for (let value of values) {
|
||||||
|
value = value.trim();
|
||||||
|
if (value) {
|
||||||
|
select(transformItem(props, value));
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onFocusin(e) {
|
||||||
|
isFocused.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
listHasFocus.value = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onFocusout(e) {
|
||||||
|
listHasFocus.value = false;
|
||||||
|
}
|
||||||
|
watch(isFocused, (val, oldVal) => {
|
||||||
|
if (val || val === oldVal) return;
|
||||||
|
selectionIndex.value = -1;
|
||||||
|
menu.value = false;
|
||||||
|
if (search.value) {
|
||||||
|
if (props.multiple) {
|
||||||
|
select(transformItem(props, search.value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hasSelectionSlot.value) return;
|
||||||
|
if (model.value.some((_ref3) => {
|
||||||
|
let {
|
||||||
|
title
|
||||||
|
} = _ref3;
|
||||||
|
return title === search.value;
|
||||||
|
})) {
|
||||||
|
_search.value = "";
|
||||||
|
} else {
|
||||||
|
select(transformItem(props, search.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watch(menu, (val) => {
|
||||||
|
if (!props.hideSelected && val && model.value.length && isPristine.value) {
|
||||||
|
const index = displayItems.value.findIndex((item) => model.value.some((s) => (props.valueComparator || deepEqual)(s.value, item.value)));
|
||||||
|
IN_BROWSER && window.requestAnimationFrame(() => {
|
||||||
|
index >= 0 && vVirtualScrollRef.value?.scrollToIndex(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (val) _searchLock.value = null;
|
||||||
|
});
|
||||||
|
watch(items, (newVal, oldVal) => {
|
||||||
|
if (menu.value) return;
|
||||||
|
if (isFocused.value && !oldVal.length && newVal.length) {
|
||||||
|
menu.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useRender(() => {
|
||||||
|
const hasList = !!(!props.hideNoData || displayItems.value.length || slots["prepend-item"] || slots["append-item"] || slots["no-data"]);
|
||||||
|
const isDirty = model.value.length > 0;
|
||||||
|
const textFieldProps = VTextField.filterProps(props);
|
||||||
|
return createVNode(VTextField, mergeProps({
|
||||||
|
"ref": vTextFieldRef
|
||||||
|
}, textFieldProps, {
|
||||||
|
"modelValue": search.value,
|
||||||
|
"onUpdate:modelValue": ($event) => search.value = $event,
|
||||||
|
"focused": isFocused.value,
|
||||||
|
"onUpdate:focused": ($event) => isFocused.value = $event,
|
||||||
|
"validationValue": model.externalValue,
|
||||||
|
"counterValue": counterValue.value,
|
||||||
|
"dirty": isDirty,
|
||||||
|
"class": ["v-combobox", {
|
||||||
|
"v-combobox--active-menu": menu.value,
|
||||||
|
"v-combobox--chips": !!props.chips,
|
||||||
|
"v-combobox--selection-slot": !!hasSelectionSlot.value,
|
||||||
|
"v-combobox--selecting-index": selectionIndex.value > -1,
|
||||||
|
[`v-combobox--${props.multiple ? "multiple" : "single"}`]: true
|
||||||
|
}, props.class],
|
||||||
|
"style": props.style,
|
||||||
|
"readonly": form.isReadonly.value,
|
||||||
|
"placeholder": isDirty ? void 0 : props.placeholder,
|
||||||
|
"onClick:clear": onClear,
|
||||||
|
"onMousedown:control": onMousedownControl,
|
||||||
|
"onKeydown": onKeydown,
|
||||||
|
"onPaste": onPaste,
|
||||||
|
"aria-expanded": ariaExpanded.value,
|
||||||
|
"aria-controls": ariaControls.value
|
||||||
|
}), {
|
||||||
|
...slots,
|
||||||
|
default: (_ref4) => {
|
||||||
|
let {
|
||||||
|
id
|
||||||
|
} = _ref4;
|
||||||
|
return createBaseVNode(Fragment, null, [createVNode(VMenu, mergeProps({
|
||||||
|
"id": menuId.value,
|
||||||
|
"ref": vMenuRef,
|
||||||
|
"modelValue": menu.value,
|
||||||
|
"onUpdate:modelValue": ($event) => menu.value = $event,
|
||||||
|
"activator": "parent",
|
||||||
|
"contentClass": "v-combobox__content",
|
||||||
|
"disabled": menuDisabled.value,
|
||||||
|
"eager": props.eager,
|
||||||
|
"maxHeight": 310,
|
||||||
|
"openOnClick": false,
|
||||||
|
"closeOnContentClick": false,
|
||||||
|
"onAfterEnter": onAfterEnter,
|
||||||
|
"onAfterLeave": onAfterLeave
|
||||||
|
}, props.menuProps), {
|
||||||
|
default: () => [hasList && createVNode(VList, mergeProps({
|
||||||
|
"ref": listRef,
|
||||||
|
"filterable": true,
|
||||||
|
"selected": selectedValues.value,
|
||||||
|
"selectStrategy": props.multiple ? "independent" : "single-independent",
|
||||||
|
"onMousedown": (e) => e.preventDefault(),
|
||||||
|
"selectable": true,
|
||||||
|
"onKeydown": onListKeydown,
|
||||||
|
"onFocusin": onFocusin,
|
||||||
|
"onFocusout": onFocusout,
|
||||||
|
"tabindex": "-1",
|
||||||
|
"aria-live": "polite",
|
||||||
|
"aria-labelledby": `${id.value}-label`,
|
||||||
|
"aria-multiselectable": props.multiple,
|
||||||
|
"color": props.itemColor ?? props.color
|
||||||
|
}, listEvents, props.listProps), {
|
||||||
|
default: () => [slots["prepend-item"]?.(), !displayItems.value.length && !props.hideNoData && (slots["no-data"]?.() ?? createVNode(VListItem, {
|
||||||
|
"key": "no-data",
|
||||||
|
"title": t(props.noDataText)
|
||||||
|
}, null)), createVNode(VVirtualScroll, {
|
||||||
|
"ref": vVirtualScrollRef,
|
||||||
|
"renderless": true,
|
||||||
|
"items": displayItems.value,
|
||||||
|
"itemKey": "value"
|
||||||
|
}, {
|
||||||
|
default: (_ref5) => {
|
||||||
|
let {
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
itemRef
|
||||||
|
} = _ref5;
|
||||||
|
const itemProps = mergeProps(item.props, {
|
||||||
|
ref: itemRef,
|
||||||
|
key: item.value,
|
||||||
|
active: highlightFirst.value && item === firstSelectableItem.value ? true : void 0,
|
||||||
|
onClick: () => select(item, null),
|
||||||
|
"aria-posinset": index + 1,
|
||||||
|
"aria-setsize": displayItems.value.length
|
||||||
|
});
|
||||||
|
if (item.type === "divider") {
|
||||||
|
return slots.divider?.({
|
||||||
|
props: item.raw,
|
||||||
|
index
|
||||||
|
}) ?? createVNode(VDivider, mergeProps(item.props, {
|
||||||
|
"key": `divider-${index}`
|
||||||
|
}), null);
|
||||||
|
}
|
||||||
|
if (item.type === "subheader") {
|
||||||
|
return slots.subheader?.({
|
||||||
|
props: item.raw,
|
||||||
|
index
|
||||||
|
}) ?? createVNode(VListSubheader, mergeProps(item.props, {
|
||||||
|
"key": `subheader-${index}`
|
||||||
|
}), null);
|
||||||
|
}
|
||||||
|
return slots.item?.({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
props: itemProps
|
||||||
|
}) ?? createVNode(VListItem, mergeProps(itemProps, {
|
||||||
|
"role": "option"
|
||||||
|
}), {
|
||||||
|
prepend: (_ref6) => {
|
||||||
|
let {
|
||||||
|
isSelected
|
||||||
|
} = _ref6;
|
||||||
|
return createBaseVNode(Fragment, null, [props.multiple && !props.hideSelected ? createVNode(VCheckboxBtn, {
|
||||||
|
"key": item.value,
|
||||||
|
"modelValue": isSelected,
|
||||||
|
"ripple": false,
|
||||||
|
"tabindex": "-1",
|
||||||
|
"aria-hidden": true,
|
||||||
|
"onClick": (event) => event.preventDefault()
|
||||||
|
}, null) : void 0, item.props.prependAvatar && createVNode(VAvatar, {
|
||||||
|
"image": item.props.prependAvatar
|
||||||
|
}, null), item.props.prependIcon && createVNode(VIcon, {
|
||||||
|
"icon": item.props.prependIcon
|
||||||
|
}, null)]);
|
||||||
|
},
|
||||||
|
title: () => {
|
||||||
|
return isPristine.value ? item.title : highlightResult("v-combobox", item.title, getMatches(item)?.title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}), slots["append-item"]?.()]
|
||||||
|
})]
|
||||||
|
}), model.value.map((item, index) => {
|
||||||
|
function onChipClose(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
select(item, false);
|
||||||
|
}
|
||||||
|
const slotProps = mergeProps(VChip.filterProps(item.props), {
|
||||||
|
"onClick:close": onChipClose,
|
||||||
|
onKeydown(e) {
|
||||||
|
if (e.key !== "Enter" && e.key !== " ") return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onChipClose(e);
|
||||||
|
},
|
||||||
|
onMousedown(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
modelValue: true,
|
||||||
|
"onUpdate:modelValue": void 0
|
||||||
|
});
|
||||||
|
const hasSlot = hasChips.value ? !!slots.chip : !!slots.selection;
|
||||||
|
const slotContent = hasSlot ? ensureValidVNode(hasChips.value ? slots.chip({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
props: slotProps
|
||||||
|
}) : slots.selection({
|
||||||
|
item,
|
||||||
|
index
|
||||||
|
})) : void 0;
|
||||||
|
if (hasSlot && !slotContent) return void 0;
|
||||||
|
return createBaseVNode("div", {
|
||||||
|
"key": item.value,
|
||||||
|
"class": normalizeClass(["v-combobox__selection", index === selectionIndex.value && ["v-combobox__selection--selected", textColorClasses.value]]),
|
||||||
|
"style": normalizeStyle(index === selectionIndex.value ? textColorStyles.value : {})
|
||||||
|
}, [hasChips.value ? !slots.chip ? createVNode(VChip, mergeProps({
|
||||||
|
"key": "chip",
|
||||||
|
"closable": closableChips.value,
|
||||||
|
"size": "small",
|
||||||
|
"text": item.title,
|
||||||
|
"disabled": item.props.disabled
|
||||||
|
}, slotProps), null) : createVNode(VDefaultsProvider, {
|
||||||
|
"key": "chip-defaults",
|
||||||
|
"defaults": {
|
||||||
|
VChip: {
|
||||||
|
closable: closableChips.value,
|
||||||
|
size: "small",
|
||||||
|
text: item.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
default: () => [slotContent]
|
||||||
|
}) : slotContent ?? createBaseVNode("span", {
|
||||||
|
"class": "v-combobox__selection-text"
|
||||||
|
}, [item.title, props.multiple && index < model.value.length - 1 && createBaseVNode("span", {
|
||||||
|
"class": "v-combobox__selection-comma"
|
||||||
|
}, [createTextVNode(",")])])]);
|
||||||
|
})]);
|
||||||
|
},
|
||||||
|
"append-inner": function() {
|
||||||
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
args[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
return createBaseVNode(Fragment, null, [slots["append-inner"]?.(...args), (!props.hideNoData || props.items.length) && props.menuIcon ? createVNode(VIcon, {
|
||||||
|
"class": "v-combobox__menu-icon",
|
||||||
|
"color": vTextFieldRef.value?.fieldIconColor,
|
||||||
|
"icon": props.menuIcon,
|
||||||
|
"onMousedown": onMousedownMenuIcon,
|
||||||
|
"onClick": noop,
|
||||||
|
"aria-hidden": true,
|
||||||
|
"tabindex": "-1"
|
||||||
|
}, null) : void 0, props.appendInnerIcon && createVNode(InputIcon, {
|
||||||
|
"key": "append-icon",
|
||||||
|
"name": "appendInner",
|
||||||
|
"color": args[0].iconColor.value
|
||||||
|
}, null)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return forwardRefs({
|
||||||
|
isFocused,
|
||||||
|
isPristine,
|
||||||
|
menu,
|
||||||
|
search,
|
||||||
|
selectionIndex,
|
||||||
|
filteredItems,
|
||||||
|
select
|
||||||
|
}, vTextFieldRef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
VCombobox
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=chunk-244ON7Z6.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+3
-113
@@ -1,31 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
VOverlay,
|
keys
|
||||||
makeVOverlayProps
|
|
||||||
} from "./chunk-TS6Y6OVT.js";
|
|
||||||
import {
|
|
||||||
useScopeId
|
|
||||||
} from "./chunk-BVUPSGRF.js";
|
|
||||||
import {
|
|
||||||
forwardRefs
|
|
||||||
} from "./chunk-GUNW7S2L.js";
|
|
||||||
import {
|
|
||||||
useProxiedModel
|
|
||||||
} from "./chunk-KA77II4V.js";
|
|
||||||
import {
|
|
||||||
genericComponent,
|
|
||||||
keys,
|
|
||||||
omit,
|
|
||||||
propsFactory,
|
|
||||||
useRender
|
|
||||||
} from "./chunk-LZ5T2ERG.js";
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
createVNode,
|
|
||||||
mergeProps,
|
|
||||||
ref,
|
|
||||||
toRef,
|
|
||||||
useId
|
|
||||||
} from "./chunk-2MKFL3BX.js";
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/directives/resize/index.js
|
// node_modules/vuetify/lib/directives/resize/index.js
|
||||||
function mounted(el, binding) {
|
function mounted(el, binding) {
|
||||||
@@ -163,95 +138,10 @@ var Touch = {
|
|||||||
};
|
};
|
||||||
var touch_default = Touch;
|
var touch_default = Touch;
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VTooltip/VTooltip.js
|
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTooltip/VTooltip.css";
|
|
||||||
var makeVTooltipProps = propsFactory({
|
|
||||||
id: String,
|
|
||||||
interactive: Boolean,
|
|
||||||
text: String,
|
|
||||||
...omit(makeVOverlayProps({
|
|
||||||
closeOnBack: false,
|
|
||||||
location: "end",
|
|
||||||
locationStrategy: "connected",
|
|
||||||
eager: true,
|
|
||||||
minWidth: 0,
|
|
||||||
offset: 10,
|
|
||||||
openOnClick: false,
|
|
||||||
openOnHover: true,
|
|
||||||
origin: "auto",
|
|
||||||
scrim: false,
|
|
||||||
scrollStrategy: "reposition",
|
|
||||||
transition: null
|
|
||||||
}), ["absolute", "retainFocus", "captureFocus", "disableInitialFocus"])
|
|
||||||
}, "VTooltip");
|
|
||||||
var VTooltip = genericComponent()({
|
|
||||||
name: "VTooltip",
|
|
||||||
props: makeVTooltipProps(),
|
|
||||||
emits: {
|
|
||||||
"update:modelValue": (value) => true
|
|
||||||
},
|
|
||||||
setup(props, _ref) {
|
|
||||||
let {
|
|
||||||
slots
|
|
||||||
} = _ref;
|
|
||||||
const isActive = useProxiedModel(props, "modelValue");
|
|
||||||
const {
|
|
||||||
scopeId
|
|
||||||
} = useScopeId();
|
|
||||||
const uid = useId();
|
|
||||||
const id = toRef(() => props.id || `v-tooltip-${uid}`);
|
|
||||||
const overlay = ref();
|
|
||||||
const location = computed(() => {
|
|
||||||
return props.location.split(" ").length > 1 ? props.location : props.location + " center";
|
|
||||||
});
|
|
||||||
const origin = computed(() => {
|
|
||||||
return props.origin === "auto" || props.origin === "overlap" || props.origin.split(" ").length > 1 || props.location.split(" ").length > 1 ? props.origin : props.origin + " center";
|
|
||||||
});
|
|
||||||
const transition = toRef(() => {
|
|
||||||
if (props.transition != null) return props.transition;
|
|
||||||
return isActive.value ? "scale-transition" : "fade-transition";
|
|
||||||
});
|
|
||||||
const activatorProps = computed(() => mergeProps({
|
|
||||||
"aria-describedby": id.value
|
|
||||||
}, props.activatorProps));
|
|
||||||
useRender(() => {
|
|
||||||
const overlayProps = VOverlay.filterProps(props);
|
|
||||||
return createVNode(VOverlay, mergeProps({
|
|
||||||
"ref": overlay,
|
|
||||||
"class": ["v-tooltip", {
|
|
||||||
"v-tooltip--interactive": props.interactive
|
|
||||||
}, props.class],
|
|
||||||
"style": props.style,
|
|
||||||
"id": id.value
|
|
||||||
}, overlayProps, {
|
|
||||||
"modelValue": isActive.value,
|
|
||||||
"onUpdate:modelValue": ($event) => isActive.value = $event,
|
|
||||||
"transition": transition.value,
|
|
||||||
"absolute": true,
|
|
||||||
"location": location.value,
|
|
||||||
"origin": origin.value,
|
|
||||||
"role": "tooltip",
|
|
||||||
"activatorProps": activatorProps.value,
|
|
||||||
"_disableGlobalStack": true
|
|
||||||
}, scopeId), {
|
|
||||||
activator: slots.activator,
|
|
||||||
default: function() {
|
|
||||||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
||||||
args[_key] = arguments[_key];
|
|
||||||
}
|
|
||||||
return slots.default?.(...args) ?? props.text;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return forwardRefs({}, overlay);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Resize,
|
Resize,
|
||||||
resize_default,
|
resize_default,
|
||||||
Touch,
|
Touch,
|
||||||
touch_default,
|
touch_default
|
||||||
VTooltip
|
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-6VKWM6MN.js.map
|
//# sourceMappingURL=chunk-2AYDXCXT.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+18
-18
@@ -1,18 +1,18 @@
|
|||||||
|
import {
|
||||||
|
VAvatar
|
||||||
|
} from "./chunk-SXI5CS7S.js";
|
||||||
|
import {
|
||||||
|
VImg
|
||||||
|
} from "./chunk-TTIGZHWR.js";
|
||||||
import {
|
import {
|
||||||
makePositionProps,
|
makePositionProps,
|
||||||
usePosition
|
usePosition
|
||||||
} from "./chunk-HOEZWRBG.js";
|
} from "./chunk-HOEZWRBG.js";
|
||||||
import {
|
|
||||||
VAvatar
|
|
||||||
} from "./chunk-Z3677HMS.js";
|
|
||||||
import {
|
import {
|
||||||
genOverlays,
|
genOverlays,
|
||||||
makeVariantProps,
|
makeVariantProps,
|
||||||
useVariant
|
useVariant
|
||||||
} from "./chunk-ADXDXPPR.js";
|
} from "./chunk-ADXDXPPR.js";
|
||||||
import {
|
|
||||||
VImg
|
|
||||||
} from "./chunk-6CRS5HI4.js";
|
|
||||||
import {
|
import {
|
||||||
makeBorderProps,
|
makeBorderProps,
|
||||||
useBorder
|
useBorder
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
LoaderSlot,
|
LoaderSlot,
|
||||||
makeLoaderProps,
|
makeLoaderProps,
|
||||||
useLoader
|
useLoader
|
||||||
} from "./chunk-U2DSRSME.js";
|
} from "./chunk-UXZ55AO7.js";
|
||||||
import {
|
import {
|
||||||
makeLocationProps,
|
makeLocationProps,
|
||||||
useLocation
|
useLocation
|
||||||
@@ -36,31 +36,31 @@ import {
|
|||||||
} from "./chunk-TM3MIDYC.js";
|
} from "./chunk-TM3MIDYC.js";
|
||||||
import {
|
import {
|
||||||
VIcon
|
VIcon
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
} from "./chunk-4XZHKYGH.js";
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
makeRoundedProps,
|
makeRoundedProps,
|
||||||
useRounded
|
useRounded
|
||||||
} from "./chunk-AQK3CYVW.js";
|
} from "./chunk-AQK3CYVW.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
import {
|
import {
|
||||||
ripple_default
|
IconValue
|
||||||
} from "./chunk-5BPWKCNA.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
import {
|
import {
|
||||||
makeRouterProps,
|
makeRouterProps,
|
||||||
useLink
|
useLink
|
||||||
} from "./chunk-573D4TYC.js";
|
} from "./chunk-573D4TYC.js";
|
||||||
|
import {
|
||||||
|
ripple_default
|
||||||
|
} from "./chunk-DS4J67XG.js";
|
||||||
import {
|
import {
|
||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
IconValue
|
|
||||||
} from "./chunk-OJDZBU5D.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -87,7 +87,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCard/VCard.js
|
// node_modules/vuetify/lib/components/VCard/VCard.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCard/VCard.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCard/VCard.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCard/VCardActions.js
|
// node_modules/vuetify/lib/components/VCard/VCardActions.js
|
||||||
var makeVCardActionsProps = propsFactory({
|
var makeVCardActionsProps = propsFactory({
|
||||||
@@ -441,4 +441,4 @@ export {
|
|||||||
VCardText,
|
VCardText,
|
||||||
VCard
|
VCard
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-MPVSDM4V.js.map
|
//# sourceMappingURL=chunk-3NOJ7Z44.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+2
-2
@@ -23,7 +23,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VApp/VApp.js
|
// node_modules/vuetify/lib/components/VApp/VApp.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VApp/VApp.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VApp/VApp.css";
|
||||||
var makeVAppProps = propsFactory({
|
var makeVAppProps = propsFactory({
|
||||||
...makeComponentProps(),
|
...makeComponentProps(),
|
||||||
...omit(makeLayoutProps(), ["fullHeight"]),
|
...omit(makeLayoutProps(), ["fullHeight"]),
|
||||||
@@ -67,4 +67,4 @@ var VApp = genericComponent()({
|
|||||||
export {
|
export {
|
||||||
VApp
|
VApp
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-IYFY4IHW.js.map
|
//# sourceMappingURL=chunk-47GEKY7C.js.map
|
||||||
Generated
Vendored
-7
File diff suppressed because one or more lines are too long
Generated
Vendored
+2
-2
@@ -33,7 +33,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VIcon/VIcon.js
|
// node_modules/vuetify/lib/components/VIcon/VIcon.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VIcon/VIcon.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VIcon/VIcon.css";
|
||||||
var makeVIconProps = propsFactory({
|
var makeVIconProps = propsFactory({
|
||||||
color: String,
|
color: String,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
@@ -106,4 +106,4 @@ var VIcon = genericComponent()({
|
|||||||
export {
|
export {
|
||||||
VIcon
|
VIcon
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-CMZ4UYUX.js.map
|
//# sourceMappingURL=chunk-4XZHKYGH.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+3
-3
@@ -44,7 +44,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VBtnGroup/VBtnGroup.js
|
// node_modules/vuetify/lib/components/VBtnGroup/VBtnGroup.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VBtnGroup/VBtnGroup.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VBtnGroup/VBtnGroup.css";
|
||||||
var makeVBtnGroupProps = propsFactory({
|
var makeVBtnGroupProps = propsFactory({
|
||||||
baseColor: String,
|
baseColor: String,
|
||||||
divided: Boolean,
|
divided: Boolean,
|
||||||
@@ -105,7 +105,7 @@ var VBtnGroup = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VBtnToggle/VBtnToggle.js
|
// node_modules/vuetify/lib/components/VBtnToggle/VBtnToggle.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VBtnToggle/VBtnToggle.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VBtnToggle/VBtnToggle.css";
|
||||||
var VBtnToggleSymbol = /* @__PURE__ */ Symbol.for("vuetify:v-btn-toggle");
|
var VBtnToggleSymbol = /* @__PURE__ */ Symbol.for("vuetify:v-btn-toggle");
|
||||||
var makeVBtnToggleProps = propsFactory({
|
var makeVBtnToggleProps = propsFactory({
|
||||||
...makeVBtnGroupProps(),
|
...makeVBtnGroupProps(),
|
||||||
@@ -157,4 +157,4 @@ export {
|
|||||||
VBtnToggleSymbol,
|
VBtnToggleSymbol,
|
||||||
VBtnToggle
|
VBtnToggle
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-NURQ3473.js.map
|
//# sourceMappingURL=chunk-5AQLTWWS.js.map
|
||||||
Generated
Vendored
+162
@@ -0,0 +1,162 @@
|
|||||||
|
import {
|
||||||
|
VSelectionControl,
|
||||||
|
makeVSelectionControlProps
|
||||||
|
} from "./chunk-KHWE4FHS.js";
|
||||||
|
import {
|
||||||
|
VInput,
|
||||||
|
makeVInputProps,
|
||||||
|
useFocus
|
||||||
|
} from "./chunk-IK67YVHF.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
IconValue
|
||||||
|
} from "./chunk-OJDZBU5D.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
|
import {
|
||||||
|
filterInputAttrs,
|
||||||
|
genericComponent,
|
||||||
|
omit,
|
||||||
|
propsFactory,
|
||||||
|
useRender
|
||||||
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
import {
|
||||||
|
createVNode,
|
||||||
|
mergeProps,
|
||||||
|
ref,
|
||||||
|
toRef,
|
||||||
|
useId
|
||||||
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VCheckbox/VCheckbox.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCheckbox/VCheckbox.css";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VCheckbox/VCheckboxBtn.js
|
||||||
|
var makeVCheckboxBtnProps = propsFactory({
|
||||||
|
indeterminate: Boolean,
|
||||||
|
indeterminateIcon: {
|
||||||
|
type: IconValue,
|
||||||
|
default: "$checkboxIndeterminate"
|
||||||
|
},
|
||||||
|
...makeVSelectionControlProps({
|
||||||
|
falseIcon: "$checkboxOff",
|
||||||
|
trueIcon: "$checkboxOn"
|
||||||
|
})
|
||||||
|
}, "VCheckboxBtn");
|
||||||
|
var VCheckboxBtn = genericComponent()({
|
||||||
|
name: "VCheckboxBtn",
|
||||||
|
props: makeVCheckboxBtnProps(),
|
||||||
|
emits: {
|
||||||
|
"update:modelValue": (value) => true,
|
||||||
|
"update:indeterminate": (value) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const indeterminate = useProxiedModel(props, "indeterminate");
|
||||||
|
const model = useProxiedModel(props, "modelValue");
|
||||||
|
function onChange(v) {
|
||||||
|
if (indeterminate.value) {
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const falseIcon = toRef(() => {
|
||||||
|
return indeterminate.value ? props.indeterminateIcon : props.falseIcon;
|
||||||
|
});
|
||||||
|
const trueIcon = toRef(() => {
|
||||||
|
return indeterminate.value ? props.indeterminateIcon : props.trueIcon;
|
||||||
|
});
|
||||||
|
useRender(() => {
|
||||||
|
const controlProps = omit(VSelectionControl.filterProps(props), ["modelValue"]);
|
||||||
|
return createVNode(VSelectionControl, mergeProps(controlProps, {
|
||||||
|
"modelValue": model.value,
|
||||||
|
"onUpdate:modelValue": [($event) => model.value = $event, onChange],
|
||||||
|
"class": ["v-checkbox-btn", props.class],
|
||||||
|
"style": props.style,
|
||||||
|
"type": "checkbox",
|
||||||
|
"falseIcon": falseIcon.value,
|
||||||
|
"trueIcon": trueIcon.value,
|
||||||
|
"aria-checked": indeterminate.value ? "mixed" : void 0
|
||||||
|
}), slots);
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VCheckbox/VCheckbox.js
|
||||||
|
var makeVCheckboxProps = propsFactory({
|
||||||
|
...makeVInputProps(),
|
||||||
|
...omit(makeVCheckboxBtnProps(), ["inline"])
|
||||||
|
}, "VCheckbox");
|
||||||
|
var VCheckbox = genericComponent()({
|
||||||
|
name: "VCheckbox",
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: makeVCheckboxProps(),
|
||||||
|
emits: {
|
||||||
|
"update:modelValue": (value) => true,
|
||||||
|
"update:focused": (focused) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
attrs,
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const model = useProxiedModel(props, "modelValue");
|
||||||
|
const {
|
||||||
|
isFocused,
|
||||||
|
focus,
|
||||||
|
blur
|
||||||
|
} = useFocus(props);
|
||||||
|
const inputRef = ref();
|
||||||
|
const uid = useId();
|
||||||
|
useRender(() => {
|
||||||
|
const [rootAttrs, controlAttrs] = filterInputAttrs(attrs);
|
||||||
|
const inputProps = VInput.filterProps(props);
|
||||||
|
const checkboxProps = VCheckboxBtn.filterProps(props);
|
||||||
|
return createVNode(VInput, mergeProps({
|
||||||
|
"ref": inputRef,
|
||||||
|
"class": ["v-checkbox", props.class]
|
||||||
|
}, rootAttrs, inputProps, {
|
||||||
|
"modelValue": model.value,
|
||||||
|
"onUpdate:modelValue": ($event) => model.value = $event,
|
||||||
|
"id": props.id || `checkbox-${uid}`,
|
||||||
|
"focused": isFocused.value,
|
||||||
|
"style": props.style
|
||||||
|
}), {
|
||||||
|
...slots,
|
||||||
|
default: (_ref2) => {
|
||||||
|
let {
|
||||||
|
id,
|
||||||
|
messagesId,
|
||||||
|
isDisabled,
|
||||||
|
isReadonly,
|
||||||
|
isValid
|
||||||
|
} = _ref2;
|
||||||
|
return createVNode(VCheckboxBtn, mergeProps(checkboxProps, {
|
||||||
|
"id": id.value,
|
||||||
|
"aria-describedby": messagesId.value,
|
||||||
|
"disabled": isDisabled.value,
|
||||||
|
"readonly": isReadonly.value
|
||||||
|
}, controlAttrs, {
|
||||||
|
"error": isValid.value === false,
|
||||||
|
"modelValue": model.value,
|
||||||
|
"onUpdate:modelValue": ($event) => model.value = $event,
|
||||||
|
"onFocus": focus,
|
||||||
|
"onBlur": blur
|
||||||
|
}), slots);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return forwardRefs({}, inputRef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
VCheckboxBtn,
|
||||||
|
VCheckbox
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=chunk-6HLLVLOR.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+7
-7
@@ -7,18 +7,18 @@ import {
|
|||||||
VCounter,
|
VCounter,
|
||||||
VField,
|
VField,
|
||||||
makeVFieldProps
|
makeVFieldProps
|
||||||
} from "./chunk-J7Q2NMV7.js";
|
} from "./chunk-ITO3JWXJ.js";
|
||||||
import {
|
import {
|
||||||
VInput,
|
VInput,
|
||||||
makeVInputProps,
|
makeVInputProps,
|
||||||
useFocus
|
useFocus
|
||||||
} from "./chunk-4VABFUPG.js";
|
} from "./chunk-IK67YVHF.js";
|
||||||
import {
|
|
||||||
forwardRefs
|
|
||||||
} from "./chunk-GUNW7S2L.js";
|
|
||||||
import {
|
import {
|
||||||
intersect_default
|
intersect_default
|
||||||
} from "./chunk-5ONGSJQ2.js";
|
} from "./chunk-5ONGSJQ2.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
import {
|
import {
|
||||||
useProxiedModel
|
useProxiedModel
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
@@ -44,7 +44,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VTextField/VTextField.js
|
// node_modules/vuetify/lib/components/VTextField/VTextField.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTextField/VTextField.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTextField/VTextField.css";
|
||||||
var activeTypes = ["color", "file", "time", "date", "datetime-local", "week", "month"];
|
var activeTypes = ["color", "file", "time", "date", "datetime-local", "week", "month"];
|
||||||
var makeVTextFieldProps = propsFactory({
|
var makeVTextFieldProps = propsFactory({
|
||||||
autofocus: Boolean,
|
autofocus: Boolean,
|
||||||
@@ -267,4 +267,4 @@ export {
|
|||||||
makeVTextFieldProps,
|
makeVTextFieldProps,
|
||||||
VTextField
|
VTextField
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-VSEC5QGQ.js.map
|
//# sourceMappingURL=chunk-6MO6MKTY.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+6
-6
@@ -12,6 +12,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor,
|
||||||
|
useTextColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
useResizeObserver
|
useResizeObserver
|
||||||
} from "./chunk-XDSAO7OT.js";
|
} from "./chunk-XDSAO7OT.js";
|
||||||
@@ -22,10 +26,6 @@ import {
|
|||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
useBackgroundColor,
|
|
||||||
useTextColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -53,7 +53,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VProgressLinear/VProgressLinear.js
|
// node_modules/vuetify/lib/components/VProgressLinear/VProgressLinear.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VProgressLinear/VProgressLinear.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VProgressLinear/VProgressLinear.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VProgressLinear/chunks.js
|
// node_modules/vuetify/lib/components/VProgressLinear/chunks.js
|
||||||
var makeChunksProps = propsFactory({
|
var makeChunksProps = propsFactory({
|
||||||
@@ -318,4 +318,4 @@ var VProgressLinear = genericComponent()({
|
|||||||
export {
|
export {
|
||||||
VProgressLinear
|
VProgressLinear
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-FVDWV4NO.js.map
|
//# sourceMappingURL=chunk-6RFSM2OD.js.map
|
||||||
Generated
Vendored
-7
File diff suppressed because one or more lines are too long
Generated
Vendored
+5
-5
@@ -21,13 +21,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
useBackgroundColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSheet/VSheet.js
|
// node_modules/vuetify/lib/components/VSheet/VSheet.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSheet/VSheet.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSheet/VSheet.css";
|
||||||
var makeVSheetProps = propsFactory({
|
var makeVSheetProps = propsFactory({
|
||||||
color: String,
|
color: String,
|
||||||
...makeBorderProps(),
|
...makeBorderProps(),
|
||||||
@@ -102,4 +102,4 @@ export {
|
|||||||
makeVSheetProps,
|
makeVSheetProps,
|
||||||
VSheet
|
VSheet
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-RNFCLGZV.js.map
|
//# sourceMappingURL=chunk-7M5DDQ46.js.map
|
||||||
Generated
Vendored
-7
File diff suppressed because one or more lines are too long
Generated
Vendored
+19
-19
@@ -1,3 +1,17 @@
|
|||||||
|
import {
|
||||||
|
VOverlay,
|
||||||
|
makeVOverlayProps
|
||||||
|
} from "./chunk-CL6G3ICF.js";
|
||||||
|
import {
|
||||||
|
useScopeId
|
||||||
|
} from "./chunk-BVUPSGRF.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
VuetifyLayoutKey,
|
||||||
|
useLayout
|
||||||
|
} from "./chunk-KQXZAEJP.js";
|
||||||
import {
|
import {
|
||||||
makePositionProps,
|
makePositionProps,
|
||||||
usePosition
|
usePosition
|
||||||
@@ -9,31 +23,17 @@ import {
|
|||||||
} from "./chunk-ADXDXPPR.js";
|
} from "./chunk-ADXDXPPR.js";
|
||||||
import {
|
import {
|
||||||
VProgressLinear
|
VProgressLinear
|
||||||
} from "./chunk-FVDWV4NO.js";
|
} from "./chunk-6RFSM2OD.js";
|
||||||
import {
|
import {
|
||||||
makeLocationProps
|
makeLocationProps
|
||||||
} from "./chunk-KTTVT6V2.js";
|
} from "./chunk-KTTVT6V2.js";
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
makeRoundedProps,
|
makeRoundedProps,
|
||||||
useRounded
|
useRounded
|
||||||
} from "./chunk-AQK3CYVW.js";
|
} from "./chunk-AQK3CYVW.js";
|
||||||
import {
|
import {
|
||||||
VOverlay,
|
VDefaultsProvider
|
||||||
makeVOverlayProps
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
} from "./chunk-TS6Y6OVT.js";
|
|
||||||
import {
|
|
||||||
useScopeId
|
|
||||||
} from "./chunk-BVUPSGRF.js";
|
|
||||||
import {
|
|
||||||
forwardRefs
|
|
||||||
} from "./chunk-GUNW7S2L.js";
|
|
||||||
import {
|
|
||||||
VuetifyLayoutKey,
|
|
||||||
useLayout
|
|
||||||
} from "./chunk-KQXZAEJP.js";
|
|
||||||
import {
|
import {
|
||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
@@ -65,7 +65,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSnackbar/VSnackbar.js
|
// node_modules/vuetify/lib/components/VSnackbar/VSnackbar.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSnackbar/VSnackbar.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSnackbar/VSnackbar.css";
|
||||||
function useCountdown(milliseconds) {
|
function useCountdown(milliseconds) {
|
||||||
const time = shallowRef(milliseconds());
|
const time = shallowRef(milliseconds());
|
||||||
let timer = -1;
|
let timer = -1;
|
||||||
@@ -275,4 +275,4 @@ export {
|
|||||||
makeVSnackbarProps,
|
makeVSnackbarProps,
|
||||||
VSnackbar
|
VSnackbar
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-YDVSSFCH.js.map
|
//# sourceMappingURL=chunk-CCNWGDBL.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+84
-84
@@ -9,21 +9,24 @@ import {
|
|||||||
useFocusTrap,
|
useFocusTrap,
|
||||||
useScopeId
|
useScopeId
|
||||||
} from "./chunk-BVUPSGRF.js";
|
} from "./chunk-BVUPSGRF.js";
|
||||||
import {
|
|
||||||
useBackButton,
|
|
||||||
useRouter
|
|
||||||
} from "./chunk-573D4TYC.js";
|
|
||||||
import {
|
import {
|
||||||
MaybeTransition,
|
MaybeTransition,
|
||||||
makeTransitionProps
|
makeTransitionProps
|
||||||
} from "./chunk-CAGZXMOS.js";
|
} from "./chunk-CAGZXMOS.js";
|
||||||
|
import {
|
||||||
|
useDisplay
|
||||||
|
} from "./chunk-N4LJHDYD.js";
|
||||||
|
import {
|
||||||
|
useBackButton,
|
||||||
|
useRouter
|
||||||
|
} from "./chunk-573D4TYC.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
useDisplay
|
|
||||||
} from "./chunk-N4LJHDYD.js";
|
|
||||||
import {
|
import {
|
||||||
useRtl
|
useRtl
|
||||||
} from "./chunk-ENTLCOCZ.js";
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
@@ -31,9 +34,6 @@ import {
|
|||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
useBackgroundColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -103,79 +103,8 @@ import {
|
|||||||
withDirectives
|
withDirectives
|
||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/directives/click-outside/index.js
|
|
||||||
function defaultConditional() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
function checkEvent(e, el, binding) {
|
|
||||||
if (!e || checkIsActive(e, binding) === false) return false;
|
|
||||||
const root = attachedRoot(el);
|
|
||||||
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot && root.host === e.target) return false;
|
|
||||||
const elements = (typeof binding.value === "object" && binding.value.include || (() => []))();
|
|
||||||
elements.push(el);
|
|
||||||
return !elements.some((el2) => el2?.contains(e.target));
|
|
||||||
}
|
|
||||||
function checkIsActive(e, binding) {
|
|
||||||
const isActive = typeof binding.value === "object" && binding.value.closeConditional || defaultConditional;
|
|
||||||
return isActive(e);
|
|
||||||
}
|
|
||||||
function directive(e, el, binding) {
|
|
||||||
const handler = typeof binding.value === "function" ? binding.value : binding.value.handler;
|
|
||||||
e.shadowTarget = e.target;
|
|
||||||
el._clickOutside.lastMousedownWasOutside && checkEvent(e, el, binding) && setTimeout(() => {
|
|
||||||
checkIsActive(e, binding) && handler && handler(e);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
function handleShadow(el, callback) {
|
|
||||||
const root = attachedRoot(el);
|
|
||||||
callback(document);
|
|
||||||
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot) {
|
|
||||||
callback(root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ClickOutside = {
|
|
||||||
// [data-app] may not be found
|
|
||||||
// if using bind, inserted makes
|
|
||||||
// sure that the root element is
|
|
||||||
// available, iOS does not support
|
|
||||||
// clicks on body
|
|
||||||
mounted(el, binding) {
|
|
||||||
const onClick = (e) => directive(e, el, binding);
|
|
||||||
const onMousedown = (e) => {
|
|
||||||
el._clickOutside.lastMousedownWasOutside = checkEvent(e, el, binding);
|
|
||||||
};
|
|
||||||
handleShadow(el, (app) => {
|
|
||||||
app.addEventListener("click", onClick, true);
|
|
||||||
app.addEventListener("mousedown", onMousedown, true);
|
|
||||||
});
|
|
||||||
if (!el._clickOutside) {
|
|
||||||
el._clickOutside = {
|
|
||||||
lastMousedownWasOutside: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
el._clickOutside[binding.instance.$.uid] = {
|
|
||||||
onClick,
|
|
||||||
onMousedown
|
|
||||||
};
|
|
||||||
},
|
|
||||||
beforeUnmount(el, binding) {
|
|
||||||
if (!el._clickOutside) return;
|
|
||||||
handleShadow(el, (app) => {
|
|
||||||
if (!app || !el._clickOutside?.[binding.instance.$.uid]) return;
|
|
||||||
const {
|
|
||||||
onClick,
|
|
||||||
onMousedown
|
|
||||||
} = el._clickOutside[binding.instance.$.uid];
|
|
||||||
app.removeEventListener("click", onClick, true);
|
|
||||||
app.removeEventListener("mousedown", onMousedown, true);
|
|
||||||
});
|
|
||||||
delete el._clickOutside[binding.instance.$.uid];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var click_outside_default = ClickOutside;
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VOverlay/VOverlay.js
|
// node_modules/vuetify/lib/components/VOverlay/VOverlay.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VOverlay/VOverlay.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VOverlay/VOverlay.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VOverlay/util/point.js
|
// node_modules/vuetify/lib/components/VOverlay/util/point.js
|
||||||
function elementToViewport(point, offset) {
|
function elementToViewport(point, offset) {
|
||||||
@@ -1111,6 +1040,77 @@ function useTeleport(target) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/directives/click-outside/index.js
|
||||||
|
function defaultConditional() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function checkEvent(e, el, binding) {
|
||||||
|
if (!e || checkIsActive(e, binding) === false) return false;
|
||||||
|
const root = attachedRoot(el);
|
||||||
|
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot && root.host === e.target) return false;
|
||||||
|
const elements = (typeof binding.value === "object" && binding.value.include || (() => []))();
|
||||||
|
elements.push(el);
|
||||||
|
return !elements.some((el2) => el2?.contains(e.target));
|
||||||
|
}
|
||||||
|
function checkIsActive(e, binding) {
|
||||||
|
const isActive = typeof binding.value === "object" && binding.value.closeConditional || defaultConditional;
|
||||||
|
return isActive(e);
|
||||||
|
}
|
||||||
|
function directive(e, el, binding) {
|
||||||
|
const handler = typeof binding.value === "function" ? binding.value : binding.value.handler;
|
||||||
|
e.shadowTarget = e.target;
|
||||||
|
el._clickOutside.lastMousedownWasOutside && checkEvent(e, el, binding) && setTimeout(() => {
|
||||||
|
checkIsActive(e, binding) && handler && handler(e);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
function handleShadow(el, callback) {
|
||||||
|
const root = attachedRoot(el);
|
||||||
|
callback(document);
|
||||||
|
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot) {
|
||||||
|
callback(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ClickOutside = {
|
||||||
|
// [data-app] may not be found
|
||||||
|
// if using bind, inserted makes
|
||||||
|
// sure that the root element is
|
||||||
|
// available, iOS does not support
|
||||||
|
// clicks on body
|
||||||
|
mounted(el, binding) {
|
||||||
|
const onClick = (e) => directive(e, el, binding);
|
||||||
|
const onMousedown = (e) => {
|
||||||
|
el._clickOutside.lastMousedownWasOutside = checkEvent(e, el, binding);
|
||||||
|
};
|
||||||
|
handleShadow(el, (app) => {
|
||||||
|
app.addEventListener("click", onClick, true);
|
||||||
|
app.addEventListener("mousedown", onMousedown, true);
|
||||||
|
});
|
||||||
|
if (!el._clickOutside) {
|
||||||
|
el._clickOutside = {
|
||||||
|
lastMousedownWasOutside: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
el._clickOutside[binding.instance.$.uid] = {
|
||||||
|
onClick,
|
||||||
|
onMousedown
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeUnmount(el, binding) {
|
||||||
|
if (!el._clickOutside) return;
|
||||||
|
handleShadow(el, (app) => {
|
||||||
|
if (!app || !el._clickOutside?.[binding.instance.$.uid]) return;
|
||||||
|
const {
|
||||||
|
onClick,
|
||||||
|
onMousedown
|
||||||
|
} = el._clickOutside[binding.instance.$.uid];
|
||||||
|
app.removeEventListener("click", onClick, true);
|
||||||
|
app.removeEventListener("mousedown", onMousedown, true);
|
||||||
|
});
|
||||||
|
delete el._clickOutside[binding.instance.$.uid];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var click_outside_default = ClickOutside;
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VOverlay/VOverlay.js
|
// node_modules/vuetify/lib/components/VOverlay/VOverlay.js
|
||||||
function Scrim(props) {
|
function Scrim(props) {
|
||||||
const {
|
const {
|
||||||
@@ -1412,10 +1412,10 @@ var VOverlay = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ClickOutside,
|
|
||||||
VMenuSymbol,
|
VMenuSymbol,
|
||||||
useHydration,
|
useHydration,
|
||||||
|
ClickOutside,
|
||||||
makeVOverlayProps,
|
makeVOverlayProps,
|
||||||
VOverlay
|
VOverlay
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-TS6Y6OVT.js.map
|
//# sourceMappingURL=chunk-CL6G3ICF.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+8
-8
@@ -1,3 +1,6 @@
|
|||||||
|
import {
|
||||||
|
breakpoints
|
||||||
|
} from "./chunk-N4LJHDYD.js";
|
||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
@@ -5,9 +8,6 @@ import {
|
|||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
breakpoints
|
|
||||||
} from "./chunk-N4LJHDYD.js";
|
|
||||||
import {
|
import {
|
||||||
useRtl
|
useRtl
|
||||||
} from "./chunk-ENTLCOCZ.js";
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VGrid/VContainer.js
|
// node_modules/vuetify/lib/components/VGrid/VContainer.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
||||||
var makeVContainerProps = propsFactory({
|
var makeVContainerProps = propsFactory({
|
||||||
fluid: {
|
fluid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -62,7 +62,7 @@ var VContainer = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VGrid/VCol.js
|
// node_modules/vuetify/lib/components/VGrid/VCol.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
||||||
var breakpointProps = (() => {
|
var breakpointProps = (() => {
|
||||||
return breakpoints.reduce((props, val) => {
|
return breakpoints.reduce((props, val) => {
|
||||||
props[val] = {
|
props[val] = {
|
||||||
@@ -176,7 +176,7 @@ var VCol = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VGrid/VRow.js
|
// node_modules/vuetify/lib/components/VGrid/VRow.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
||||||
var ALIGNMENT = ["start", "end", "center"];
|
var ALIGNMENT = ["start", "end", "center"];
|
||||||
var SPACE = ["space-between", "space-around", "space-evenly"];
|
var SPACE = ["space-between", "space-around", "space-evenly"];
|
||||||
function makeRowProps(prefix, def) {
|
function makeRowProps(prefix, def) {
|
||||||
@@ -287,7 +287,7 @@ var VRow = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VGrid/VSpacer.js
|
// node_modules/vuetify/lib/components/VGrid/VSpacer.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VGrid/VGrid.css";
|
||||||
var VSpacer = createSimpleFunctional("v-spacer", "div", "VSpacer");
|
var VSpacer = createSimpleFunctional("v-spacer", "div", "VSpacer");
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -296,4 +296,4 @@ export {
|
|||||||
VRow,
|
VRow,
|
||||||
VSpacer
|
VSpacer
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-UIXKNP5E.js.map
|
//# sourceMappingURL=chunk-CMTCIV56.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+2
-2
@@ -3,7 +3,7 @@ import {
|
|||||||
} from "./chunk-LZ5T2ERG.js";
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/directives/ripple/index.js
|
// node_modules/vuetify/lib/directives/ripple/index.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/directives/ripple/VRipple.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/directives/ripple/VRipple.css";
|
||||||
var stopSymbol = /* @__PURE__ */ Symbol("rippleStop");
|
var stopSymbol = /* @__PURE__ */ Symbol("rippleStop");
|
||||||
var DELAY_RIPPLE = 80;
|
var DELAY_RIPPLE = 80;
|
||||||
function transform(el, value) {
|
function transform(el, value) {
|
||||||
@@ -287,4 +287,4 @@ export {
|
|||||||
Ripple,
|
Ripple,
|
||||||
ripple_default
|
ripple_default
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-5BPWKCNA.js.map
|
//# sourceMappingURL=chunk-DS4J67XG.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+7
-7
@@ -1,16 +1,16 @@
|
|||||||
import {
|
|
||||||
VChip
|
|
||||||
} from "./chunk-SHBPUR7N.js";
|
|
||||||
import {
|
import {
|
||||||
VCounter,
|
VCounter,
|
||||||
VField,
|
VField,
|
||||||
makeVFieldProps
|
makeVFieldProps
|
||||||
} from "./chunk-J7Q2NMV7.js";
|
} from "./chunk-ITO3JWXJ.js";
|
||||||
|
import {
|
||||||
|
VChip
|
||||||
|
} from "./chunk-PLNIG6WP.js";
|
||||||
import {
|
import {
|
||||||
VInput,
|
VInput,
|
||||||
makeVInputProps,
|
makeVInputProps,
|
||||||
useFocus
|
useFocus
|
||||||
} from "./chunk-4VABFUPG.js";
|
} from "./chunk-IK67YVHF.js";
|
||||||
import {
|
import {
|
||||||
forwardRefs
|
forwardRefs
|
||||||
} from "./chunk-GUNW7S2L.js";
|
} from "./chunk-GUNW7S2L.js";
|
||||||
@@ -44,7 +44,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VFileInput/VFileInput.js
|
// node_modules/vuetify/lib/components/VFileInput/VFileInput.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VFileInput/VFileInput.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VFileInput/VFileInput.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/composables/fileDrop.js
|
// node_modules/vuetify/lib/composables/fileDrop.js
|
||||||
function useFileDrop() {
|
function useFileDrop() {
|
||||||
@@ -423,4 +423,4 @@ var VFileInput = genericComponent()({
|
|||||||
export {
|
export {
|
||||||
VFileInput
|
VFileInput
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-PEUMXYT2.js.map
|
//# sourceMappingURL=chunk-EKMPZMZF.js.map
|
||||||
Generated
Vendored
-7
File diff suppressed because one or more lines are too long
+116
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
VOverlay,
|
||||||
|
makeVOverlayProps
|
||||||
|
} from "./chunk-CL6G3ICF.js";
|
||||||
|
import {
|
||||||
|
useScopeId
|
||||||
|
} from "./chunk-BVUPSGRF.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
|
import {
|
||||||
|
genericComponent,
|
||||||
|
omit,
|
||||||
|
propsFactory,
|
||||||
|
useRender
|
||||||
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
createVNode,
|
||||||
|
mergeProps,
|
||||||
|
ref,
|
||||||
|
toRef,
|
||||||
|
useId
|
||||||
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VTooltip/VTooltip.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTooltip/VTooltip.css";
|
||||||
|
var makeVTooltipProps = propsFactory({
|
||||||
|
id: String,
|
||||||
|
interactive: Boolean,
|
||||||
|
text: String,
|
||||||
|
...omit(makeVOverlayProps({
|
||||||
|
closeOnBack: false,
|
||||||
|
location: "end",
|
||||||
|
locationStrategy: "connected",
|
||||||
|
eager: true,
|
||||||
|
minWidth: 0,
|
||||||
|
offset: 10,
|
||||||
|
openOnClick: false,
|
||||||
|
openOnHover: true,
|
||||||
|
origin: "auto",
|
||||||
|
scrim: false,
|
||||||
|
scrollStrategy: "reposition",
|
||||||
|
transition: null
|
||||||
|
}), ["absolute", "retainFocus", "captureFocus", "disableInitialFocus"])
|
||||||
|
}, "VTooltip");
|
||||||
|
var VTooltip = genericComponent()({
|
||||||
|
name: "VTooltip",
|
||||||
|
props: makeVTooltipProps(),
|
||||||
|
emits: {
|
||||||
|
"update:modelValue": (value) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const isActive = useProxiedModel(props, "modelValue");
|
||||||
|
const {
|
||||||
|
scopeId
|
||||||
|
} = useScopeId();
|
||||||
|
const uid = useId();
|
||||||
|
const id = toRef(() => props.id || `v-tooltip-${uid}`);
|
||||||
|
const overlay = ref();
|
||||||
|
const location = computed(() => {
|
||||||
|
return props.location.split(" ").length > 1 ? props.location : props.location + " center";
|
||||||
|
});
|
||||||
|
const origin = computed(() => {
|
||||||
|
return props.origin === "auto" || props.origin === "overlap" || props.origin.split(" ").length > 1 || props.location.split(" ").length > 1 ? props.origin : props.origin + " center";
|
||||||
|
});
|
||||||
|
const transition = toRef(() => {
|
||||||
|
if (props.transition != null) return props.transition;
|
||||||
|
return isActive.value ? "scale-transition" : "fade-transition";
|
||||||
|
});
|
||||||
|
const activatorProps = computed(() => mergeProps({
|
||||||
|
"aria-describedby": id.value
|
||||||
|
}, props.activatorProps));
|
||||||
|
useRender(() => {
|
||||||
|
const overlayProps = VOverlay.filterProps(props);
|
||||||
|
return createVNode(VOverlay, mergeProps({
|
||||||
|
"ref": overlay,
|
||||||
|
"class": ["v-tooltip", {
|
||||||
|
"v-tooltip--interactive": props.interactive
|
||||||
|
}, props.class],
|
||||||
|
"style": props.style,
|
||||||
|
"id": id.value
|
||||||
|
}, overlayProps, {
|
||||||
|
"modelValue": isActive.value,
|
||||||
|
"onUpdate:modelValue": ($event) => isActive.value = $event,
|
||||||
|
"transition": transition.value,
|
||||||
|
"absolute": true,
|
||||||
|
"location": location.value,
|
||||||
|
"origin": origin.value,
|
||||||
|
"role": "tooltip",
|
||||||
|
"activatorProps": activatorProps.value,
|
||||||
|
"_disableGlobalStack": true
|
||||||
|
}, scopeId), {
|
||||||
|
activator: slots.activator,
|
||||||
|
default: function() {
|
||||||
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
args[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
return slots.default?.(...args) ?? props.text;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return forwardRefs({}, overlay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
VTooltip
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=chunk-EXUNAYJA.js.map
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"sources": ["../../vuetify/src/components/VTooltip/VTooltip.tsx"],
|
||||||
|
"sourcesContent": ["// Styles\nimport './VTooltip.sass'\n\n// Components\nimport { VOverlay } from '@/components/VOverlay'\nimport { makeVOverlayProps } from '@/components/VOverlay/VOverlay'\n\n// Composables\nimport { forwardRefs } from '@/composables/forwardRefs'\nimport { useProxiedModel } from '@/composables/proxiedModel'\nimport { useScopeId } from '@/composables/scopeId'\n\n// Utilities\nimport { computed, mergeProps, ref, toRef, useId } from 'vue'\nimport { genericComponent, omit, propsFactory, useRender } from '@/util'\n\n// Types\nimport type { StrategyProps } from '@/components/VOverlay/locationStrategies'\nimport type { OverlaySlots } from '@/components/VOverlay/VOverlay'\n\nexport const makeVTooltipProps = propsFactory({\n id: String,\n interactive: Boolean,\n text: String,\n\n ...omit(makeVOverlayProps({\n closeOnBack: false,\n location: 'end' as const,\n locationStrategy: 'connected' as const,\n eager: true,\n minWidth: 0,\n offset: 10,\n openOnClick: false,\n openOnHover: true,\n origin: 'auto' as const,\n scrim: false,\n scrollStrategy: 'reposition' as const,\n transition: null,\n }), [\n 'absolute',\n 'retainFocus',\n 'captureFocus',\n 'disableInitialFocus',\n ]),\n}, 'VTooltip')\n\nexport const VTooltip = genericComponent<OverlaySlots>()({\n name: 'VTooltip',\n\n props: makeVTooltipProps(),\n\n emits: {\n 'update:modelValue': (value: boolean) => true,\n },\n\n setup (props, { slots }) {\n const isActive = useProxiedModel(props, 'modelValue')\n const { scopeId } = useScopeId()\n\n const uid = useId()\n const id = toRef(() => props.id || `v-tooltip-${uid}`)\n\n const overlay = ref<VOverlay>()\n\n const location = computed(() => {\n return props.location.split(' ').length > 1\n ? props.location\n : props.location + ' center' as StrategyProps['location']\n })\n\n const origin = computed(() => {\n return (\n props.origin === 'auto' ||\n props.origin === 'overlap' ||\n props.origin.split(' ').length > 1 ||\n props.location.split(' ').length > 1\n ) ? props.origin\n : props.origin + ' center' as StrategyProps['origin']\n })\n\n const transition = toRef(() => {\n if (props.transition != null) return props.transition\n return isActive.value ? 'scale-transition' : 'fade-transition'\n })\n\n const activatorProps = computed(() =>\n mergeProps({\n 'aria-describedby': id.value,\n }, props.activatorProps)\n )\n\n useRender(() => {\n const overlayProps = VOverlay.filterProps(props)\n\n return (\n <VOverlay\n ref={ overlay }\n class={[\n 'v-tooltip',\n { 'v-tooltip--interactive': props.interactive },\n props.class,\n ]}\n style={ props.style }\n id={ id.value }\n { ...overlayProps }\n v-model={ isActive.value }\n transition={ transition.value }\n absolute\n location={ location.value }\n origin={ origin.value }\n role=\"tooltip\"\n activatorProps={ activatorProps.value }\n _disableGlobalStack\n { ...scopeId }\n >\n {{\n activator: slots.activator,\n default: (...args) => slots.default?.(...args) ?? props.text,\n }}\n </VOverlay>\n )\n })\n\n return forwardRefs({}, overlay)\n },\n})\n\nexport type VTooltip = InstanceType<typeof VTooltip>\n"],
|
||||||
|
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,OAAA;AAmBO,IAAMA,oBAAoBC,aAAa;EAC5CC,IAAIC;EACJC,aAAaC;EACbC,MAAMH;EAEN,GAAGI,KAAKC,kBAAkB;IACxBC,aAAa;IACbC,UAAU;IACVC,kBAAkB;IAClBC,OAAO;IACPC,UAAU;IACVC,QAAQ;IACRC,aAAa;IACbC,aAAa;IACbC,QAAQ;IACRC,OAAO;IACPC,gBAAgB;IAChBC,YAAY;EACd,CAAC,GAAG,CACF,YACA,eACA,gBACA,qBAAqB,CACtB;AACH,GAAG,UAAU;AAEN,IAAMC,WAAWC,iBAA+B,EAAE;EACvDC,MAAM;EAENC,OAAOxB,kBAAkB;EAEzByB,OAAO;IACL,qBAAsBC,WAAmB;EAC3C;EAEAC,MAAOH,OAAKI,MAAa;AAAA,QAAX;MAAEC;IAAM,IAACD;AACrB,UAAME,WAAWC,gBAAgBP,OAAO,YAAY;AACpD,UAAM;MAAEQ;IAAQ,IAAIC,WAAW;AAE/B,UAAMC,MAAMC,MAAM;AAClB,UAAMjC,KAAKkC,MAAM,MAAMZ,MAAMtB,MAAM,aAAagC,GAAG,EAAE;AAErD,UAAMG,UAAUC,IAAc;AAE9B,UAAM5B,WAAW6B,SAAS,MAAM;AAC9B,aAAOf,MAAMd,SAAS8B,MAAM,GAAG,EAAEC,SAAS,IACtCjB,MAAMd,WACNc,MAAMd,WAAW;IACvB,CAAC;AAED,UAAMO,SAASsB,SAAS,MAAM;AAC5B,aACEf,MAAMP,WAAW,UACjBO,MAAMP,WAAW,aACjBO,MAAMP,OAAOuB,MAAM,GAAG,EAAEC,SAAS,KACjCjB,MAAMd,SAAS8B,MAAM,GAAG,EAAEC,SAAS,IACjCjB,MAAMP,SACNO,MAAMP,SAAS;IACrB,CAAC;AAED,UAAMG,aAAagB,MAAM,MAAM;AAC7B,UAAIZ,MAAMJ,cAAc,KAAM,QAAOI,MAAMJ;AAC3C,aAAOU,SAASJ,QAAQ,qBAAqB;IAC/C,CAAC;AAED,UAAMgB,iBAAiBH,SAAS,MAC9BI,WAAW;MACT,oBAAoBzC,GAAGwB;IACzB,GAAGF,MAAMkB,cAAc,CACzB;AAEAE,cAAU,MAAM;AACd,YAAMC,eAAeC,SAASC,YAAYvB,KAAK;AAE/C,aAAAwB,YAAAF,UAAAG,WAAA;QAAA,OAEUZ;QAAO,SACN,CACL,aACA;UAAE,0BAA0Bb,MAAMpB;QAAY,GAC9CoB,MAAM0B,KAAK;QACZ,SACO1B,MAAM2B;QAAK,MACdjD,GAAGwB;MAAK,GACRmB,cAAY;QAAA,cACPf,SAASJ;QAAK,uBAAA0B,YAAdtB,SAASJ,QAAK0B;QAAA,cACXhC,WAAWM;QAAK,YAAA;QAAA,YAElBhB,SAASgB;QAAK,UAChBT,OAAOS;QAAK,QAAA;QAAA,kBAEJgB,eAAehB;QAAK,uBAAA;MAAA,GAEhCM,OAAO,GAAA;QAGVqB,WAAWxB,MAAMwB;QACjBC,SAAS,WAAA;AAAA,mBAAAC,OAAAC,UAAAf,QAAIgB,OAAI,IAAAC,MAAAH,IAAA,GAAAI,OAAA,GAAAA,OAAAJ,MAAAI,QAAA;AAAJF,iBAAIE,IAAA,IAAAH,UAAAG,IAAA;UAAA;AAAA,iBAAK9B,MAAMyB,UAAU,GAAGG,IAAI,KAAKjC,MAAMlB;QAAI;MAAA,CAAA;IAIpE,CAAC;AAED,WAAOsD,YAAY,CAAC,GAAGvB,OAAO;EAChC;AACF,CAAC;",
|
||||||
|
"names": ["makeVTooltipProps", "propsFactory", "id", "String", "interactive", "Boolean", "text", "omit", "makeVOverlayProps", "closeOnBack", "location", "locationStrategy", "eager", "minWidth", "offset", "openOnClick", "openOnHover", "origin", "scrim", "scrollStrategy", "transition", "VTooltip", "genericComponent", "name", "props", "emits", "value", "setup", "_ref", "slots", "isActive", "useProxiedModel", "scopeId", "useScopeId", "uid", "useId", "toRef", "overlay", "ref", "computed", "split", "length", "activatorProps", "mergeProps", "useRender", "overlayProps", "VOverlay", "filterProps", "_createVNode", "_mergeProps", "class", "style", "$event", "activator", "default", "_len", "arguments", "args", "Array", "_key", "forwardRefs"]
|
||||||
|
}
|
||||||
Generated
Vendored
+25
-179
@@ -1,18 +1,29 @@
|
|||||||
|
import {
|
||||||
|
makeFilterProps,
|
||||||
|
useFilter
|
||||||
|
} from "./chunk-RB6DAYHX.js";
|
||||||
import {
|
import {
|
||||||
VSelect,
|
VSelect,
|
||||||
VVirtualScrollItem,
|
VVirtualScrollItem,
|
||||||
makeVirtualProps,
|
makeVirtualProps,
|
||||||
useVirtual
|
useVirtual
|
||||||
} from "./chunk-ERI3RTQS.js";
|
} from "./chunk-IZDIYS2S.js";
|
||||||
import {
|
import {
|
||||||
VCheckboxBtn
|
VCheckboxBtn
|
||||||
} from "./chunk-BYBKODOB.js";
|
} from "./chunk-6HLLVLOR.js";
|
||||||
import {
|
import {
|
||||||
VChip
|
VChip
|
||||||
} from "./chunk-SHBPUR7N.js";
|
} from "./chunk-PLNIG6WP.js";
|
||||||
|
import {
|
||||||
|
VDivider
|
||||||
|
} from "./chunk-X3WY3J75.js";
|
||||||
|
import {
|
||||||
|
makeDisplayProps,
|
||||||
|
useDisplay
|
||||||
|
} from "./chunk-N4LJHDYD.js";
|
||||||
import {
|
import {
|
||||||
VBtn
|
VBtn
|
||||||
} from "./chunk-QC7VWQER.js";
|
} from "./chunk-UQALH6SL.js";
|
||||||
import {
|
import {
|
||||||
makeVariantProps
|
makeVariantProps
|
||||||
} from "./chunk-ADXDXPPR.js";
|
} from "./chunk-ADXDXPPR.js";
|
||||||
@@ -26,14 +37,14 @@ import {
|
|||||||
LoaderSlot,
|
LoaderSlot,
|
||||||
makeLoaderProps,
|
makeLoaderProps,
|
||||||
useLoader
|
useLoader
|
||||||
} from "./chunk-U2DSRSME.js";
|
} from "./chunk-UXZ55AO7.js";
|
||||||
import {
|
import {
|
||||||
makeDensityProps,
|
makeDensityProps,
|
||||||
useDensity
|
useDensity
|
||||||
} from "./chunk-TM3MIDYC.js";
|
} from "./chunk-TM3MIDYC.js";
|
||||||
import {
|
import {
|
||||||
VIcon
|
VIcon
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
} from "./chunk-4XZHKYGH.js";
|
||||||
import {
|
import {
|
||||||
makeSizeProps
|
makeSizeProps
|
||||||
} from "./chunk-PIT7IT55.js";
|
} from "./chunk-PIT7IT55.js";
|
||||||
@@ -43,13 +54,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
import {
|
|
||||||
makeDisplayProps,
|
|
||||||
useDisplay
|
|
||||||
} from "./chunk-N4LJHDYD.js";
|
|
||||||
import {
|
import {
|
||||||
IconValue
|
IconValue
|
||||||
} from "./chunk-OJDZBU5D.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
useResizeObserver
|
useResizeObserver
|
||||||
} from "./chunk-XDSAO7OT.js";
|
} from "./chunk-XDSAO7OT.js";
|
||||||
@@ -60,12 +70,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
useProxiedModel
|
useProxiedModel
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
VDivider
|
|
||||||
} from "./chunk-WXMECWZN.js";
|
|
||||||
import {
|
|
||||||
useBackgroundColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -116,20 +120,19 @@ import {
|
|||||||
toRef,
|
toRef,
|
||||||
toRefs,
|
toRefs,
|
||||||
toValue,
|
toValue,
|
||||||
unref,
|
|
||||||
watch,
|
watch,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
withModifiers
|
withModifiers
|
||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VDataTable/VDataTable.js
|
// node_modules/vuetify/lib/components/VDataTable/VDataTable.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VDataTable/VDataTable.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VDataTable/VDataTable.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VDataTable/VDataTableFooter.js
|
// node_modules/vuetify/lib/components/VDataTable/VDataTableFooter.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VDataTable/VDataTableFooter.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VDataTable/VDataTableFooter.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VPagination/VPagination.js
|
// node_modules/vuetify/lib/components/VPagination/VPagination.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VPagination/VPagination.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VPagination/VPagination.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/composables/refs.js
|
// node_modules/vuetify/lib/composables/refs.js
|
||||||
function useRefs() {
|
function useRefs() {
|
||||||
@@ -2243,7 +2246,7 @@ var VDataTableRows = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VTable/VTable.js
|
// node_modules/vuetify/lib/components/VTable/VTable.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTable/VTable.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VTable/VTable.css";
|
||||||
var makeVTableProps = propsFactory({
|
var makeVTableProps = propsFactory({
|
||||||
fixedHeader: Boolean,
|
fixedHeader: Boolean,
|
||||||
fixedFooter: Boolean,
|
fixedFooter: Boolean,
|
||||||
@@ -2373,160 +2376,6 @@ function useOptions(_ref) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// node_modules/vuetify/lib/composables/filter.js
|
|
||||||
var defaultFilter = (value, query, item) => {
|
|
||||||
if (value == null || query == null) return -1;
|
|
||||||
if (!query.length) return 0;
|
|
||||||
value = value.toString().toLocaleLowerCase();
|
|
||||||
query = query.toString().toLocaleLowerCase();
|
|
||||||
const result = [];
|
|
||||||
let idx = value.indexOf(query);
|
|
||||||
while (~idx) {
|
|
||||||
result.push([idx, idx + query.length]);
|
|
||||||
idx = value.indexOf(query, idx + query.length);
|
|
||||||
}
|
|
||||||
return result.length ? result : -1;
|
|
||||||
};
|
|
||||||
function normaliseMatch(match, query) {
|
|
||||||
if (match == null || typeof match === "boolean" || match === -1) return;
|
|
||||||
if (typeof match === "number") return [[match, match + query.length]];
|
|
||||||
if (Array.isArray(match[0])) return match;
|
|
||||||
return [match];
|
|
||||||
}
|
|
||||||
var makeFilterProps = propsFactory({
|
|
||||||
customFilter: Function,
|
|
||||||
customKeyFilter: Object,
|
|
||||||
filterKeys: [Array, String],
|
|
||||||
filterMode: {
|
|
||||||
type: String,
|
|
||||||
default: "intersection"
|
|
||||||
},
|
|
||||||
noFilter: Boolean
|
|
||||||
}, "filter");
|
|
||||||
function filterItems(items, query, options) {
|
|
||||||
const array = [];
|
|
||||||
const filter = options?.default ?? defaultFilter;
|
|
||||||
const keys = options?.filterKeys ? wrapInArray(options.filterKeys) : false;
|
|
||||||
const customFiltersLength = Object.keys(options?.customKeyFilter ?? {}).length;
|
|
||||||
if (!items?.length) return array;
|
|
||||||
let lookAheadItem = null;
|
|
||||||
loop: for (let i = 0; i < items.length; i++) {
|
|
||||||
const [item, transformed = item] = wrapInArray(items[i]);
|
|
||||||
const customMatches = {};
|
|
||||||
const defaultMatches = {};
|
|
||||||
let match = -1;
|
|
||||||
if ((query || customFiltersLength > 0) && !options?.noFilter) {
|
|
||||||
let hasOnlyCustomFilters = false;
|
|
||||||
if (typeof item === "object") {
|
|
||||||
if (item.type === "divider" || item.type === "subheader") {
|
|
||||||
if (lookAheadItem?.type === "divider" && item.type === "subheader") {
|
|
||||||
array.push(lookAheadItem);
|
|
||||||
}
|
|
||||||
lookAheadItem = {
|
|
||||||
index: i,
|
|
||||||
matches: {},
|
|
||||||
type: item.type
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const filterKeys = keys || Object.keys(transformed);
|
|
||||||
hasOnlyCustomFilters = filterKeys.length === customFiltersLength;
|
|
||||||
for (const key of filterKeys) {
|
|
||||||
const value = getPropertyFromItem(transformed, key);
|
|
||||||
const keyFilter = options?.customKeyFilter?.[key];
|
|
||||||
match = keyFilter ? keyFilter(value, query, item) : filter(value, query, item);
|
|
||||||
if (match !== -1 && match !== false) {
|
|
||||||
if (keyFilter) customMatches[key] = normaliseMatch(match, query);
|
|
||||||
else defaultMatches[key] = normaliseMatch(match, query);
|
|
||||||
} else if (options?.filterMode === "every") {
|
|
||||||
continue loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match = filter(item, query, item);
|
|
||||||
if (match !== -1 && match !== false) {
|
|
||||||
defaultMatches.title = normaliseMatch(match, query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const defaultMatchesLength = Object.keys(defaultMatches).length;
|
|
||||||
const customMatchesLength = Object.keys(customMatches).length;
|
|
||||||
if (!defaultMatchesLength && !customMatchesLength) continue;
|
|
||||||
if (options?.filterMode === "union" && customMatchesLength !== customFiltersLength && !defaultMatchesLength) continue;
|
|
||||||
if (options?.filterMode === "intersection" && (customMatchesLength !== customFiltersLength || !defaultMatchesLength && customFiltersLength > 0 && !hasOnlyCustomFilters)) continue;
|
|
||||||
}
|
|
||||||
if (lookAheadItem) {
|
|
||||||
array.push(lookAheadItem);
|
|
||||||
lookAheadItem = null;
|
|
||||||
}
|
|
||||||
array.push({
|
|
||||||
index: i,
|
|
||||||
matches: {
|
|
||||||
...defaultMatches,
|
|
||||||
...customMatches
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
function useFilter(props, items, query, options) {
|
|
||||||
const filteredItems = shallowRef([]);
|
|
||||||
const filteredMatches = shallowRef(/* @__PURE__ */ new Map());
|
|
||||||
const transformedItems = computed(() => options?.transform ? unref(items).map((item) => [item, options.transform(item)]) : unref(items));
|
|
||||||
watchEffect(() => {
|
|
||||||
const _query = typeof query === "function" ? query() : unref(query);
|
|
||||||
const strQuery = typeof _query !== "string" && typeof _query !== "number" ? "" : String(_query);
|
|
||||||
const results = filterItems(transformedItems.value, strQuery, {
|
|
||||||
customKeyFilter: {
|
|
||||||
...props.customKeyFilter,
|
|
||||||
...unref(options?.customKeyFilter)
|
|
||||||
},
|
|
||||||
default: props.customFilter,
|
|
||||||
filterKeys: props.filterKeys,
|
|
||||||
filterMode: props.filterMode,
|
|
||||||
noFilter: props.noFilter
|
|
||||||
});
|
|
||||||
const originalItems = unref(items);
|
|
||||||
const _filteredItems = [];
|
|
||||||
const _filteredMatches = /* @__PURE__ */ new Map();
|
|
||||||
results.forEach((_ref) => {
|
|
||||||
let {
|
|
||||||
index,
|
|
||||||
matches
|
|
||||||
} = _ref;
|
|
||||||
const item = originalItems[index];
|
|
||||||
_filteredItems.push(item);
|
|
||||||
_filteredMatches.set(item.value, matches);
|
|
||||||
});
|
|
||||||
filteredItems.value = _filteredItems;
|
|
||||||
filteredMatches.value = _filteredMatches;
|
|
||||||
});
|
|
||||||
function getMatches(item) {
|
|
||||||
return filteredMatches.value.get(item.value);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
filteredItems,
|
|
||||||
filteredMatches,
|
|
||||||
getMatches
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function highlightResult(name, text, matches) {
|
|
||||||
if (matches == null || !matches.length) return text;
|
|
||||||
return matches.map((match, i) => {
|
|
||||||
const start = i === 0 ? 0 : matches[i - 1][1];
|
|
||||||
const result = [createBaseVNode("span", {
|
|
||||||
"class": normalizeClass(`${name}__unmask`)
|
|
||||||
}, [text.slice(start, match[0])]), createBaseVNode("span", {
|
|
||||||
"class": normalizeClass(`${name}__mask`)
|
|
||||||
}, [text.slice(match[0], match[1])])];
|
|
||||||
if (i === matches.length - 1) {
|
|
||||||
result.push(createBaseVNode("span", {
|
|
||||||
"class": normalizeClass(`${name}__unmask`)
|
|
||||||
}, [text.slice(match[1])]));
|
|
||||||
}
|
|
||||||
return createBaseVNode(Fragment, null, [result]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VDataTable/VDataTable.js
|
// node_modules/vuetify/lib/components/VDataTable/VDataTable.js
|
||||||
var makeDataTableProps = propsFactory({
|
var makeDataTableProps = propsFactory({
|
||||||
...makeVDataTableRowsProps(),
|
...makeVDataTableRowsProps(),
|
||||||
@@ -3157,9 +3006,6 @@ var VDataTableServer = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
makeFilterProps,
|
|
||||||
useFilter,
|
|
||||||
highlightResult,
|
|
||||||
makeDataTableExpandProps,
|
makeDataTableExpandProps,
|
||||||
provideExpanded,
|
provideExpanded,
|
||||||
makeDataTableGroupProps,
|
makeDataTableGroupProps,
|
||||||
@@ -3186,4 +3032,4 @@ export {
|
|||||||
VDataTableVirtual,
|
VDataTableVirtual,
|
||||||
VDataTableServer
|
VDataTableServer
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-4HCXUCLO.js.map
|
//# sourceMappingURL=chunk-FCTFWI5H.js.map
|
||||||
Generated
Vendored
+4
-4
File diff suppressed because one or more lines are too long
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"sources": [],
|
||||||
|
"sourcesContent": [],
|
||||||
|
"mappings": "",
|
||||||
|
"names": []
|
||||||
|
}
|
||||||
Generated
Vendored
+12
-12
@@ -1,6 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
VImg
|
VImg
|
||||||
} from "./chunk-6CRS5HI4.js";
|
} from "./chunk-TTIGZHWR.js";
|
||||||
|
import {
|
||||||
|
VExpandTransition
|
||||||
|
} from "./chunk-B4JFJDWM.js";
|
||||||
import {
|
import {
|
||||||
makeBorderProps,
|
makeBorderProps,
|
||||||
useBorder
|
useBorder
|
||||||
@@ -9,25 +12,22 @@ import {
|
|||||||
makeElevationProps,
|
makeElevationProps,
|
||||||
useElevation
|
useElevation
|
||||||
} from "./chunk-YJD2PNP6.js";
|
} from "./chunk-YJD2PNP6.js";
|
||||||
import {
|
|
||||||
VExpandTransition
|
|
||||||
} from "./chunk-B4JFJDWM.js";
|
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
makeRoundedProps,
|
makeRoundedProps,
|
||||||
useRounded
|
useRounded
|
||||||
} from "./chunk-AQK3CYVW.js";
|
} from "./chunk-AQK3CYVW.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
import {
|
|
||||||
useRtl
|
|
||||||
} from "./chunk-ENTLCOCZ.js";
|
|
||||||
import {
|
import {
|
||||||
useBackgroundColor
|
useBackgroundColor
|
||||||
} from "./chunk-BHV5Z34M.js";
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useRtl
|
||||||
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -78,7 +78,7 @@ var VToolbarTitle = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VToolbar/VToolbar.js
|
// node_modules/vuetify/lib/components/VToolbar/VToolbar.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VToolbar/VToolbar.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VToolbar/VToolbar.css";
|
||||||
var allowedDensities = [null, "prominent", "default", "comfortable", "compact"];
|
var allowedDensities = [null, "prominent", "default", "comfortable", "compact"];
|
||||||
var makeVToolbarProps = propsFactory({
|
var makeVToolbarProps = propsFactory({
|
||||||
absolute: Boolean,
|
absolute: Boolean,
|
||||||
@@ -236,4 +236,4 @@ export {
|
|||||||
makeVToolbarProps,
|
makeVToolbarProps,
|
||||||
VToolbar
|
VToolbar
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-LPZZJ62X.js.map
|
//# sourceMappingURL=chunk-H2QFYTJQ.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+42
-42
@@ -4,24 +4,27 @@ import {
|
|||||||
import {
|
import {
|
||||||
VSlideYTransition
|
VSlideYTransition
|
||||||
} from "./chunk-B4JFJDWM.js";
|
} from "./chunk-B4JFJDWM.js";
|
||||||
|
import {
|
||||||
|
MaybeTransition,
|
||||||
|
makeTransitionProps
|
||||||
|
} from "./chunk-CAGZXMOS.js";
|
||||||
import {
|
import {
|
||||||
makeDensityProps,
|
makeDensityProps,
|
||||||
useDensity
|
useDensity
|
||||||
} from "./chunk-TM3MIDYC.js";
|
} from "./chunk-TM3MIDYC.js";
|
||||||
import {
|
import {
|
||||||
VIcon
|
VIcon
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
} from "./chunk-4XZHKYGH.js";
|
||||||
import {
|
import {
|
||||||
MaybeTransition,
|
IconValue
|
||||||
makeTransitionProps
|
} from "./chunk-OJDZBU5D.js";
|
||||||
} from "./chunk-CAGZXMOS.js";
|
import {
|
||||||
|
useTextColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
IconValue
|
|
||||||
} from "./chunk-OJDZBU5D.js";
|
|
||||||
import {
|
import {
|
||||||
useLocale,
|
useLocale,
|
||||||
useRtl
|
useRtl
|
||||||
@@ -30,9 +33,6 @@ import {
|
|||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
useTextColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -69,34 +69,8 @@ import {
|
|||||||
watch
|
watch
|
||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VLabel/VLabel.js
|
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VLabel/VLabel.css";
|
|
||||||
var makeVLabelProps = propsFactory({
|
|
||||||
text: String,
|
|
||||||
onClick: EventProp(),
|
|
||||||
...makeComponentProps(),
|
|
||||||
...makeThemeProps()
|
|
||||||
}, "VLabel");
|
|
||||||
var VLabel = genericComponent()({
|
|
||||||
name: "VLabel",
|
|
||||||
props: makeVLabelProps(),
|
|
||||||
setup(props, _ref) {
|
|
||||||
let {
|
|
||||||
slots
|
|
||||||
} = _ref;
|
|
||||||
useRender(() => createBaseVNode("label", {
|
|
||||||
"class": normalizeClass(["v-label", {
|
|
||||||
"v-label--clickable": !!props.onClick
|
|
||||||
}, props.class]),
|
|
||||||
"style": normalizeStyle(props.style),
|
|
||||||
"onClick": props.onClick
|
|
||||||
}, [props.text, slots.default?.()]));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VMessages/VMessages.js
|
// node_modules/vuetify/lib/components/VMessages/VMessages.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VMessages/VMessages.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VMessages/VMessages.css";
|
||||||
var makeVMessagesProps = propsFactory({
|
var makeVMessagesProps = propsFactory({
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
color: String,
|
color: String,
|
||||||
@@ -143,7 +117,7 @@ var VMessages = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VInput/VInput.js
|
// node_modules/vuetify/lib/components/VInput/VInput.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VInput/VInput.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VInput/VInput.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VInput/InputIcon.js
|
// node_modules/vuetify/lib/components/VInput/InputIcon.js
|
||||||
function useInputIcon(props) {
|
function useInputIcon(props) {
|
||||||
@@ -548,15 +522,41 @@ var VInput = genericComponent()({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VLabel/VLabel.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VLabel/VLabel.css";
|
||||||
|
var makeVLabelProps = propsFactory({
|
||||||
|
text: String,
|
||||||
|
onClick: EventProp(),
|
||||||
|
...makeComponentProps(),
|
||||||
|
...makeThemeProps()
|
||||||
|
}, "VLabel");
|
||||||
|
var VLabel = genericComponent()({
|
||||||
|
name: "VLabel",
|
||||||
|
props: makeVLabelProps(),
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
useRender(() => createBaseVNode("label", {
|
||||||
|
"class": normalizeClass(["v-label", {
|
||||||
|
"v-label--clickable": !!props.onClick
|
||||||
|
}, props.class]),
|
||||||
|
"style": normalizeStyle(props.style),
|
||||||
|
"onClick": props.onClick
|
||||||
|
}, [props.text, slots.default?.()]));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
VLabel,
|
|
||||||
useInputIcon,
|
useInputIcon,
|
||||||
|
VMessages,
|
||||||
makeFocusProps,
|
makeFocusProps,
|
||||||
useFocus,
|
useFocus,
|
||||||
VMessages,
|
|
||||||
makeValidationProps,
|
makeValidationProps,
|
||||||
useValidation,
|
useValidation,
|
||||||
makeVInputProps,
|
makeVInputProps,
|
||||||
VInput
|
VInput,
|
||||||
|
VLabel
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-4VABFUPG.js.map
|
//# sourceMappingURL=chunk-IK67YVHF.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
-14
@@ -3,37 +3,37 @@ import {
|
|||||||
makeFocusProps,
|
makeFocusProps,
|
||||||
useFocus,
|
useFocus,
|
||||||
useInputIcon
|
useInputIcon
|
||||||
} from "./chunk-4VABFUPG.js";
|
} from "./chunk-IK67YVHF.js";
|
||||||
import {
|
import {
|
||||||
VExpandXTransition,
|
VExpandXTransition,
|
||||||
VSlideYTransition
|
VSlideYTransition
|
||||||
} from "./chunk-B4JFJDWM.js";
|
} from "./chunk-B4JFJDWM.js";
|
||||||
|
import {
|
||||||
|
MaybeTransition,
|
||||||
|
makeTransitionProps
|
||||||
|
} from "./chunk-CAGZXMOS.js";
|
||||||
import {
|
import {
|
||||||
LoaderSlot,
|
LoaderSlot,
|
||||||
makeLoaderProps,
|
makeLoaderProps,
|
||||||
useLoader
|
useLoader
|
||||||
} from "./chunk-U2DSRSME.js";
|
} from "./chunk-UXZ55AO7.js";
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
makeRoundedProps,
|
makeRoundedProps,
|
||||||
useRounded
|
useRounded
|
||||||
} from "./chunk-AQK3CYVW.js";
|
} from "./chunk-AQK3CYVW.js";
|
||||||
import {
|
import {
|
||||||
MaybeTransition,
|
VDefaultsProvider
|
||||||
makeTransitionProps
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
} from "./chunk-CAGZXMOS.js";
|
|
||||||
import {
|
import {
|
||||||
IconValue
|
IconValue
|
||||||
} from "./chunk-OJDZBU5D.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
import {
|
|
||||||
useRtl
|
|
||||||
} from "./chunk-ENTLCOCZ.js";
|
|
||||||
import {
|
import {
|
||||||
useBackgroundColor,
|
useBackgroundColor,
|
||||||
useTextColor
|
useTextColor
|
||||||
} from "./chunk-BHV5Z34M.js";
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useRtl
|
||||||
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -67,7 +67,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCounter/VCounter.js
|
// node_modules/vuetify/lib/components/VCounter/VCounter.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCounter/VCounter.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCounter/VCounter.css";
|
||||||
var makeVCounterProps = propsFactory({
|
var makeVCounterProps = propsFactory({
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
@@ -135,7 +135,7 @@ var VFieldLabel = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VField/VField.js
|
// node_modules/vuetify/lib/components/VField/VField.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VField/VField.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VField/VField.css";
|
||||||
var allowedVariants = ["underlined", "outlined", "filled", "solo", "solo-inverted", "solo-filled", "plain"];
|
var allowedVariants = ["underlined", "outlined", "filled", "solo", "solo-inverted", "solo-filled", "plain"];
|
||||||
var makeVFieldProps = propsFactory({
|
var makeVFieldProps = propsFactory({
|
||||||
appendInnerIcon: IconValue,
|
appendInnerIcon: IconValue,
|
||||||
@@ -452,4 +452,4 @@ export {
|
|||||||
makeVFieldProps,
|
makeVFieldProps,
|
||||||
VField
|
VField
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-J7Q2NMV7.js.map
|
//# sourceMappingURL=chunk-ITO3JWXJ.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+27
-27
@@ -1,60 +1,63 @@
|
|||||||
import {
|
import {
|
||||||
VTextField,
|
VTextField,
|
||||||
makeVTextFieldProps
|
makeVTextFieldProps
|
||||||
} from "./chunk-VSEC5QGQ.js";
|
} from "./chunk-6MO6MKTY.js";
|
||||||
|
import {
|
||||||
|
useAutocomplete
|
||||||
|
} from "./chunk-IZQZFEAL.js";
|
||||||
import {
|
import {
|
||||||
VCheckboxBtn
|
VCheckboxBtn
|
||||||
} from "./chunk-BYBKODOB.js";
|
} from "./chunk-6HLLVLOR.js";
|
||||||
import {
|
import {
|
||||||
VChip
|
VChip
|
||||||
} from "./chunk-SHBPUR7N.js";
|
} from "./chunk-PLNIG6WP.js";
|
||||||
import {
|
import {
|
||||||
VMenu
|
VMenu
|
||||||
} from "./chunk-HGIC64KW.js";
|
} from "./chunk-LPDOFVDK.js";
|
||||||
import {
|
import {
|
||||||
VList,
|
VList,
|
||||||
VListItem,
|
VListItem,
|
||||||
VListSubheader,
|
VListSubheader,
|
||||||
makeItemsProps,
|
makeItemsProps,
|
||||||
useItems
|
useItems
|
||||||
} from "./chunk-HV7P7R7J.js";
|
} from "./chunk-MQSMF5CI.js";
|
||||||
import {
|
import {
|
||||||
VAvatar
|
VAvatar
|
||||||
} from "./chunk-Z3677HMS.js";
|
} from "./chunk-SXI5CS7S.js";
|
||||||
import {
|
import {
|
||||||
useAutocomplete
|
VDivider
|
||||||
} from "./chunk-IZQZFEAL.js";
|
} from "./chunk-X3WY3J75.js";
|
||||||
import {
|
import {
|
||||||
useInputIcon
|
useInputIcon
|
||||||
} from "./chunk-4VABFUPG.js";
|
} from "./chunk-IK67YVHF.js";
|
||||||
import {
|
import {
|
||||||
useForm
|
useForm
|
||||||
} from "./chunk-GAYXO4FM.js";
|
} from "./chunk-GAYXO4FM.js";
|
||||||
import {
|
import {
|
||||||
VDialogTransition
|
VDialogTransition
|
||||||
} from "./chunk-B4JFJDWM.js";
|
} from "./chunk-B4JFJDWM.js";
|
||||||
import {
|
|
||||||
VIcon
|
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
forwardRefs
|
forwardRefs
|
||||||
} from "./chunk-GUNW7S2L.js";
|
} from "./chunk-GUNW7S2L.js";
|
||||||
import {
|
import {
|
||||||
makeTransitionProps
|
makeTransitionProps
|
||||||
} from "./chunk-CAGZXMOS.js";
|
} from "./chunk-CAGZXMOS.js";
|
||||||
import {
|
|
||||||
makeDimensionProps,
|
|
||||||
useDimension
|
|
||||||
} from "./chunk-6EZKHCRW.js";
|
|
||||||
import {
|
import {
|
||||||
useDisplay
|
useDisplay
|
||||||
} from "./chunk-N4LJHDYD.js";
|
} from "./chunk-N4LJHDYD.js";
|
||||||
|
import {
|
||||||
|
VIcon
|
||||||
|
} from "./chunk-4XZHKYGH.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
import {
|
import {
|
||||||
IconValue
|
IconValue
|
||||||
} from "./chunk-OJDZBU5D.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
|
import {
|
||||||
|
makeDimensionProps,
|
||||||
|
useDimension
|
||||||
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
import {
|
||||||
useResizeObserver
|
useResizeObserver
|
||||||
} from "./chunk-XDSAO7OT.js";
|
} from "./chunk-XDSAO7OT.js";
|
||||||
@@ -65,9 +68,6 @@ import {
|
|||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
VDivider
|
|
||||||
} from "./chunk-WXMECWZN.js";
|
|
||||||
import {
|
import {
|
||||||
IN_BROWSER,
|
IN_BROWSER,
|
||||||
camelizeProps,
|
camelizeProps,
|
||||||
@@ -109,11 +109,8 @@ import {
|
|||||||
watchEffect
|
watchEffect
|
||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSelect/VSelect.js
|
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelect/VSelect.css";
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScroll.js
|
// node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScroll.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScroll.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScroll.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScrollItem.js
|
// node_modules/vuetify/lib/components/VVirtualScroll/VVirtualScrollItem.js
|
||||||
var makeVVirtualScrollItemProps = propsFactory({
|
var makeVVirtualScrollItemProps = propsFactory({
|
||||||
@@ -494,6 +491,9 @@ var VVirtualScroll = genericComponent()({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VSelect/VSelect.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelect/VSelect.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSelect/useScrolling.js
|
// node_modules/vuetify/lib/components/VSelect/useScrolling.js
|
||||||
function useScrolling(listRef, textFieldRef) {
|
function useScrolling(listRef, textFieldRef) {
|
||||||
const isScrolling = shallowRef(false);
|
const isScrolling = shallowRef(false);
|
||||||
@@ -1079,4 +1079,4 @@ export {
|
|||||||
makeSelectProps,
|
makeSelectProps,
|
||||||
VSelect
|
VSelect
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-ERI3RTQS.js.map
|
//# sourceMappingURL=chunk-IZDIYS2S.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+12
-147
@@ -1,32 +1,26 @@
|
|||||||
import {
|
import {
|
||||||
VInput,
|
VLabel
|
||||||
VLabel,
|
} from "./chunk-IK67YVHF.js";
|
||||||
makeVInputProps,
|
|
||||||
useFocus
|
|
||||||
} from "./chunk-4VABFUPG.js";
|
|
||||||
import {
|
import {
|
||||||
makeDensityProps,
|
makeDensityProps,
|
||||||
useDensity
|
useDensity
|
||||||
} from "./chunk-TM3MIDYC.js";
|
} from "./chunk-TM3MIDYC.js";
|
||||||
import {
|
import {
|
||||||
VIcon
|
VIcon
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
} from "./chunk-4XZHKYGH.js";
|
||||||
import {
|
|
||||||
ripple_default
|
|
||||||
} from "./chunk-5BPWKCNA.js";
|
|
||||||
import {
|
|
||||||
forwardRefs
|
|
||||||
} from "./chunk-GUNW7S2L.js";
|
|
||||||
import {
|
import {
|
||||||
IconValue
|
IconValue
|
||||||
} from "./chunk-OJDZBU5D.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
import {
|
import {
|
||||||
useProxiedModel
|
ripple_default
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-DS4J67XG.js";
|
||||||
import {
|
import {
|
||||||
useBackgroundColor,
|
useBackgroundColor,
|
||||||
useTextColor
|
useTextColor
|
||||||
} from "./chunk-BHV5Z34M.js";
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
import {
|
||||||
makeThemeProps
|
makeThemeProps
|
||||||
} from "./chunk-3DSR7NCB.js";
|
} from "./chunk-3DSR7NCB.js";
|
||||||
@@ -36,7 +30,6 @@ import {
|
|||||||
genericComponent,
|
genericComponent,
|
||||||
makeComponentProps,
|
makeComponentProps,
|
||||||
matchesSelector,
|
matchesSelector,
|
||||||
omit,
|
|
||||||
propsFactory,
|
propsFactory,
|
||||||
provideDefaults,
|
provideDefaults,
|
||||||
useRender,
|
useRender,
|
||||||
@@ -61,14 +54,8 @@ import {
|
|||||||
withDirectives
|
withDirectives
|
||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCheckbox/VCheckbox.js
|
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VCheckbox/VCheckbox.css";
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSelectionControl/VSelectionControl.js
|
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelectionControl/VSelectionControl.css";
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSelectionControlGroup/VSelectionControlGroup.js
|
// node_modules/vuetify/lib/components/VSelectionControlGroup/VSelectionControlGroup.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelectionControlGroup/VSelectionControlGroup.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelectionControlGroup/VSelectionControlGroup.css";
|
||||||
var VSelectionControlGroupSymbol = /* @__PURE__ */ Symbol.for("vuetify:selection-control-group");
|
var VSelectionControlGroupSymbol = /* @__PURE__ */ Symbol.for("vuetify:selection-control-group");
|
||||||
var makeSelectionControlGroupProps = propsFactory({
|
var makeSelectionControlGroupProps = propsFactory({
|
||||||
color: String,
|
color: String,
|
||||||
@@ -167,6 +154,7 @@ var VSelectionControlGroup = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VSelectionControl/VSelectionControl.js
|
// node_modules/vuetify/lib/components/VSelectionControl/VSelectionControl.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSelectionControl/VSelectionControl.css";
|
||||||
var makeVSelectionControlProps = propsFactory({
|
var makeVSelectionControlProps = propsFactory({
|
||||||
label: String,
|
label: String,
|
||||||
baseColor: String,
|
baseColor: String,
|
||||||
@@ -369,133 +357,10 @@ var VSelectionControl = genericComponent()({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCheckbox/VCheckboxBtn.js
|
|
||||||
var makeVCheckboxBtnProps = propsFactory({
|
|
||||||
indeterminate: Boolean,
|
|
||||||
indeterminateIcon: {
|
|
||||||
type: IconValue,
|
|
||||||
default: "$checkboxIndeterminate"
|
|
||||||
},
|
|
||||||
...makeVSelectionControlProps({
|
|
||||||
falseIcon: "$checkboxOff",
|
|
||||||
trueIcon: "$checkboxOn"
|
|
||||||
})
|
|
||||||
}, "VCheckboxBtn");
|
|
||||||
var VCheckboxBtn = genericComponent()({
|
|
||||||
name: "VCheckboxBtn",
|
|
||||||
props: makeVCheckboxBtnProps(),
|
|
||||||
emits: {
|
|
||||||
"update:modelValue": (value) => true,
|
|
||||||
"update:indeterminate": (value) => true
|
|
||||||
},
|
|
||||||
setup(props, _ref) {
|
|
||||||
let {
|
|
||||||
slots
|
|
||||||
} = _ref;
|
|
||||||
const indeterminate = useProxiedModel(props, "indeterminate");
|
|
||||||
const model = useProxiedModel(props, "modelValue");
|
|
||||||
function onChange(v) {
|
|
||||||
if (indeterminate.value) {
|
|
||||||
indeterminate.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const falseIcon = toRef(() => {
|
|
||||||
return indeterminate.value ? props.indeterminateIcon : props.falseIcon;
|
|
||||||
});
|
|
||||||
const trueIcon = toRef(() => {
|
|
||||||
return indeterminate.value ? props.indeterminateIcon : props.trueIcon;
|
|
||||||
});
|
|
||||||
useRender(() => {
|
|
||||||
const controlProps = omit(VSelectionControl.filterProps(props), ["modelValue"]);
|
|
||||||
return createVNode(VSelectionControl, mergeProps(controlProps, {
|
|
||||||
"modelValue": model.value,
|
|
||||||
"onUpdate:modelValue": [($event) => model.value = $event, onChange],
|
|
||||||
"class": ["v-checkbox-btn", props.class],
|
|
||||||
"style": props.style,
|
|
||||||
"type": "checkbox",
|
|
||||||
"falseIcon": falseIcon.value,
|
|
||||||
"trueIcon": trueIcon.value,
|
|
||||||
"aria-checked": indeterminate.value ? "mixed" : void 0
|
|
||||||
}), slots);
|
|
||||||
});
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VCheckbox/VCheckbox.js
|
|
||||||
var makeVCheckboxProps = propsFactory({
|
|
||||||
...makeVInputProps(),
|
|
||||||
...omit(makeVCheckboxBtnProps(), ["inline"])
|
|
||||||
}, "VCheckbox");
|
|
||||||
var VCheckbox = genericComponent()({
|
|
||||||
name: "VCheckbox",
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: makeVCheckboxProps(),
|
|
||||||
emits: {
|
|
||||||
"update:modelValue": (value) => true,
|
|
||||||
"update:focused": (focused) => true
|
|
||||||
},
|
|
||||||
setup(props, _ref) {
|
|
||||||
let {
|
|
||||||
attrs,
|
|
||||||
slots
|
|
||||||
} = _ref;
|
|
||||||
const model = useProxiedModel(props, "modelValue");
|
|
||||||
const {
|
|
||||||
isFocused,
|
|
||||||
focus,
|
|
||||||
blur
|
|
||||||
} = useFocus(props);
|
|
||||||
const inputRef = ref();
|
|
||||||
const uid = useId();
|
|
||||||
useRender(() => {
|
|
||||||
const [rootAttrs, controlAttrs] = filterInputAttrs(attrs);
|
|
||||||
const inputProps = VInput.filterProps(props);
|
|
||||||
const checkboxProps = VCheckboxBtn.filterProps(props);
|
|
||||||
return createVNode(VInput, mergeProps({
|
|
||||||
"ref": inputRef,
|
|
||||||
"class": ["v-checkbox", props.class]
|
|
||||||
}, rootAttrs, inputProps, {
|
|
||||||
"modelValue": model.value,
|
|
||||||
"onUpdate:modelValue": ($event) => model.value = $event,
|
|
||||||
"id": props.id || `checkbox-${uid}`,
|
|
||||||
"focused": isFocused.value,
|
|
||||||
"style": props.style
|
|
||||||
}), {
|
|
||||||
...slots,
|
|
||||||
default: (_ref2) => {
|
|
||||||
let {
|
|
||||||
id,
|
|
||||||
messagesId,
|
|
||||||
isDisabled,
|
|
||||||
isReadonly,
|
|
||||||
isValid
|
|
||||||
} = _ref2;
|
|
||||||
return createVNode(VCheckboxBtn, mergeProps(checkboxProps, {
|
|
||||||
"id": id.value,
|
|
||||||
"aria-describedby": messagesId.value,
|
|
||||||
"disabled": isDisabled.value,
|
|
||||||
"readonly": isReadonly.value
|
|
||||||
}, controlAttrs, {
|
|
||||||
"error": isValid.value === false,
|
|
||||||
"modelValue": model.value,
|
|
||||||
"onUpdate:modelValue": ($event) => model.value = $event,
|
|
||||||
"onFocus": focus,
|
|
||||||
"onBlur": blur
|
|
||||||
}), slots);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return forwardRefs({}, inputRef);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
makeSelectionControlGroupProps,
|
makeSelectionControlGroupProps,
|
||||||
VSelectionControlGroup,
|
VSelectionControlGroup,
|
||||||
makeVSelectionControlProps,
|
makeVSelectionControlProps,
|
||||||
VSelectionControl,
|
VSelectionControl
|
||||||
VCheckboxBtn,
|
|
||||||
VCheckbox
|
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-BYBKODOB.js.map
|
//# sourceMappingURL=chunk-KHWE4FHS.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
+226
@@ -0,0 +1,226 @@
|
|||||||
|
import {
|
||||||
|
VSelectionControl,
|
||||||
|
makeVSelectionControlProps
|
||||||
|
} from "./chunk-KHWE4FHS.js";
|
||||||
|
import {
|
||||||
|
VInput,
|
||||||
|
makeVInputProps,
|
||||||
|
useFocus
|
||||||
|
} from "./chunk-IK67YVHF.js";
|
||||||
|
import {
|
||||||
|
VScaleTransition
|
||||||
|
} from "./chunk-B4JFJDWM.js";
|
||||||
|
import {
|
||||||
|
forwardRefs
|
||||||
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
VProgressCircular
|
||||||
|
} from "./chunk-MH76ZJYE.js";
|
||||||
|
import {
|
||||||
|
LoaderSlot,
|
||||||
|
useLoader
|
||||||
|
} from "./chunk-UXZ55AO7.js";
|
||||||
|
import {
|
||||||
|
VIcon
|
||||||
|
} from "./chunk-4XZHKYGH.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
|
import {
|
||||||
|
SUPPORTS_MATCH_MEDIA,
|
||||||
|
filterInputAttrs,
|
||||||
|
genericComponent,
|
||||||
|
propsFactory,
|
||||||
|
useRender
|
||||||
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
import {
|
||||||
|
Fragment,
|
||||||
|
createBaseVNode,
|
||||||
|
createVNode,
|
||||||
|
mergeProps,
|
||||||
|
normalizeClass,
|
||||||
|
normalizeStyle,
|
||||||
|
ref,
|
||||||
|
toRef,
|
||||||
|
useId
|
||||||
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VSwitch/VSwitch.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VSwitch/VSwitch.css";
|
||||||
|
var makeVSwitchProps = propsFactory({
|
||||||
|
indeterminate: Boolean,
|
||||||
|
inset: Boolean,
|
||||||
|
flat: Boolean,
|
||||||
|
loading: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
...makeVInputProps(),
|
||||||
|
...makeVSelectionControlProps()
|
||||||
|
}, "VSwitch");
|
||||||
|
var VSwitch = genericComponent()({
|
||||||
|
name: "VSwitch",
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: makeVSwitchProps(),
|
||||||
|
emits: {
|
||||||
|
"update:focused": (focused) => true,
|
||||||
|
"update:modelValue": (value) => true,
|
||||||
|
"update:indeterminate": (value) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
attrs,
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const indeterminate = useProxiedModel(props, "indeterminate");
|
||||||
|
const model = useProxiedModel(props, "modelValue");
|
||||||
|
const {
|
||||||
|
loaderClasses
|
||||||
|
} = useLoader(props);
|
||||||
|
const {
|
||||||
|
isFocused,
|
||||||
|
focus,
|
||||||
|
blur
|
||||||
|
} = useFocus(props);
|
||||||
|
const control = ref();
|
||||||
|
const inputRef = ref();
|
||||||
|
const isForcedColorsModeActive = SUPPORTS_MATCH_MEDIA && window.matchMedia("(forced-colors: active)").matches;
|
||||||
|
const loaderColor = toRef(() => {
|
||||||
|
return typeof props.loading === "string" && props.loading !== "" ? props.loading : props.color;
|
||||||
|
});
|
||||||
|
const uid = useId();
|
||||||
|
const id = toRef(() => props.id || `switch-${uid}`);
|
||||||
|
function onChange() {
|
||||||
|
if (indeterminate.value) {
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onTrackClick(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
control.value?.input?.click();
|
||||||
|
}
|
||||||
|
useRender(() => {
|
||||||
|
const [rootAttrs, controlAttrs] = filterInputAttrs(attrs);
|
||||||
|
const inputProps = VInput.filterProps(props);
|
||||||
|
const controlProps = VSelectionControl.filterProps(props);
|
||||||
|
return createVNode(VInput, mergeProps({
|
||||||
|
"ref": inputRef,
|
||||||
|
"class": ["v-switch", {
|
||||||
|
"v-switch--flat": props.flat
|
||||||
|
}, {
|
||||||
|
"v-switch--inset": props.inset
|
||||||
|
}, {
|
||||||
|
"v-switch--indeterminate": indeterminate.value
|
||||||
|
}, loaderClasses.value, props.class]
|
||||||
|
}, rootAttrs, inputProps, {
|
||||||
|
"modelValue": model.value,
|
||||||
|
"onUpdate:modelValue": ($event) => model.value = $event,
|
||||||
|
"id": id.value,
|
||||||
|
"focused": isFocused.value,
|
||||||
|
"style": props.style
|
||||||
|
}), {
|
||||||
|
...slots,
|
||||||
|
default: (_ref2) => {
|
||||||
|
let {
|
||||||
|
id: id2,
|
||||||
|
messagesId,
|
||||||
|
isDisabled,
|
||||||
|
isReadonly,
|
||||||
|
isValid
|
||||||
|
} = _ref2;
|
||||||
|
const slotProps = {
|
||||||
|
model,
|
||||||
|
isValid
|
||||||
|
};
|
||||||
|
return createVNode(VSelectionControl, mergeProps({
|
||||||
|
"ref": control
|
||||||
|
}, controlProps, {
|
||||||
|
"modelValue": model.value,
|
||||||
|
"onUpdate:modelValue": [($event) => model.value = $event, onChange],
|
||||||
|
"id": id2.value,
|
||||||
|
"aria-describedby": messagesId.value,
|
||||||
|
"type": "checkbox",
|
||||||
|
"aria-checked": indeterminate.value ? "mixed" : void 0,
|
||||||
|
"disabled": isDisabled.value,
|
||||||
|
"readonly": isReadonly.value,
|
||||||
|
"onFocus": focus,
|
||||||
|
"onBlur": blur
|
||||||
|
}, controlAttrs), {
|
||||||
|
...slots,
|
||||||
|
default: (_ref3) => {
|
||||||
|
let {
|
||||||
|
backgroundColorClasses,
|
||||||
|
backgroundColorStyles
|
||||||
|
} = _ref3;
|
||||||
|
return createBaseVNode("div", {
|
||||||
|
"class": normalizeClass(["v-switch__track", !isForcedColorsModeActive ? backgroundColorClasses.value : void 0]),
|
||||||
|
"style": normalizeStyle(backgroundColorStyles.value),
|
||||||
|
"onClick": onTrackClick
|
||||||
|
}, [slots["track-true"] && createBaseVNode("div", {
|
||||||
|
"key": "prepend",
|
||||||
|
"class": "v-switch__track-true"
|
||||||
|
}, [slots["track-true"](slotProps)]), slots["track-false"] && createBaseVNode("div", {
|
||||||
|
"key": "append",
|
||||||
|
"class": "v-switch__track-false"
|
||||||
|
}, [slots["track-false"](slotProps)])]);
|
||||||
|
},
|
||||||
|
input: (_ref4) => {
|
||||||
|
let {
|
||||||
|
inputNode,
|
||||||
|
icon,
|
||||||
|
backgroundColorClasses,
|
||||||
|
backgroundColorStyles
|
||||||
|
} = _ref4;
|
||||||
|
return createBaseVNode(Fragment, null, [inputNode, createBaseVNode("div", {
|
||||||
|
"class": normalizeClass(["v-switch__thumb", {
|
||||||
|
"v-switch__thumb--filled": icon || props.loading
|
||||||
|
}, props.inset || isForcedColorsModeActive ? void 0 : backgroundColorClasses.value]),
|
||||||
|
"style": normalizeStyle(props.inset ? void 0 : backgroundColorStyles.value)
|
||||||
|
}, [slots.thumb ? createVNode(VDefaultsProvider, {
|
||||||
|
"defaults": {
|
||||||
|
VIcon: {
|
||||||
|
icon,
|
||||||
|
size: "x-small"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
default: () => [slots.thumb({
|
||||||
|
...slotProps,
|
||||||
|
icon
|
||||||
|
})]
|
||||||
|
}) : createVNode(VScaleTransition, null, {
|
||||||
|
default: () => [!props.loading ? icon && createVNode(VIcon, {
|
||||||
|
"key": String(icon),
|
||||||
|
"icon": icon,
|
||||||
|
"size": "x-small"
|
||||||
|
}, null) : createVNode(LoaderSlot, {
|
||||||
|
"name": "v-switch",
|
||||||
|
"active": true,
|
||||||
|
"color": isValid.value === false ? void 0 : loaderColor.value
|
||||||
|
}, {
|
||||||
|
default: (slotProps2) => slots.loader ? slots.loader(slotProps2) : createVNode(VProgressCircular, {
|
||||||
|
"active": slotProps2.isActive,
|
||||||
|
"color": slotProps2.color,
|
||||||
|
"indeterminate": true,
|
||||||
|
"size": "16",
|
||||||
|
"width": "2"
|
||||||
|
}, null)
|
||||||
|
})]
|
||||||
|
})])]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return forwardRefs({}, inputRef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
VSwitch
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=chunk-L7BQJLEW.js.map
|
||||||
+7
File diff suppressed because one or more lines are too long
+177
@@ -0,0 +1,177 @@
|
|||||||
|
import {
|
||||||
|
useSsrBoot
|
||||||
|
} from "./chunk-PCKQGDLK.js";
|
||||||
|
import {
|
||||||
|
makeLayoutItemProps,
|
||||||
|
useLayoutItem
|
||||||
|
} from "./chunk-KQXZAEJP.js";
|
||||||
|
import {
|
||||||
|
VBtnToggleSymbol
|
||||||
|
} from "./chunk-5AQLTWWS.js";
|
||||||
|
import {
|
||||||
|
makeGroupProps,
|
||||||
|
useGroup
|
||||||
|
} from "./chunk-W6DJL42A.js";
|
||||||
|
import {
|
||||||
|
makeBorderProps,
|
||||||
|
useBorder
|
||||||
|
} from "./chunk-X7YBE6PF.js";
|
||||||
|
import {
|
||||||
|
makeElevationProps,
|
||||||
|
useElevation
|
||||||
|
} from "./chunk-YJD2PNP6.js";
|
||||||
|
import {
|
||||||
|
makeDensityProps,
|
||||||
|
useDensity
|
||||||
|
} from "./chunk-TM3MIDYC.js";
|
||||||
|
import {
|
||||||
|
makeRoundedProps,
|
||||||
|
useRounded
|
||||||
|
} from "./chunk-AQK3CYVW.js";
|
||||||
|
import {
|
||||||
|
makeTagProps
|
||||||
|
} from "./chunk-2HZIACSB.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useProxiedModel
|
||||||
|
} from "./chunk-KA77II4V.js";
|
||||||
|
import {
|
||||||
|
makeThemeProps,
|
||||||
|
useTheme
|
||||||
|
} from "./chunk-3DSR7NCB.js";
|
||||||
|
import {
|
||||||
|
convertToUnit,
|
||||||
|
genericComponent,
|
||||||
|
makeComponentProps,
|
||||||
|
propsFactory,
|
||||||
|
provideDefaults,
|
||||||
|
useRender
|
||||||
|
} from "./chunk-LZ5T2ERG.js";
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
createBaseVNode,
|
||||||
|
createVNode,
|
||||||
|
normalizeClass,
|
||||||
|
normalizeStyle,
|
||||||
|
toRef
|
||||||
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
|
// node_modules/vuetify/lib/components/VBottomNavigation/VBottomNavigation.js
|
||||||
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VBottomNavigation/VBottomNavigation.css";
|
||||||
|
var makeVBottomNavigationProps = propsFactory({
|
||||||
|
baseColor: String,
|
||||||
|
bgColor: String,
|
||||||
|
color: String,
|
||||||
|
grow: Boolean,
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
validator: (v) => !v || ["horizontal", "shift"].includes(v)
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 56
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
...makeBorderProps(),
|
||||||
|
...makeComponentProps(),
|
||||||
|
...makeDensityProps(),
|
||||||
|
...makeElevationProps(),
|
||||||
|
...makeRoundedProps(),
|
||||||
|
...makeLayoutItemProps({
|
||||||
|
name: "bottom-navigation"
|
||||||
|
}),
|
||||||
|
...makeTagProps({
|
||||||
|
tag: "header"
|
||||||
|
}),
|
||||||
|
...makeGroupProps({
|
||||||
|
selectedClass: "v-btn--selected"
|
||||||
|
}),
|
||||||
|
...makeThemeProps()
|
||||||
|
}, "VBottomNavigation");
|
||||||
|
var VBottomNavigation = genericComponent()({
|
||||||
|
name: "VBottomNavigation",
|
||||||
|
props: makeVBottomNavigationProps(),
|
||||||
|
emits: {
|
||||||
|
"update:active": (value) => true,
|
||||||
|
"update:modelValue": (value) => true
|
||||||
|
},
|
||||||
|
setup(props, _ref) {
|
||||||
|
let {
|
||||||
|
slots
|
||||||
|
} = _ref;
|
||||||
|
const {
|
||||||
|
themeClasses
|
||||||
|
} = useTheme();
|
||||||
|
const {
|
||||||
|
borderClasses
|
||||||
|
} = useBorder(props);
|
||||||
|
const {
|
||||||
|
backgroundColorClasses,
|
||||||
|
backgroundColorStyles
|
||||||
|
} = useBackgroundColor(() => props.bgColor);
|
||||||
|
const {
|
||||||
|
densityClasses
|
||||||
|
} = useDensity(props);
|
||||||
|
const {
|
||||||
|
elevationClasses
|
||||||
|
} = useElevation(props);
|
||||||
|
const {
|
||||||
|
roundedClasses
|
||||||
|
} = useRounded(props);
|
||||||
|
const {
|
||||||
|
ssrBootStyles
|
||||||
|
} = useSsrBoot();
|
||||||
|
const height = computed(() => Number(props.height) - (props.density === "comfortable" ? 8 : 0) - (props.density === "compact" ? 16 : 0));
|
||||||
|
const isActive = useProxiedModel(props, "active", props.active);
|
||||||
|
const {
|
||||||
|
layoutItemStyles
|
||||||
|
} = useLayoutItem({
|
||||||
|
id: props.name,
|
||||||
|
order: computed(() => parseInt(props.order, 10)),
|
||||||
|
position: toRef(() => "bottom"),
|
||||||
|
layoutSize: toRef(() => isActive.value ? height.value : 0),
|
||||||
|
elementSize: height,
|
||||||
|
active: isActive,
|
||||||
|
absolute: toRef(() => props.absolute)
|
||||||
|
});
|
||||||
|
useGroup(props, VBtnToggleSymbol);
|
||||||
|
provideDefaults({
|
||||||
|
VBtn: {
|
||||||
|
baseColor: toRef(() => props.baseColor),
|
||||||
|
color: toRef(() => props.color),
|
||||||
|
density: toRef(() => props.density),
|
||||||
|
stacked: toRef(() => props.mode !== "horizontal"),
|
||||||
|
variant: "text"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scoped: true
|
||||||
|
});
|
||||||
|
useRender(() => {
|
||||||
|
return createVNode(props.tag, {
|
||||||
|
"class": normalizeClass(["v-bottom-navigation", {
|
||||||
|
"v-bottom-navigation--active": isActive.value,
|
||||||
|
"v-bottom-navigation--grow": props.grow,
|
||||||
|
"v-bottom-navigation--shift": props.mode === "shift"
|
||||||
|
}, themeClasses.value, backgroundColorClasses.value, borderClasses.value, densityClasses.value, elevationClasses.value, roundedClasses.value, props.class]),
|
||||||
|
"style": normalizeStyle([backgroundColorStyles.value, layoutItemStyles.value, {
|
||||||
|
height: convertToUnit(height.value)
|
||||||
|
}, ssrBootStyles.value, props.style])
|
||||||
|
}, {
|
||||||
|
default: () => [slots.default && createBaseVNode("div", {
|
||||||
|
"class": "v-bottom-navigation__content"
|
||||||
|
}, [slots.default()])]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
VBottomNavigation
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=chunk-LFKLAYDH.js.map
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"sources": ["../../vuetify/src/components/VBottomNavigation/VBottomNavigation.tsx"],
|
||||||
|
"sourcesContent": ["// Styles\nimport './VBottomNavigation.sass'\n\n// Components\nimport { VBtnToggleSymbol } from '@/components/VBtnToggle/VBtnToggle'\n\n// Composables\nimport { makeBorderProps, useBorder } from '@/composables/border'\nimport { useBackgroundColor } from '@/composables/color'\nimport { makeComponentProps } from '@/composables/component'\nimport { provideDefaults } from '@/composables/defaults'\nimport { makeDensityProps, useDensity } from '@/composables/density'\nimport { makeElevationProps, useElevation } from '@/composables/elevation'\nimport { makeGroupProps, useGroup } from '@/composables/group'\nimport { makeLayoutItemProps, useLayoutItem } from '@/composables/layout'\nimport { useProxiedModel } from '@/composables/proxiedModel'\nimport { makeRoundedProps, useRounded } from '@/composables/rounded'\nimport { useSsrBoot } from '@/composables/ssrBoot'\nimport { makeTagProps } from '@/composables/tag'\nimport { makeThemeProps, useTheme } from '@/composables/theme'\n\n// Utilities\nimport { computed, toRef } from 'vue'\nimport { convertToUnit, genericComponent, propsFactory, useRender } from '@/util'\n\n// Types\nimport type { GenericProps } from '@/util'\n\nexport const makeVBottomNavigationProps = propsFactory({\n baseColor: String,\n bgColor: String,\n color: String,\n grow: Boolean,\n mode: {\n type: String,\n validator: (v: any) => !v || ['horizontal', 'shift'].includes(v),\n },\n height: {\n type: [Number, String],\n default: 56,\n },\n active: {\n type: Boolean,\n default: true,\n },\n\n ...makeBorderProps(),\n ...makeComponentProps(),\n ...makeDensityProps(),\n ...makeElevationProps(),\n ...makeRoundedProps(),\n ...makeLayoutItemProps({ name: 'bottom-navigation' }),\n ...makeTagProps({ tag: 'header' }),\n ...makeGroupProps({ selectedClass: 'v-btn--selected' }),\n ...makeThemeProps(),\n}, 'VBottomNavigation')\n\nexport const VBottomNavigation = genericComponent<new <T>(\n props: {\n modelValue?: T\n 'onUpdate:modelValue'?: (value: T) => void\n },\n slots: { default: never },\n) => GenericProps<typeof props, typeof slots>>()({\n name: 'VBottomNavigation',\n\n props: makeVBottomNavigationProps(),\n\n emits: {\n 'update:active': (value: any) => true,\n 'update:modelValue': (value: any) => true,\n },\n\n setup (props, { slots }) {\n const { themeClasses } = useTheme()\n const { borderClasses } = useBorder(props)\n const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.bgColor)\n const { densityClasses } = useDensity(props)\n const { elevationClasses } = useElevation(props)\n const { roundedClasses } = useRounded(props)\n const { ssrBootStyles } = useSsrBoot()\n const height = computed(() => (\n Number(props.height) -\n (props.density === 'comfortable' ? 8 : 0) -\n (props.density === 'compact' ? 16 : 0)\n ))\n const isActive = useProxiedModel(props, 'active', props.active)\n const { layoutItemStyles } = useLayoutItem({\n id: props.name,\n order: computed(() => parseInt(props.order, 10)),\n position: toRef(() => 'bottom'),\n layoutSize: toRef(() => isActive.value ? height.value : 0),\n elementSize: height,\n active: isActive,\n absolute: toRef(() => props.absolute),\n })\n\n useGroup(props, VBtnToggleSymbol)\n\n provideDefaults({\n VBtn: {\n baseColor: toRef(() => props.baseColor),\n color: toRef(() => props.color),\n density: toRef(() => props.density),\n stacked: toRef(() => props.mode !== 'horizontal'),\n variant: 'text',\n },\n }, { scoped: true })\n\n useRender(() => {\n return (\n <props.tag\n class={[\n 'v-bottom-navigation',\n {\n 'v-bottom-navigation--active': isActive.value,\n 'v-bottom-navigation--grow': props.grow,\n 'v-bottom-navigation--shift': props.mode === 'shift',\n },\n themeClasses.value,\n backgroundColorClasses.value,\n borderClasses.value,\n densityClasses.value,\n elevationClasses.value,\n roundedClasses.value,\n props.class,\n ]}\n style={[\n backgroundColorStyles.value,\n layoutItemStyles.value,\n {\n height: convertToUnit(height.value),\n },\n ssrBootStyles.value,\n props.style,\n ]}\n >\n { slots.default && (\n <div class=\"v-bottom-navigation__content\">\n { slots.default() }\n </div>\n )}\n </props.tag>\n )\n })\n\n return {}\n },\n})\n\nexport type VBottomNavigation = InstanceType<typeof VBottomNavigation>\n"],
|
||||||
|
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,OAAA;AA2BO,IAAMA,6BAA6BC,aAAa;EACrDC,WAAWC;EACXC,SAASD;EACTE,OAAOF;EACPG,MAAMC;EACNC,MAAM;IACJC,MAAMN;IACNO,WAAYC,OAAW,CAACA,KAAK,CAAC,cAAc,OAAO,EAAEC,SAASD,CAAC;EACjE;EACAE,QAAQ;IACNJ,MAAM,CAACK,QAAQX,MAAM;IACrBY,SAAS;EACX;EACAC,QAAQ;IACNP,MAAMF;IACNQ,SAAS;EACX;EAEA,GAAGE,gBAAgB;EACnB,GAAGC,mBAAmB;EACtB,GAAGC,iBAAiB;EACpB,GAAGC,mBAAmB;EACtB,GAAGC,iBAAiB;EACpB,GAAGC,oBAAoB;IAAEC,MAAM;EAAoB,CAAC;EACpD,GAAGC,aAAa;IAAEC,KAAK;EAAS,CAAC;EACjC,GAAGC,eAAe;IAAEC,eAAe;EAAkB,CAAC;EACtD,GAAGC,eAAe;AACpB,GAAG,mBAAmB;AAEf,IAAMC,oBAAoBC,iBAMc,EAAE;EAC/CP,MAAM;EAENQ,OAAO/B,2BAA2B;EAElCgC,OAAO;IACL,iBAAkBC,WAAe;IACjC,qBAAsBA,WAAe;EACvC;EAEAC,MAAOH,OAAKI,MAAa;AAAA,QAAX;MAAEC;IAAM,IAACD;AACrB,UAAM;MAAEE;IAAa,IAAIC,SAAS;AAClC,UAAM;MAAEC;IAAc,IAAIC,UAAUT,KAAK;AACzC,UAAM;MAAEU;MAAwBC;IAAsB,IAAIC,mBAAmB,MAAMZ,MAAM3B,OAAO;AAChG,UAAM;MAAEwC;IAAe,IAAIC,WAAWd,KAAK;AAC3C,UAAM;MAAEe;IAAiB,IAAIC,aAAahB,KAAK;AAC/C,UAAM;MAAEiB;IAAe,IAAIC,WAAWlB,KAAK;AAC3C,UAAM;MAAEmB;IAAc,IAAIC,WAAW;AACrC,UAAMtC,SAASuC,SAAS,MACtBtC,OAAOiB,MAAMlB,MAAM,KAClBkB,MAAMsB,YAAY,gBAAgB,IAAI,MACtCtB,MAAMsB,YAAY,YAAY,KAAK,EACrC;AACD,UAAMC,WAAWC,gBAAgBxB,OAAO,UAAUA,MAAMf,MAAM;AAC9D,UAAM;MAAEwC;IAAiB,IAAIC,cAAc;MACzCC,IAAI3B,MAAMR;MACVoC,OAAOP,SAAS,MAAMQ,SAAS7B,MAAM4B,OAAO,EAAE,CAAC;MAC/CE,UAAUC,MAAM,MAAM,QAAQ;MAC9BC,YAAYD,MAAM,MAAMR,SAASrB,QAAQpB,OAAOoB,QAAQ,CAAC;MACzD+B,aAAanD;MACbG,QAAQsC;MACRW,UAAUH,MAAM,MAAM/B,MAAMkC,QAAQ;IACtC,CAAC;AAEDC,aAASnC,OAAOoC,gBAAgB;AAEhCC,oBAAgB;MACdC,MAAM;QACJnE,WAAW4D,MAAM,MAAM/B,MAAM7B,SAAS;QACtCG,OAAOyD,MAAM,MAAM/B,MAAM1B,KAAK;QAC9BgD,SAASS,MAAM,MAAM/B,MAAMsB,OAAO;QAClCiB,SAASR,MAAM,MAAM/B,MAAMvB,SAAS,YAAY;QAChD+D,SAAS;MACX;IACF,GAAG;MAAEC,QAAQ;IAAK,CAAC;AAEnBC,cAAU,MAAM;AACd,aAAAC,YAAA3C,MAAAN,KAAA;QAAA,SAAAkD,eAEW,CACL,uBACA;UACE,+BAA+BrB,SAASrB;UACxC,6BAA6BF,MAAMzB;UACnC,8BAA8ByB,MAAMvB,SAAS;QAC/C,GACA6B,aAAaJ,OACbQ,uBAAuBR,OACvBM,cAAcN,OACdW,eAAeX,OACfa,iBAAiBb,OACjBe,eAAef,OACfF,MAAM6C,KAAK,CACZ;QAAA,SAAAC,eACM,CACLnC,sBAAsBT,OACtBuB,iBAAiBvB,OACjB;UACEpB,QAAQiE,cAAcjE,OAAOoB,KAAK;QACpC,GACAiB,cAAcjB,OACdF,MAAMgD,KAAK,CACZ;MAAA,GAAA;QAAAhE,SAAAA,MAAA,CAECqB,MAAMrB,WAAOiE,gBAAA,OAAA;UAAA,SAAA;QAAA,GAAA,CAET5C,MAAMrB,QAAQ,CAAC,CAAA,CAEpB;MAAA,CAAA;IAGP,CAAC;AAED,WAAO,CAAC;EACV;AACF,CAAC;",
|
||||||
|
"names": ["makeVBottomNavigationProps", "propsFactory", "baseColor", "String", "bgColor", "color", "grow", "Boolean", "mode", "type", "validator", "v", "includes", "height", "Number", "default", "active", "makeBorderProps", "makeComponentProps", "makeDensityProps", "makeElevationProps", "makeRoundedProps", "makeLayoutItemProps", "name", "makeTagProps", "tag", "makeGroupProps", "selectedClass", "makeThemeProps", "VBottomNavigation", "genericComponent", "props", "emits", "value", "setup", "_ref", "slots", "themeClasses", "useTheme", "borderClasses", "useBorder", "backgroundColorClasses", "backgroundColorStyles", "useBackgroundColor", "densityClasses", "useDensity", "elevationClasses", "useElevation", "roundedClasses", "useRounded", "ssrBootStyles", "useSsrBoot", "computed", "density", "isActive", "useProxiedModel", "layoutItemStyles", "useLayoutItem", "id", "order", "parseInt", "position", "toRef", "layoutSize", "elementSize", "absolute", "useGroup", "VBtnToggleSymbol", "provideDefaults", "VBtn", "stacked", "variant", "scoped", "useRender", "_createVNode", "_normalizeClass", "class", "_normalizeStyle", "convertToUnit", "style", "_createElementVNode"]
|
||||||
|
}
|
||||||
Generated
Vendored
+6
-6
@@ -1,20 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
VDialogTransition
|
VDialogTransition
|
||||||
} from "./chunk-B4JFJDWM.js";
|
} from "./chunk-B4JFJDWM.js";
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
VMenuSymbol,
|
VMenuSymbol,
|
||||||
VOverlay,
|
VOverlay,
|
||||||
makeVOverlayProps
|
makeVOverlayProps
|
||||||
} from "./chunk-TS6Y6OVT.js";
|
} from "./chunk-CL6G3ICF.js";
|
||||||
import {
|
import {
|
||||||
useScopeId
|
useScopeId
|
||||||
} from "./chunk-BVUPSGRF.js";
|
} from "./chunk-BVUPSGRF.js";
|
||||||
import {
|
import {
|
||||||
forwardRefs
|
forwardRefs
|
||||||
} from "./chunk-GUNW7S2L.js";
|
} from "./chunk-GUNW7S2L.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
import {
|
import {
|
||||||
useRtl
|
useRtl
|
||||||
} from "./chunk-ENTLCOCZ.js";
|
} from "./chunk-ENTLCOCZ.js";
|
||||||
@@ -47,7 +47,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VMenu/VMenu.js
|
// node_modules/vuetify/lib/components/VMenu/VMenu.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VMenu/VMenu.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VMenu/VMenu.css";
|
||||||
var makeVMenuProps = propsFactory({
|
var makeVMenuProps = propsFactory({
|
||||||
// TODO
|
// TODO
|
||||||
// disableKeys: Boolean,
|
// disableKeys: Boolean,
|
||||||
@@ -203,4 +203,4 @@ export {
|
|||||||
makeVMenuProps,
|
makeVMenuProps,
|
||||||
VMenu
|
VMenu
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-HGIC64KW.js.map
|
//# sourceMappingURL=chunk-LPDOFVDK.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+5
-5
@@ -8,12 +8,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
import {
|
|
||||||
useResizeObserver
|
|
||||||
} from "./chunk-XDSAO7OT.js";
|
|
||||||
import {
|
import {
|
||||||
useTextColor
|
useTextColor
|
||||||
} from "./chunk-BHV5Z34M.js";
|
} from "./chunk-BHV5Z34M.js";
|
||||||
|
import {
|
||||||
|
useResizeObserver
|
||||||
|
} from "./chunk-XDSAO7OT.js";
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VProgressCircular/VProgressCircular.js
|
// node_modules/vuetify/lib/components/VProgressCircular/VProgressCircular.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VProgressCircular/VProgressCircular.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VProgressCircular/VProgressCircular.css";
|
||||||
var makeVProgressCircularProps = propsFactory({
|
var makeVProgressCircularProps = propsFactory({
|
||||||
bgColor: String,
|
bgColor: String,
|
||||||
color: String,
|
color: String,
|
||||||
@@ -168,4 +168,4 @@ var VProgressCircular = genericComponent()({
|
|||||||
export {
|
export {
|
||||||
VProgressCircular
|
VProgressCircular
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-ET5GCC5W.js.map
|
//# sourceMappingURL=chunk-MH76ZJYE.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+28
-28
@@ -1,14 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
VAvatar
|
VAvatar
|
||||||
} from "./chunk-Z3677HMS.js";
|
} from "./chunk-SXI5CS7S.js";
|
||||||
|
import {
|
||||||
|
VDivider
|
||||||
|
} from "./chunk-X3WY3J75.js";
|
||||||
|
import {
|
||||||
|
useSsrBoot
|
||||||
|
} from "./chunk-PCKQGDLK.js";
|
||||||
|
import {
|
||||||
|
VExpandTransition
|
||||||
|
} from "./chunk-B4JFJDWM.js";
|
||||||
|
import {
|
||||||
|
MaybeTransition
|
||||||
|
} from "./chunk-CAGZXMOS.js";
|
||||||
import {
|
import {
|
||||||
genOverlays,
|
genOverlays,
|
||||||
makeVariantProps,
|
makeVariantProps,
|
||||||
useVariant
|
useVariant
|
||||||
} from "./chunk-ADXDXPPR.js";
|
} from "./chunk-ADXDXPPR.js";
|
||||||
import {
|
|
||||||
useSsrBoot
|
|
||||||
} from "./chunk-PCKQGDLK.js";
|
|
||||||
import {
|
import {
|
||||||
makeBorderProps,
|
makeBorderProps,
|
||||||
useBorder
|
useBorder
|
||||||
@@ -17,53 +26,44 @@ import {
|
|||||||
makeElevationProps,
|
makeElevationProps,
|
||||||
useElevation
|
useElevation
|
||||||
} from "./chunk-YJD2PNP6.js";
|
} from "./chunk-YJD2PNP6.js";
|
||||||
import {
|
|
||||||
VExpandTransition
|
|
||||||
} from "./chunk-B4JFJDWM.js";
|
|
||||||
import {
|
import {
|
||||||
makeDensityProps,
|
makeDensityProps,
|
||||||
useDensity
|
useDensity
|
||||||
} from "./chunk-TM3MIDYC.js";
|
} from "./chunk-TM3MIDYC.js";
|
||||||
import {
|
import {
|
||||||
VIcon
|
VIcon
|
||||||
} from "./chunk-CMZ4UYUX.js";
|
} from "./chunk-4XZHKYGH.js";
|
||||||
import {
|
|
||||||
VDefaultsProvider
|
|
||||||
} from "./chunk-VH7QIDM4.js";
|
|
||||||
import {
|
import {
|
||||||
makeRoundedProps,
|
makeRoundedProps,
|
||||||
useRounded
|
useRounded
|
||||||
} from "./chunk-AQK3CYVW.js";
|
} from "./chunk-AQK3CYVW.js";
|
||||||
|
import {
|
||||||
|
VDefaultsProvider
|
||||||
|
} from "./chunk-PAT7NP2Z.js";
|
||||||
import {
|
import {
|
||||||
makeTagProps
|
makeTagProps
|
||||||
} from "./chunk-2HZIACSB.js";
|
} from "./chunk-2HZIACSB.js";
|
||||||
import {
|
import {
|
||||||
ripple_default
|
IconValue
|
||||||
} from "./chunk-5BPWKCNA.js";
|
} from "./chunk-OJDZBU5D.js";
|
||||||
import {
|
import {
|
||||||
makeRouterProps,
|
makeRouterProps,
|
||||||
useLink
|
useLink
|
||||||
} from "./chunk-573D4TYC.js";
|
} from "./chunk-573D4TYC.js";
|
||||||
import {
|
import {
|
||||||
MaybeTransition
|
ripple_default
|
||||||
} from "./chunk-CAGZXMOS.js";
|
} from "./chunk-DS4J67XG.js";
|
||||||
|
import {
|
||||||
|
useBackgroundColor,
|
||||||
|
useTextColor
|
||||||
|
} from "./chunk-BHV5Z34M.js";
|
||||||
import {
|
import {
|
||||||
makeDimensionProps,
|
makeDimensionProps,
|
||||||
useDimension
|
useDimension
|
||||||
} from "./chunk-6EZKHCRW.js";
|
} from "./chunk-6EZKHCRW.js";
|
||||||
import {
|
|
||||||
IconValue
|
|
||||||
} from "./chunk-OJDZBU5D.js";
|
|
||||||
import {
|
import {
|
||||||
useProxiedModel
|
useProxiedModel
|
||||||
} from "./chunk-KA77II4V.js";
|
} from "./chunk-KA77II4V.js";
|
||||||
import {
|
|
||||||
VDivider
|
|
||||||
} from "./chunk-WXMECWZN.js";
|
|
||||||
import {
|
|
||||||
useBackgroundColor,
|
|
||||||
useTextColor
|
|
||||||
} from "./chunk-BHV5Z34M.js";
|
|
||||||
import {
|
import {
|
||||||
makeThemeProps,
|
makeThemeProps,
|
||||||
provideTheme
|
provideTheme
|
||||||
@@ -116,7 +116,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VList/VList.js
|
// node_modules/vuetify/lib/components/VList/VList.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VList/VList.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VList/VList.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VList/list.js
|
// node_modules/vuetify/lib/components/VList/list.js
|
||||||
var ListKey = /* @__PURE__ */ Symbol.for("vuetify:list");
|
var ListKey = /* @__PURE__ */ Symbol.for("vuetify:list");
|
||||||
@@ -1015,7 +1015,7 @@ var VListGroup = genericComponent()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VList/VListItem.js
|
// node_modules/vuetify/lib/components/VList/VListItem.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VList/VListItem.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VList/VListItem.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VList/VListItemSubtitle.js
|
// node_modules/vuetify/lib/components/VList/VListItemSubtitle.js
|
||||||
var makeVListItemSubtitleProps = propsFactory({
|
var makeVListItemSubtitleProps = propsFactory({
|
||||||
@@ -1893,4 +1893,4 @@ export {
|
|||||||
VListItemAction,
|
VListItemAction,
|
||||||
VListItemMedia
|
VListItemMedia
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-HV7P7R7J.js.map
|
//# sourceMappingURL=chunk-MQSMF5CI.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+7
-7
@@ -3,11 +3,7 @@ import {
|
|||||||
VToolbarTitle,
|
VToolbarTitle,
|
||||||
makeVToolbarProps,
|
makeVToolbarProps,
|
||||||
makeVToolbarTitleProps
|
makeVToolbarTitleProps
|
||||||
} from "./chunk-LPZZJ62X.js";
|
} from "./chunk-H2QFYTJQ.js";
|
||||||
import {
|
|
||||||
VBtn,
|
|
||||||
makeVBtnProps
|
|
||||||
} from "./chunk-QC7VWQER.js";
|
|
||||||
import {
|
import {
|
||||||
useSsrBoot
|
useSsrBoot
|
||||||
} from "./chunk-PCKQGDLK.js";
|
} from "./chunk-PCKQGDLK.js";
|
||||||
@@ -15,6 +11,10 @@ import {
|
|||||||
makeLayoutItemProps,
|
makeLayoutItemProps,
|
||||||
useLayoutItem
|
useLayoutItem
|
||||||
} from "./chunk-KQXZAEJP.js";
|
} from "./chunk-KQXZAEJP.js";
|
||||||
|
import {
|
||||||
|
VBtn,
|
||||||
|
makeVBtnProps
|
||||||
|
} from "./chunk-UQALH6SL.js";
|
||||||
import {
|
import {
|
||||||
useProxiedModel,
|
useProxiedModel,
|
||||||
useToggleScope
|
useToggleScope
|
||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
} from "./chunk-2MKFL3BX.js";
|
} from "./chunk-2MKFL3BX.js";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/components/VAppBar/VAppBar.js
|
// node_modules/vuetify/lib/components/VAppBar/VAppBar.js
|
||||||
import "/home/david/Documents/vscode/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VAppBar/VAppBar.css";
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VAppBar/VAppBar.css";
|
||||||
|
|
||||||
// node_modules/vuetify/lib/composables/scroll.js
|
// node_modules/vuetify/lib/composables/scroll.js
|
||||||
var makeScrollProps = propsFactory({
|
var makeScrollProps = propsFactory({
|
||||||
@@ -357,4 +357,4 @@ export {
|
|||||||
VAppBarNavIcon,
|
VAppBarNavIcon,
|
||||||
VAppBarTitle
|
VAppBarTitle
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-BXEVD6W6.js.map
|
//# sourceMappingURL=chunk-P6EQK5CJ.js.map
|
||||||
Generated
Vendored
Generated
Vendored
+1
-1
@@ -42,4 +42,4 @@ var VDefaultsProvider = genericComponent(false)({
|
|||||||
export {
|
export {
|
||||||
VDefaultsProvider
|
VDefaultsProvider
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=chunk-VH7QIDM4.js.map
|
//# sourceMappingURL=chunk-PAT7NP2Z.js.map
|
||||||
Generated
Vendored
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user