Expanshion

This commit is contained in:
StNicolay 2024-08-09 20:33:39 +03:00
parent ff629b8903
commit 654a1e7191
Signed by: StNicolay
GPG Key ID: 9693D04DCD962B0D
3 changed files with 130 additions and 32 deletions

BIN
assets/file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

BIN
assets/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -3,13 +3,19 @@ from __future__ import annotations
import datetime
import os
import uuid
from typing import Protocol
from typing import Protocol, Self
import httpx
import pydantic
import state
from PyQt6.QtCore import QPoint, Qt
from PyQt6.QtGui import QAction, QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon
from PyQt6.QtGui import (
QAction,
QDragEnterEvent,
QDragMoveEvent,
QDropEvent,
QIcon,
)
from PyQt6.QtWidgets import (
QHBoxLayout,
QListWidget,
@ -19,6 +25,8 @@ from PyQt6.QtWidgets import (
QPushButton,
QVBoxLayout,
QWidget,
QLabel,
QFileDialog,
)
from request_client import RequestClient
@ -30,7 +38,13 @@ class DisplayProtocol(Protocol):
def delete(self) -> None:
raise NotImplementedError
def details(self) -> QWidget:
def details(self, list: FileListWidget) -> QWidget:
raise NotImplementedError
def icon(self) -> QIcon:
raise NotImplementedError
def double_click(self, list: FileListWidget) -> None:
raise NotImplementedError
@ -46,11 +60,51 @@ class File(pydantic.BaseModel):
return self.file_name
def delete(self) -> None:
RequestClient().client.delete("/files", params={"file_id": self.file_id})
RequestClient().client.delete(
"/files", params={"file_id": self.file_id}
)
def details_button(self) -> QPushButton:
# TODO
raise NotImplementedError
def details(self, list: FileListWidget) -> QWidget:
del list
details = (
f"file id: {self.file_id}\nfile_name: {self.file_name}\n"
+ f"file_size: {self._format_bytes(self.file_size)}\n"
+ f"created at: {self.created_at}\nupdated at: {self.updated_at}"
)
label = QLabel()
label.setWindowTitle("File info")
label.setText(details)
return label
@staticmethod
def _format_bytes(size: int):
power = 2**10
n = 0
power_labels = {0: "", 1: "kilo", 2: "mega", 3: "giga", 4: "tera"}
while size > power and n < 4:
size /= power
n += 1
return size, power_labels[n] + "bytes"
def icon(self) -> QIcon:
return QIcon("assets/file.png")
def double_click(self, list: FileListWidget) -> None:
location = QFileDialog.getExistingDirectory(
list, caption="Select save location"
)
if not location:
return
with open(
os.path.join(location, self.file_name), "wb"
) as f, RequestClient().client.stream(
"GET", "/files", params={"file_id": self.file_id}
) as stream:
if not stream.is_success:
QMessageBox.warning(list, "Error downloading the file")
return
for data in stream.iter_bytes():
f.write(data)
class Folder(pydantic.BaseModel):
@ -63,12 +117,29 @@ class Folder(pydantic.BaseModel):
return self.folder_name
def delete(self) -> None:
RequestClient().client.delete("/folders", params={"folder_id": self.folder_id})
RequestClient().client.delete(
"/folders", params={"folder_id": self.folder_id}
)
def details_button(self) -> QPushButton:
def details(self, list: FileListWidget) -> QWidget:
# TODO
raise NotImplementedError
def icon(self) -> QIcon:
return QIcon("assets/folder.png")
def double_click(self, list: FileListWidget) -> None:
list.responses.append(ListResponse.get(self.folder_id))
list.update()
class ResponseProtocol(Protocol):
def items(self) -> list[DisplayProtocol]:
raise NotImplementedError
def update(self) -> ResponseProtocol:
raise NotImplementedError
class ListResponse(pydantic.BaseModel):
folder_id: uuid.UUID
@ -78,6 +149,18 @@ class ListResponse(pydantic.BaseModel):
def items(self) -> list[DisplayProtocol]:
return self.files + self.folders
@classmethod
def get(cls, folder_id: uuid.UUID | None = None) -> Self:
params = {}
if folder_id:
params["folder_id"] = folder_id
return ListResponse.model_validate_json(
RequestClient().client.get("/folders", params=params).text
)
def update(self) -> ResponseProtocol:
return self.get(self.folder_id)
class FileListWidget(QListWidget):
def __init__(self, state: state.State):
@ -86,9 +169,16 @@ class FileListWidget(QListWidget):
self.setAcceptDrops(True)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)
self.itemDoubleClicked.connect(self.show_item)
self.details_widget = None
self.responses: list[ListResponse] = []
self.update_response()
self.responses: list[ResponseProtocol] = [ListResponse.get()]
self.update()
def show_item(self, qitem: QListWidgetItem) -> None:
row = self.row(qitem)
item = self.current_response().items()[row]
item.double_click(self)
def current_response(self) -> ListResponse:
if not self.responses:
@ -96,17 +186,7 @@ class FileListWidget(QListWidget):
return self.responses[-1]
def update_response(self):
if not self.responses:
response = ListResponse.model_validate_json(
RequestClient().client.get("/folders").text
)
self.responses.append(response)
print(response.files)
self.responses[-1] = ListResponse.model_validate_json(
RequestClient()
.client.get("/folders", params={"folder_id": self.responses[-1].folder_id})
.text
)
self.responses[-1] = self.responses[-1].update()
self.update()
def dragEnterEvent(self, event: QDragEnterEvent):
@ -133,7 +213,9 @@ class FileListWidget(QListWidget):
response = RequestClient().client.post(
"http://localhost:3000/files",
files=files,
params={"parent_folder": self.current_response().folder_id},
params={
"parent_folder": self.current_response().folder_id
},
)
if response.is_success:
QMessageBox.information(
@ -147,10 +229,10 @@ class FileListWidget(QListWidget):
except httpx.HTTPError as e:
QMessageBox.critical(self, "HTTP Error", str(e))
def add_file_item(self, file_name):
item = QListWidgetItem(file_name)
item.setIcon(QIcon.fromTheme("text-x-generic")) # File icon
self.addItem(item)
def add_item(self, item: DisplayProtocol):
widget = QListWidgetItem(item.name())
widget.setIcon(item.icon())
self.addItem(widget)
def show_context_menu(self, pos: QPoint):
item = self.itemAt(pos)
@ -165,8 +247,10 @@ class FileListWidget(QListWidget):
menu.exec(self.mapToGlobal(pos))
def show_details(self, item):
file_name = item.text()
QMessageBox.information(self, "Details", f"Details for {file_name}")
row = self.row(item)
item = self.current_response().items()[row]
self.details_widget = item.details(self)
self.details_widget.show()
def delete_item(self, item):
row = self.row(item)
@ -179,13 +263,14 @@ class FileListWidget(QListWidget):
self.clear()
last = self.responses[-1]
for item in last.items():
self.add_file_item(item.name())
self.add_item(item)
class Sidebar(QWidget):
def __init__(self, state: state.State):
def __init__(self, state: state.State, file_list: FileListWidget):
super().__init__()
self.state = state
self.file_list = file_list
layout = QVBoxLayout()
self.setLayout(layout)
# Add your sidebar buttons here
@ -194,14 +279,27 @@ class Sidebar(QWidget):
layout.addWidget(btn)
layout.addStretch()
def get_user(self): ...
def logout(self): ...
def go_back(self): ...
def go_root(self): ...
def get_tlp(self):
"""Get top level permitted folders"""
def sync(self): ...
class MainFileWidget(QWidget):
def __init__(self, state: state.State):
super().__init__()
self.state = state
layout = QHBoxLayout()
self.sidebar = Sidebar(state)
self.file_list = FileListWidget(state)
self.sidebar = Sidebar(state, self.file_list)
layout.addWidget(self.sidebar)
layout.addWidget(self.file_list)
self.setLayout(layout)