test ferst
This commit is contained in:
217
web/views/media_library.py
Normal file
217
web/views/media_library.py
Normal file
@@ -0,0 +1,217 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user