Compare commits
3 Commits
654a1e7191
...
37940d633a
Author | SHA1 | Date | |
---|---|---|---|
37940d633a | |||
b0f20680b4 | |||
cb3b5a3c27 |
37
desktop_client/create_folder_widget.py
Normal file
37
desktop_client/create_folder_widget.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import file_widgets
|
||||||
|
from PyQt6.QtWidgets import QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QWidget
|
||||||
|
from request_client import RequestClient
|
||||||
|
|
||||||
|
|
||||||
|
class CreateFolderWidget(QWidget):
|
||||||
|
def __init__(self, folder_id: uuid.UUID, file_list: file_widgets.FileListWidget):
|
||||||
|
super().__init__()
|
||||||
|
self.folder_id = folder_id
|
||||||
|
self.file_list = file_list
|
||||||
|
self.setWindowTitle("Folder creation")
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
self.edit = QLineEdit()
|
||||||
|
layout.addWidget(self.edit)
|
||||||
|
button = QPushButton("Submit")
|
||||||
|
button.clicked.connect(self.submit)
|
||||||
|
layout.addWidget(button)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
response = RequestClient().client.post(
|
||||||
|
"/folders",
|
||||||
|
json={
|
||||||
|
"folder_name": self.edit.text(),
|
||||||
|
"parent_folder_id": str(self.folder_id),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not response.is_success:
|
||||||
|
QMessageBox.warning(None, "Error creating folder", response.text)
|
||||||
|
else:
|
||||||
|
QMessageBox.information(None, "Folder created", "Folder created")
|
||||||
|
self.file_list.update_response()
|
||||||
|
self.close()
|
@ -1,23 +1,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Protocol, Self
|
from typing import Protocol, Self
|
||||||
|
|
||||||
|
import create_folder_widget
|
||||||
import httpx
|
import httpx
|
||||||
import pydantic
|
import pydantic
|
||||||
import state
|
import state
|
||||||
|
import user
|
||||||
from PyQt6.QtCore import QPoint, Qt
|
from PyQt6.QtCore import QPoint, Qt
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import QAction, QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon
|
||||||
QAction,
|
|
||||||
QDragEnterEvent,
|
|
||||||
QDragMoveEvent,
|
|
||||||
QDropEvent,
|
|
||||||
QIcon,
|
|
||||||
)
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
|
QFileDialog,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
|
QLabel,
|
||||||
QListWidget,
|
QListWidget,
|
||||||
QListWidgetItem,
|
QListWidgetItem,
|
||||||
QMenu,
|
QMenu,
|
||||||
@ -25,8 +24,6 @@ from PyQt6.QtWidgets import (
|
|||||||
QPushButton,
|
QPushButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
QLabel,
|
|
||||||
QFileDialog,
|
|
||||||
)
|
)
|
||||||
from request_client import RequestClient
|
from request_client import RequestClient
|
||||||
|
|
||||||
@ -60,15 +57,15 @@ class File(pydantic.BaseModel):
|
|||||||
return self.file_name
|
return self.file_name
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
RequestClient().client.delete(
|
RequestClient().client.delete("/files", params={"file_id": self.file_id})
|
||||||
"/files", params={"file_id": self.file_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
def details(self, list: FileListWidget) -> QWidget:
|
def details(self, list: FileListWidget) -> QWidget:
|
||||||
del list
|
del list
|
||||||
|
file_size = self._format_bytes(self.file_size)
|
||||||
|
file_size_text = f"{file_size[0]:.2f} {file_size[1]}"
|
||||||
details = (
|
details = (
|
||||||
f"file id: {self.file_id}\nfile_name: {self.file_name}\n"
|
f"file id: {self.file_id}\nfile_name: {self.file_name}\n"
|
||||||
+ f"file_size: {self._format_bytes(self.file_size)}\n"
|
+ f"file_size: {file_size_text}\n"
|
||||||
+ f"created at: {self.created_at}\nupdated at: {self.updated_at}"
|
+ f"created at: {self.created_at}\nupdated at: {self.updated_at}"
|
||||||
)
|
)
|
||||||
label = QLabel()
|
label = QLabel()
|
||||||
@ -80,7 +77,7 @@ class File(pydantic.BaseModel):
|
|||||||
def _format_bytes(size: int):
|
def _format_bytes(size: int):
|
||||||
power = 2**10
|
power = 2**10
|
||||||
n = 0
|
n = 0
|
||||||
power_labels = {0: "", 1: "kilo", 2: "mega", 3: "giga", 4: "tera"}
|
power_labels = {0: "", 1: "kibi", 2: "mebi", 3: "gibi", 4: "tebi"}
|
||||||
while size > power and n < 4:
|
while size > power and n < 4:
|
||||||
size /= power
|
size /= power
|
||||||
n += 1
|
n += 1
|
||||||
@ -117,8 +114,10 @@ class Folder(pydantic.BaseModel):
|
|||||||
return self.folder_name
|
return self.folder_name
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
RequestClient().client.delete(
|
print(
|
||||||
"/folders", params={"folder_id": self.folder_id}
|
RequestClient()
|
||||||
|
.client.delete("/folders", params={"folder_id": self.folder_id})
|
||||||
|
.text
|
||||||
)
|
)
|
||||||
|
|
||||||
def details(self, list: FileListWidget) -> QWidget:
|
def details(self, list: FileListWidget) -> QWidget:
|
||||||
@ -140,6 +139,9 @@ class ResponseProtocol(Protocol):
|
|||||||
def update(self) -> ResponseProtocol:
|
def update(self) -> ResponseProtocol:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def create_folder(self) -> QWidget | None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class ListResponse(pydantic.BaseModel):
|
class ListResponse(pydantic.BaseModel):
|
||||||
folder_id: uuid.UUID
|
folder_id: uuid.UUID
|
||||||
@ -161,6 +163,32 @@ class ListResponse(pydantic.BaseModel):
|
|||||||
def update(self) -> ResponseProtocol:
|
def update(self) -> ResponseProtocol:
|
||||||
return self.get(self.folder_id)
|
return self.get(self.folder_id)
|
||||||
|
|
||||||
|
def create_folder(self, file_list: FileListWidget):
|
||||||
|
return create_folder_widget.CreateFolderWidget(self.folder_id, file_list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(slots=True)
|
||||||
|
class TlpResponse:
|
||||||
|
folders: list[Folder]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get() -> Self:
|
||||||
|
url = "/permissions/get_top_level_permitted_folders"
|
||||||
|
return TlpResponse(
|
||||||
|
pydantic.TypeAdapter(list[Folder]).validate_json(
|
||||||
|
RequestClient().client.get(url).text
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def items(self) -> list[DisplayProtocol]:
|
||||||
|
return self.folders
|
||||||
|
|
||||||
|
def update(self) -> ResponseProtocol:
|
||||||
|
return self.get()
|
||||||
|
|
||||||
|
def create_folder(self):
|
||||||
|
return # Not much to do
|
||||||
|
|
||||||
|
|
||||||
class FileListWidget(QListWidget):
|
class FileListWidget(QListWidget):
|
||||||
def __init__(self, state: state.State):
|
def __init__(self, state: state.State):
|
||||||
@ -213,9 +241,7 @@ class FileListWidget(QListWidget):
|
|||||||
response = RequestClient().client.post(
|
response = RequestClient().client.post(
|
||||||
"http://localhost:3000/files",
|
"http://localhost:3000/files",
|
||||||
files=files,
|
files=files,
|
||||||
params={
|
params={"parent_folder": self.current_response().folder_id},
|
||||||
"parent_folder": self.current_response().folder_id
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if response.is_success:
|
if response.is_success:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
@ -271,26 +297,56 @@ class Sidebar(QWidget):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.state = state
|
self.state = state
|
||||||
self.file_list = file_list
|
self.file_list = file_list
|
||||||
|
self.user_widget = None
|
||||||
|
self.folder_widget = None
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
# Add your sidebar buttons here
|
buttons = [
|
||||||
for i in range(5): # Example buttons
|
("User info", self.get_user),
|
||||||
btn = QPushButton(f"Button {i+1}")
|
("Log out", self.logout),
|
||||||
layout.addWidget(btn)
|
("Go back", self.go_back),
|
||||||
|
("Go root", self.go_root),
|
||||||
|
("Get permitted", self.get_tlp),
|
||||||
|
("Sync", self.sync),
|
||||||
|
("Create folder", self.create_folder),
|
||||||
|
]
|
||||||
|
for text, func in buttons:
|
||||||
|
button = QPushButton(text)
|
||||||
|
button.clicked.connect(func)
|
||||||
|
layout.addWidget(button)
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
|
|
||||||
def get_user(self): ...
|
def get_user(self):
|
||||||
|
self.user_widget = user.UserWidget(self.state)
|
||||||
|
self.user_widget.show()
|
||||||
|
|
||||||
def logout(self): ...
|
def logout(self):
|
||||||
|
self.state.logout()
|
||||||
|
|
||||||
def go_back(self): ...
|
def go_back(self):
|
||||||
|
if len(self.file_list.responses) >= 2:
|
||||||
|
self.file_list.responses.pop()
|
||||||
|
self.file_list.update()
|
||||||
|
|
||||||
def go_root(self): ...
|
def go_root(self):
|
||||||
|
self.file_list.responses.append(ListResponse.get())
|
||||||
|
self.file_list.update()
|
||||||
|
|
||||||
def get_tlp(self):
|
def get_tlp(self):
|
||||||
"""Get top level permitted folders"""
|
"""Get top level permitted folders"""
|
||||||
|
self.file_list.responses.append(TlpResponse.get())
|
||||||
|
self.file_list.update()
|
||||||
|
|
||||||
def sync(self): ...
|
def sync(self):
|
||||||
|
# TODO
|
||||||
|
...
|
||||||
|
|
||||||
|
def create_folder(self):
|
||||||
|
self.folder_widget = self.file_list.current_response().create_folder(
|
||||||
|
self.file_list
|
||||||
|
)
|
||||||
|
if self.folder_widget is not None:
|
||||||
|
self.folder_widget.show()
|
||||||
|
|
||||||
|
|
||||||
class MainFileWidget(QWidget):
|
class MainFileWidget(QWidget):
|
||||||
|
@ -19,6 +19,10 @@ class RequestClient:
|
|||||||
def set_token(cls, token: str):
|
def set_token(cls, token: str):
|
||||||
cls._client.headers = {"Authorization": f"Bearer {token}"}
|
cls._client.headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_token(cls):
|
||||||
|
cls._client.headers = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client(self) -> httpx.Client:
|
def client(self) -> httpx.Client:
|
||||||
return self._client
|
return self._client
|
||||||
|
@ -27,8 +27,8 @@ class State(QMainWindow):
|
|||||||
password = keyring.get_credential("auth_app", "access_token")
|
password = keyring.get_credential("auth_app", "access_token")
|
||||||
if password is None:
|
if password is None:
|
||||||
self.switch_to_login() # Start with the login widget
|
self.switch_to_login() # Start with the login widget
|
||||||
|
else:
|
||||||
self.login(password.password)
|
self.login(password.password)
|
||||||
|
|
||||||
def switch_to_login(self):
|
def switch_to_login(self):
|
||||||
self.stack.setCurrentWidget(self.login_widget)
|
self.stack.setCurrentWidget(self.login_widget)
|
||||||
@ -42,3 +42,8 @@ class State(QMainWindow):
|
|||||||
self.file_widget = file_widgets.MainFileWidget(self)
|
self.file_widget = file_widgets.MainFileWidget(self)
|
||||||
self.stack.addWidget(self.file_widget)
|
self.stack.addWidget(self.file_widget)
|
||||||
self.stack.setCurrentWidget(self.file_widget)
|
self.stack.setCurrentWidget(self.file_widget)
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
keyring.delete_password("auth_app", "access_token")
|
||||||
|
request_client.RequestClient().delete_token()
|
||||||
|
self.switch_to_login()
|
||||||
|
97
desktop_client/user.py
Normal file
97
desktop_client/user.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
import state
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QHBoxLayout,
|
||||||
|
QLabel,
|
||||||
|
QLineEdit,
|
||||||
|
QMessageBox,
|
||||||
|
QPushButton,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
from request_client import RequestClient
|
||||||
|
|
||||||
|
|
||||||
|
class User(pydantic.BaseModel):
|
||||||
|
user_id: int | None = None
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(user_id: int | None = None) -> User:
|
||||||
|
params = {}
|
||||||
|
if user_id:
|
||||||
|
url = "/users"
|
||||||
|
params["user_id"] = user_id
|
||||||
|
else:
|
||||||
|
url = "/users/current"
|
||||||
|
return User.model_validate_json(
|
||||||
|
RequestClient().client.get(url, params=params).text
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete():
|
||||||
|
if not RequestClient().client.delete("/users").is_success:
|
||||||
|
raise Exception("Error deleting user")
|
||||||
|
|
||||||
|
def put(self):
|
||||||
|
response = RequestClient().client.put(
|
||||||
|
"/users", json={"username": self.username, "email": self.email}
|
||||||
|
)
|
||||||
|
if not response.is_success:
|
||||||
|
QMessageBox.warning(None, "Error updating user", response.text)
|
||||||
|
return
|
||||||
|
QMessageBox.information(None, "User updated", "User updated")
|
||||||
|
|
||||||
|
|
||||||
|
class UserSearch(User):
|
||||||
|
similarity: float
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def search(search_str: str) -> list[UserSearch]:
|
||||||
|
return pydantic.TypeAdapter(list[UserSearch]).validate_json(
|
||||||
|
RequestClient()
|
||||||
|
.client.get("/users/search", params={"search_string": search_str})
|
||||||
|
.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserWidget(QWidget):
|
||||||
|
def __init__(self, state: state.State):
|
||||||
|
super().__init__()
|
||||||
|
self.state = state
|
||||||
|
self.setWindowTitle("User information")
|
||||||
|
self.user = User.get()
|
||||||
|
main_layout = QVBoxLayout()
|
||||||
|
lines = [("username", self.user.username), ("email", self.user.email)]
|
||||||
|
edits = []
|
||||||
|
for line in lines:
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
label = QLabel(line[0])
|
||||||
|
edit = QLineEdit(line[1])
|
||||||
|
layout.addWidget(label)
|
||||||
|
layout.addWidget(edit)
|
||||||
|
main_layout.addLayout(layout)
|
||||||
|
edits.append(edit)
|
||||||
|
self.username: QLineEdit = edits[0]
|
||||||
|
self.email: QLineEdit = edits[1]
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
buttons = [("Update", self.update_user), ("Delete", self.delete_user)]
|
||||||
|
for text, func in buttons:
|
||||||
|
button = QPushButton(text)
|
||||||
|
button.clicked.connect(func)
|
||||||
|
button_layout.addWidget(button)
|
||||||
|
main_layout.addLayout(button_layout)
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
def delete_user(self):
|
||||||
|
User.delete()
|
||||||
|
self.close()
|
||||||
|
self.state.logout()
|
||||||
|
|
||||||
|
def update_user(self):
|
||||||
|
self.user.username = self.username.text()
|
||||||
|
self.user.email = self.email.text()
|
||||||
|
self.user.put()
|
Reference in New Issue
Block a user