Files
news_all_spo/web/views/media_library.py
2026-05-13 19:05:07 +05:00

218 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import flet as ft
import designer as d
import api_client as api
from views.dashboard import _admin_appbar, _sidebar
class MediaLibraryView:
def __init__(self, page: ft.Page):
self.page = page
self._grid = ft.GridView(
expand=True,
runs_count=4,
max_extent=200,
child_aspect_ratio=1.0,
spacing=12,
run_spacing=12,
)
self._filter = None
self._picker = ft.FilePicker(on_result=self._on_files_picked)
def build_page_view(self) -> ft.View:
filter_tabs = ft.Tabs(
selected_index=0,
on_change=self._on_filter,
tabs=[ft.Tab(text="Все"), ft.Tab(text="Изображения"), ft.Tab(text="Видео")],
)
return ft.View(
route="/admin/media",
bgcolor=d.BACKGROUND,
appbar=_admin_appbar(self.page, "Медиа"),
controls=[
ft.Row(
[
_sidebar(self.page, active="/admin/media"),
ft.Container(
content=ft.Column(
[
ft.Row(
[
d.headline("Медиатека", size=22),
d.btn_primary(
"Загрузить файлы",
icon=ft.icons.UPLOAD,
on_click=lambda e: self._picker.pick_files(
allow_multiple=True,
dialog_title="Выберите изображения или видео",
),
),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
filter_tabs,
self._grid,
],
expand=True,
spacing=16,
),
expand=True,
padding=24,
),
],
expand=True,
spacing=0,
),
self._picker,
],
)
async def load(self):
await self._reload()
async def _on_filter(self, e):
self._filter = [None, "image", "video"][e.control.selected_index]
await self._reload()
async def _reload(self):
token = self.page.session.get("token")
items = await api.admin_list_media(token, limit=200, media_type=self._filter)
self._render(items)
def _render(self, items: list):
if not items:
self._grid.controls = [
d.empty_state(ft.icons.PERM_MEDIA_OUTLINED, "Медиафайлов пока нет")
]
self.page.update()
return
self._grid.controls = [self._build_card(m) for m in items]
self.page.update()
def _build_card(self, m: dict) -> ft.Container:
is_image = m.get("media_type") == "image"
content = (
ft.Image(src=m["url"], fit=ft.ImageFit.COVER, expand=True, error_content=ft.Icon(ft.icons.BROKEN_IMAGE))
if is_image
else ft.Column(
[ft.Icon(ft.icons.PLAY_CIRCLE_OUTLINED, size=40, color=d.TEXT_SECONDARY),
ft.Text("Видео", size=11, color=d.TEXT_SECONDARY)],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
expand=True,
)
)
size_kb = round(m.get("size_bytes", 0) / 1024, 1)
name = m.get("filename", "")[:20]
return ft.Container(
content=ft.Stack(
[
ft.Container(content=content, expand=True, bgcolor="#F0F0F0", border_radius=8),
ft.Container(
content=ft.Row(
[
ft.Container(
content=ft.Column(
[
ft.Text(name, size=10, color=ft.colors.WHITE, max_lines=1,
overflow=ft.TextOverflow.ELLIPSIS),
ft.Text(f"{size_kb} КБ", size=9, color=ft.colors.WHITE70),
],
spacing=2,
expand=True,
),
expand=True,
),
ft.Row(
[
ft.IconButton(
ft.icons.COPY,
icon_size=14,
icon_color=ft.colors.WHITE,
tooltip="Копировать URL",
on_click=lambda e, url=m["url"]: self._copy_url(url),
),
ft.IconButton(
ft.icons.DELETE_OUTLINE,
icon_size=14,
icon_color=ft.colors.WHITE,
tooltip="Удалить",
on_click=lambda e, mid=m["id"]: self._confirm_delete(mid, m.get("filename", "")),
),
],
spacing=0,
),
],
spacing=0,
),
bgcolor=ft.colors.with_opacity(0.6, ft.colors.BLACK),
border_radius=ft.BorderRadius(0, 0, 8, 8),
padding=ft.padding.symmetric(horizontal=8, vertical=6),
bottom=0,
left=0,
right=0,
),
],
expand=True,
),
height=180,
border_radius=8,
border=ft.border.all(1, d.DIVIDER),
clip_behavior=ft.ClipBehavior.HARD_EDGE,
)
def _copy_url(self, url: str):
self.page.set_clipboard(url)
d.snack(self.page, "URL скопирован в буфер обмена")
def _confirm_delete(self, media_id: str, filename: str):
dlg = d.confirm_dialog(
self.page,
"Удалить файл?",
f'"{filename}" будет удалён с сервера.',
lambda: self.page.run_task(self._delete, media_id),
)
self.page.dialog = dlg
dlg.open = True
self.page.update()
async def _delete(self, media_id: str):
token = self.page.session.get("token")
ok = await api.admin_delete_media(token, media_id)
if ok:
d.snack(self.page, "Файл удалён")
await self._reload()
else:
d.snack(self.page, "Ошибка удаления", error=True)
async def _on_files_picked(self, e: ft.FilePickerResultEvent):
if not e.files:
return
token = self.page.session.get("token")
uploaded = 0
for f in e.files:
ext = (f.name or "").rsplit(".", 1)[-1].lower()
video_exts = {"mp4", "webm", "ogg", "mov"}
kind = "video" if ext in video_exts else "image"
ct_map = {
"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png",
"gif": "image/gif", "webp": "image/webp",
"mp4": "video/mp4", "webm": "video/webm", "ogg": "video/ogg", "mov": "video/quicktime",
}
ct = ct_map.get(ext, "application/octet-stream")
try:
if hasattr(f, "path") and f.path:
with open(f.path, "rb") as fh:
data = fh.read()
result = await api.admin_upload_media(token, data, f.name, ct)
if result:
uploaded += 1
except Exception:
pass
d.snack(self.page, f"Загружено файлов: {uploaded}")
await self._reload()