Expanshion
This commit is contained in:
parent
ff629b8903
commit
654a1e7191
BIN
assets/file.png
Normal file
BIN
assets/file.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 588 KiB |
BIN
assets/folder.png
Normal file
BIN
assets/folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
@ -3,13 +3,19 @@ from __future__ import annotations
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Protocol
|
from typing import Protocol, Self
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pydantic
|
import pydantic
|
||||||
import state
|
import state
|
||||||
from PyQt6.QtCore import QPoint, Qt
|
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 (
|
from PyQt6.QtWidgets import (
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QListWidget,
|
QListWidget,
|
||||||
@ -19,6 +25,8 @@ from PyQt6.QtWidgets import (
|
|||||||
QPushButton,
|
QPushButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
QLabel,
|
||||||
|
QFileDialog,
|
||||||
)
|
)
|
||||||
from request_client import RequestClient
|
from request_client import RequestClient
|
||||||
|
|
||||||
@ -30,7 +38,13 @@ class DisplayProtocol(Protocol):
|
|||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
raise NotImplementedError
|
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
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@ -46,11 +60,51 @@ class File(pydantic.BaseModel):
|
|||||||
return self.file_name
|
return self.file_name
|
||||||
|
|
||||||
def delete(self) -> None:
|
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:
|
def details(self, list: FileListWidget) -> QWidget:
|
||||||
# TODO
|
del list
|
||||||
raise NotImplementedError
|
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):
|
class Folder(pydantic.BaseModel):
|
||||||
@ -63,12 +117,29 @@ class Folder(pydantic.BaseModel):
|
|||||||
return self.folder_name
|
return self.folder_name
|
||||||
|
|
||||||
def delete(self) -> None:
|
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
|
# TODO
|
||||||
raise NotImplementedError
|
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):
|
class ListResponse(pydantic.BaseModel):
|
||||||
folder_id: uuid.UUID
|
folder_id: uuid.UUID
|
||||||
@ -78,6 +149,18 @@ class ListResponse(pydantic.BaseModel):
|
|||||||
def items(self) -> list[DisplayProtocol]:
|
def items(self) -> list[DisplayProtocol]:
|
||||||
return self.files + self.folders
|
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):
|
class FileListWidget(QListWidget):
|
||||||
def __init__(self, state: state.State):
|
def __init__(self, state: state.State):
|
||||||
@ -86,9 +169,16 @@ class FileListWidget(QListWidget):
|
|||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
|
self.itemDoubleClicked.connect(self.show_item)
|
||||||
|
self.details_widget = None
|
||||||
|
|
||||||
self.responses: list[ListResponse] = []
|
self.responses: list[ResponseProtocol] = [ListResponse.get()]
|
||||||
self.update_response()
|
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:
|
def current_response(self) -> ListResponse:
|
||||||
if not self.responses:
|
if not self.responses:
|
||||||
@ -96,17 +186,7 @@ class FileListWidget(QListWidget):
|
|||||||
return self.responses[-1]
|
return self.responses[-1]
|
||||||
|
|
||||||
def update_response(self):
|
def update_response(self):
|
||||||
if not self.responses:
|
self.responses[-1] = self.responses[-1].update()
|
||||||
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.update()
|
self.update()
|
||||||
|
|
||||||
def dragEnterEvent(self, event: QDragEnterEvent):
|
def dragEnterEvent(self, event: QDragEnterEvent):
|
||||||
@ -133,7 +213,9 @@ class FileListWidget(QListWidget):
|
|||||||
response = RequestClient().client.post(
|
response = RequestClient().client.post(
|
||||||
"http://localhost:3000/files",
|
"http://localhost:3000/files",
|
||||||
files=files,
|
files=files,
|
||||||
params={"parent_folder": self.current_response().folder_id},
|
params={
|
||||||
|
"parent_folder": self.current_response().folder_id
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if response.is_success:
|
if response.is_success:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
@ -147,10 +229,10 @@ class FileListWidget(QListWidget):
|
|||||||
except httpx.HTTPError as e:
|
except httpx.HTTPError as e:
|
||||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||||
|
|
||||||
def add_file_item(self, file_name):
|
def add_item(self, item: DisplayProtocol):
|
||||||
item = QListWidgetItem(file_name)
|
widget = QListWidgetItem(item.name())
|
||||||
item.setIcon(QIcon.fromTheme("text-x-generic")) # File icon
|
widget.setIcon(item.icon())
|
||||||
self.addItem(item)
|
self.addItem(widget)
|
||||||
|
|
||||||
def show_context_menu(self, pos: QPoint):
|
def show_context_menu(self, pos: QPoint):
|
||||||
item = self.itemAt(pos)
|
item = self.itemAt(pos)
|
||||||
@ -165,8 +247,10 @@ class FileListWidget(QListWidget):
|
|||||||
menu.exec(self.mapToGlobal(pos))
|
menu.exec(self.mapToGlobal(pos))
|
||||||
|
|
||||||
def show_details(self, item):
|
def show_details(self, item):
|
||||||
file_name = item.text()
|
row = self.row(item)
|
||||||
QMessageBox.information(self, "Details", f"Details for {file_name}")
|
item = self.current_response().items()[row]
|
||||||
|
self.details_widget = item.details(self)
|
||||||
|
self.details_widget.show()
|
||||||
|
|
||||||
def delete_item(self, item):
|
def delete_item(self, item):
|
||||||
row = self.row(item)
|
row = self.row(item)
|
||||||
@ -179,13 +263,14 @@ class FileListWidget(QListWidget):
|
|||||||
self.clear()
|
self.clear()
|
||||||
last = self.responses[-1]
|
last = self.responses[-1]
|
||||||
for item in last.items():
|
for item in last.items():
|
||||||
self.add_file_item(item.name())
|
self.add_item(item)
|
||||||
|
|
||||||
|
|
||||||
class Sidebar(QWidget):
|
class Sidebar(QWidget):
|
||||||
def __init__(self, state: state.State):
|
def __init__(self, state: state.State, file_list: FileListWidget):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.state = state
|
self.state = state
|
||||||
|
self.file_list = file_list
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
# Add your sidebar buttons here
|
# Add your sidebar buttons here
|
||||||
@ -194,14 +279,27 @@ class Sidebar(QWidget):
|
|||||||
layout.addWidget(btn)
|
layout.addWidget(btn)
|
||||||
layout.addStretch()
|
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):
|
class MainFileWidget(QWidget):
|
||||||
def __init__(self, state: state.State):
|
def __init__(self, state: state.State):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.state = state
|
self.state = state
|
||||||
layout = QHBoxLayout()
|
layout = QHBoxLayout()
|
||||||
self.sidebar = Sidebar(state)
|
|
||||||
self.file_list = FileListWidget(state)
|
self.file_list = FileListWidget(state)
|
||||||
|
self.sidebar = Sidebar(state, self.file_list)
|
||||||
layout.addWidget(self.sidebar)
|
layout.addWidget(self.sidebar)
|
||||||
layout.addWidget(self.file_list)
|
layout.addWidget(self.file_list)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
Reference in New Issue
Block a user