Merge branch 'main' of https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate
This commit is contained in:
commit
342500bc79
@ -1,8 +0,0 @@
|
|||||||
PYTHON_PATH=path_to_python
|
|
||||||
EMAIL_ADDRESS=example@gmail.com
|
|
||||||
EMAIL_PASSWORD=ztgzegzeg
|
|
||||||
EMAIL_SMTP_SERVER=smtp.gmail.com
|
|
||||||
EMAIL_SMTP_PORT=587
|
|
||||||
LICENSE_API_URL=url
|
|
||||||
PURCHASE_URL=url
|
|
||||||
LICENSE_API_KEY=key
|
|
||||||
279
app/ui/layouts/flow_layout.py
Normal file
279
app/ui/layouts/flow_layout.py
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QRect, QSize, QPoint, QMargins
|
||||||
|
from PyQt6.QtWidgets import QLayout
|
||||||
|
|
||||||
|
|
||||||
|
class JustifyMode(Enum):
|
||||||
|
NONE = 0
|
||||||
|
SPACE_BETWEEN = 1
|
||||||
|
SPACE_AROUND = 2
|
||||||
|
SPACE_EVENLY = 3
|
||||||
|
CENTER = 4
|
||||||
|
|
||||||
|
|
||||||
|
class LastRowMode(Enum):
|
||||||
|
LEFT = 0
|
||||||
|
JUSTIFY = 1
|
||||||
|
MATCH_PREVIOUS = 2
|
||||||
|
CENTER = 3
|
||||||
|
|
||||||
|
|
||||||
|
class FlowLayout(QLayout):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent=None,
|
||||||
|
justify_mode=JustifyMode.SPACE_BETWEEN,
|
||||||
|
last_row_mode=LastRowMode.LEFT,
|
||||||
|
):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
if parent is not None:
|
||||||
|
self.setContentsMargins(QMargins(0, 0, 0, 0))
|
||||||
|
|
||||||
|
self._items = []
|
||||||
|
self.justify_mode = justify_mode
|
||||||
|
self.last_row_mode = last_row_mode
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# QLayout API
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def addItem(self, item):
|
||||||
|
self._items.append(item)
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return len(self._items)
|
||||||
|
|
||||||
|
def itemAt(self, index):
|
||||||
|
if 0 <= index < len(self._items):
|
||||||
|
return self._items[index]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def takeAt(self, index):
|
||||||
|
if 0 <= index < len(self._items):
|
||||||
|
return self._items.pop(index)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def expandingDirections(self):
|
||||||
|
return Qt.Orientation(0)
|
||||||
|
|
||||||
|
def hasHeightForWidth(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def heightForWidth(self, width):
|
||||||
|
return self._do_layout(QRect(0, 0, width, 0), True)
|
||||||
|
|
||||||
|
def setGeometry(self, rect):
|
||||||
|
super().setGeometry(rect)
|
||||||
|
self._do_layout(rect, False)
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return self.minimumSize()
|
||||||
|
|
||||||
|
def minimumSize(self):
|
||||||
|
size = QSize()
|
||||||
|
|
||||||
|
for item in self._items:
|
||||||
|
size = size.expandedTo(item.minimumSize())
|
||||||
|
|
||||||
|
margins = self.contentsMargins()
|
||||||
|
|
||||||
|
size += QSize(
|
||||||
|
margins.left() + margins.right(),
|
||||||
|
margins.top() + margins.bottom(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Internal
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _build_rows(self, available_width):
|
||||||
|
rows = []
|
||||||
|
|
||||||
|
current_row = []
|
||||||
|
current_width = 0
|
||||||
|
|
||||||
|
base_spacing = max(0, self.spacing())
|
||||||
|
|
||||||
|
for item in self._items:
|
||||||
|
width = item.sizeHint().width()
|
||||||
|
|
||||||
|
if not current_row:
|
||||||
|
current_row.append(item)
|
||||||
|
current_width = width
|
||||||
|
continue
|
||||||
|
|
||||||
|
projected = current_width + base_spacing + width
|
||||||
|
|
||||||
|
if projected > available_width:
|
||||||
|
rows.append(current_row)
|
||||||
|
|
||||||
|
current_row = [item]
|
||||||
|
current_width = width
|
||||||
|
else:
|
||||||
|
current_row.append(item)
|
||||||
|
current_width = projected
|
||||||
|
|
||||||
|
if current_row:
|
||||||
|
rows.append(current_row)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def _layout_row(
|
||||||
|
self,
|
||||||
|
row,
|
||||||
|
rect,
|
||||||
|
y,
|
||||||
|
test_only,
|
||||||
|
justify_mode,
|
||||||
|
spacing_override=None,
|
||||||
|
):
|
||||||
|
widths = [item.sizeHint().width() for item in row]
|
||||||
|
heights = [item.sizeHint().height() for item in row]
|
||||||
|
|
||||||
|
total_width = sum(widths)
|
||||||
|
row_height = max(heights)
|
||||||
|
|
||||||
|
count = len(row)
|
||||||
|
gaps = max(0, count - 1)
|
||||||
|
|
||||||
|
available_width = rect.width()
|
||||||
|
|
||||||
|
x = rect.x()
|
||||||
|
|
||||||
|
used_spacing = self.spacing()
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# Explicit spacing override (MATCH_PREVIOUS)
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
|
||||||
|
if spacing_override is not None:
|
||||||
|
used_spacing = spacing_override
|
||||||
|
|
||||||
|
for item, w, h in zip(row, widths, heights):
|
||||||
|
if not test_only:
|
||||||
|
item.setGeometry(QRect(int(round(x)), y, w, h))
|
||||||
|
|
||||||
|
x += w + used_spacing
|
||||||
|
|
||||||
|
return row_height, used_spacing
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# LEFT / NONE
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
|
||||||
|
if justify_mode == JustifyMode.NONE:
|
||||||
|
used_spacing = max(0, self.spacing())
|
||||||
|
|
||||||
|
for item, w, h in zip(row, widths, heights):
|
||||||
|
if not test_only:
|
||||||
|
item.setGeometry(QRect(int(round(x)), y, w, h))
|
||||||
|
|
||||||
|
x += w + used_spacing
|
||||||
|
|
||||||
|
return row_height, used_spacing
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# CENTER
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
|
||||||
|
if justify_mode == JustifyMode.CENTER:
|
||||||
|
used_spacing = max(0, self.spacing())
|
||||||
|
|
||||||
|
content_width = total_width
|
||||||
|
|
||||||
|
if count > 1:
|
||||||
|
content_width += gaps * used_spacing
|
||||||
|
|
||||||
|
x += max(0, (available_width - content_width) / 2)
|
||||||
|
|
||||||
|
for item, w, h in zip(row, widths, heights):
|
||||||
|
if not test_only:
|
||||||
|
item.setGeometry(QRect(int(round(x)), y, w, h))
|
||||||
|
|
||||||
|
x += w + used_spacing
|
||||||
|
|
||||||
|
return row_height, used_spacing
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# SPACE_*
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
|
||||||
|
free_space = max(0, available_width - total_width)
|
||||||
|
|
||||||
|
if justify_mode == JustifyMode.SPACE_BETWEEN:
|
||||||
|
if gaps > 0:
|
||||||
|
used_spacing = free_space / gaps
|
||||||
|
else:
|
||||||
|
used_spacing = 0
|
||||||
|
|
||||||
|
elif justify_mode == JustifyMode.SPACE_AROUND:
|
||||||
|
used_spacing = free_space / count if count else 0
|
||||||
|
x += used_spacing / 2
|
||||||
|
|
||||||
|
elif justify_mode == JustifyMode.SPACE_EVENLY:
|
||||||
|
used_spacing = free_space / (count + 1) if count else 0
|
||||||
|
x += used_spacing
|
||||||
|
|
||||||
|
for item, w, h in zip(row, widths, heights):
|
||||||
|
if not test_only:
|
||||||
|
item.setGeometry(QRect(int(round(x)), y, w, h))
|
||||||
|
|
||||||
|
x += w + used_spacing
|
||||||
|
|
||||||
|
return row_height, used_spacing
|
||||||
|
|
||||||
|
def _do_layout(self, rect, test_only):
|
||||||
|
rows = self._build_rows(rect.width())
|
||||||
|
|
||||||
|
y = rect.y()
|
||||||
|
|
||||||
|
vertical_spacing = max(0, self.spacing())
|
||||||
|
|
||||||
|
previous_spacing = None
|
||||||
|
|
||||||
|
for row_index, row in enumerate(rows):
|
||||||
|
is_last_row = row_index == len(rows) - 1
|
||||||
|
|
||||||
|
spacing_override = None
|
||||||
|
row_mode = self.justify_mode
|
||||||
|
|
||||||
|
if is_last_row:
|
||||||
|
|
||||||
|
if self.last_row_mode == LastRowMode.LEFT:
|
||||||
|
row_mode = JustifyMode.NONE
|
||||||
|
|
||||||
|
elif self.last_row_mode == LastRowMode.CENTER:
|
||||||
|
row_mode = JustifyMode.CENTER
|
||||||
|
|
||||||
|
elif self.last_row_mode == LastRowMode.JUSTIFY:
|
||||||
|
row_mode = self.justify_mode
|
||||||
|
|
||||||
|
elif (
|
||||||
|
self.last_row_mode == LastRowMode.MATCH_PREVIOUS
|
||||||
|
and previous_spacing is not None
|
||||||
|
):
|
||||||
|
spacing_override = previous_spacing
|
||||||
|
row_mode = JustifyMode.NONE
|
||||||
|
|
||||||
|
row_height, used_spacing = self._layout_row(
|
||||||
|
row=row,
|
||||||
|
rect=rect,
|
||||||
|
y=y,
|
||||||
|
test_only=test_only,
|
||||||
|
justify_mode=row_mode,
|
||||||
|
spacing_override=spacing_override,
|
||||||
|
)
|
||||||
|
|
||||||
|
previous_spacing = used_spacing
|
||||||
|
|
||||||
|
y += row_height + vertical_spacing
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
y -= vertical_spacing
|
||||||
|
|
||||||
|
return y - rect.y()
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel
|
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget
|
||||||
from PyQt6.QtGui import QResizeEvent, QCloseEvent
|
from PyQt6.QtGui import QResizeEvent, QCloseEvent
|
||||||
from PyQt6.QtCore import QSize, QEvent
|
from PyQt6.QtCore import QSize, QEvent
|
||||||
from app.core.main_manager import MainManager, NotificationType
|
from app.core.main_manager import MainManager, NotificationType
|
||||||
@ -6,6 +6,7 @@ from app.ui.widgets.tabs_widget import TabsWidget, MenuDirection, ButtonPosition
|
|||||||
from app.ui.windows.settings_window import SettingsWindow
|
from app.ui.windows.settings_window import SettingsWindow
|
||||||
from app.ui.windows.suggestion_window import SuggestionWindow
|
from app.ui.windows.suggestion_window import SuggestionWindow
|
||||||
from app.ui.windows.activation_window import ActivationWindow
|
from app.ui.windows.activation_window import ActivationWindow
|
||||||
|
from app.ui.windows.editing_window import EditingWindow
|
||||||
import app.utils.paths as paths, shutil
|
import app.utils.paths as paths, shutil
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@ -202,6 +203,9 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
for widget_type in widget_types:
|
for widget_type in widget_types:
|
||||||
for child in widget.findChildren(widget_type):
|
for child in widget.findChildren(widget_type):
|
||||||
|
|
||||||
|
if child.accessibleName() == "ignore":
|
||||||
|
continue # Ignorer les widgets marqués pour être ignorés
|
||||||
widget_id = id(child)
|
widget_id = id(child)
|
||||||
|
|
||||||
# Récupérer la taille de base
|
# Récupérer la taille de base
|
||||||
@ -249,6 +253,10 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self.side_menu = TabsWidget(self, MenuDirection.HORIZONTAL, 70, None, 10, BorderSide.BOTTOM, TabSide.TOP)
|
self.side_menu = TabsWidget(self, MenuDirection.HORIZONTAL, 70, None, 10, BorderSide.BOTTOM, TabSide.TOP)
|
||||||
|
|
||||||
|
self.editing_window = EditingWindow(self)
|
||||||
|
self.side_menu.add_widget(self.editing_window, self.language_manager.get_text("tab_editing"), paths.get_asset_svg_path("edit"), position=ButtonPosition.CENTER, text_position=TextPosition.BOTTOM)
|
||||||
|
|
||||||
|
|
||||||
self.suggestion_window = SuggestionWindow(self)
|
self.suggestion_window = SuggestionWindow(self)
|
||||||
self.side_menu.add_widget(self.suggestion_window, self.language_manager.get_text("tab_suggestions"), paths.get_asset_svg_path("suggestion"), position=ButtonPosition.CENTER, text_position=TextPosition.BOTTOM)
|
self.side_menu.add_widget(self.suggestion_window, self.language_manager.get_text("tab_suggestions"), paths.get_asset_svg_path("suggestion"), position=ButtonPosition.CENTER, text_position=TextPosition.BOTTOM)
|
||||||
|
|
||||||
|
|||||||
48
app/ui/widgets/grid_element.py
Normal file
48
app/ui/widgets/grid_element.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
||||||
|
from PyQt6.QtGui import QPixmap
|
||||||
|
|
||||||
|
|
||||||
|
class GridElement(QWidget):
|
||||||
|
def __init__(self, parent: QWidget = None, name: str = None, image: str = "", linked_object: object = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.image = QPixmap(image)
|
||||||
|
self.image_path = image
|
||||||
|
self.name = name
|
||||||
|
self.linked_object = linked_object
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self) -> None:
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
self.image_label = QLabel(self)
|
||||||
|
self.image_label.setPixmap(self.image)
|
||||||
|
self.name_label = QLabel(self.name, self)
|
||||||
|
self.name_label.setAccessibleName("ignore")
|
||||||
|
self.name_label.setObjectName("text_label")
|
||||||
|
layout.addWidget(self.image_label)
|
||||||
|
layout.addWidget(self.name_label)
|
||||||
|
self.set_image_size(70, 70) # Set default image size
|
||||||
|
def set_image(self, image: str) -> None:
|
||||||
|
self.image = QPixmap(image)
|
||||||
|
self.image_path = image
|
||||||
|
|
||||||
|
def set_name(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def set_linked_object(self, linked_object: object) -> None:
|
||||||
|
self.linked_object = linked_object
|
||||||
|
|
||||||
|
def set_image_size(self, width: int, height: int) -> None:
|
||||||
|
self.image = self.image.scaled(width, height)
|
||||||
|
self.image_label.setPixmap(self.image)
|
||||||
|
|
||||||
|
def get_image(self) -> QPixmap:
|
||||||
|
return self.image
|
||||||
|
|
||||||
|
def get_image_path(self) -> str:
|
||||||
|
return self.image_path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_linked_object(self) -> object:
|
||||||
|
return self.linked_object
|
||||||
30
app/ui/widgets/grid_widget.py
Normal file
30
app/ui/widgets/grid_widget.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from PyQt6.QtWidgets import QWidget
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from app.ui.layouts.flow_layout import FlowLayout, JustifyMode, LastRowMode
|
||||||
|
from app.ui.widgets.grid_element import GridElement
|
||||||
|
class GridWidget(QWidget):
|
||||||
|
def __init__(self, elements: list[GridElement] = [], parent=None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.elements = elements
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self) -> None:
|
||||||
|
layout = FlowLayout(self, justify_mode=JustifyMode.SPACE_BETWEEN, last_row_mode=LastRowMode.MATCH_PREVIOUS)
|
||||||
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
if self.elements:
|
||||||
|
for element in self.elements:
|
||||||
|
layout.addWidget(element)
|
||||||
|
else:
|
||||||
|
for _ in range(5000): # Add 58 placeholder elements
|
||||||
|
layout.addWidget(GridElement(self, name="Placeholder", image="data/assets/icon.png"))
|
||||||
|
|
||||||
|
|
||||||
|
def add_element(self, element: GridElement) -> None:
|
||||||
|
layout: FlowLayout = self.layout()
|
||||||
|
layout.addWidget(element)
|
||||||
|
|
||||||
|
def remove_element(self, element: GridElement) -> None:
|
||||||
|
layout: FlowLayout = self.layout()
|
||||||
|
layout.removeWidget(element)
|
||||||
|
element.setParent(None)
|
||||||
31
app/ui/windows/editing_window.py
Normal file
31
app/ui/windows/editing_window.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from PyQt6.QtWidgets import QScrollArea, QWidget, QHBoxLayout
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from app.ui.widgets.grid_widget import GridWidget
|
||||||
|
from app.core.main_manager import MainManager, NotificationType
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class EditingWindow(QWidget):
|
||||||
|
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.main_manager: MainManager = MainManager.get_instance()
|
||||||
|
self.language_manager = self.main_manager.get_language_manager()
|
||||||
|
self.settings_manager = self.main_manager.get_settings_manager()
|
||||||
|
self.theme_manager = self.main_manager.get_theme_manager()
|
||||||
|
|
||||||
|
self.observer_manager = self.main_manager.get_observer_manager()
|
||||||
|
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self) -> None:
|
||||||
|
layout = QHBoxLayout(self)
|
||||||
|
scroll_area = QScrollArea(self)
|
||||||
|
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
grid_widget = GridWidget(parent=self)
|
||||||
|
scroll_area.setWidget(grid_widget)
|
||||||
|
layout.addWidget(scroll_area)
|
||||||
|
|
||||||
|
|
||||||
|
def update_language(self) -> None:
|
||||||
|
return
|
||||||
@ -14,14 +14,6 @@ class SettingsWindow(QWidget):
|
|||||||
self.observer_manager = self.main_manager.get_observer_manager()
|
self.observer_manager = self.main_manager.get_observer_manager()
|
||||||
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||||
|
|
||||||
# Type hints for UI elements
|
|
||||||
self.language_layout: QHBoxLayout
|
|
||||||
self.languageLabel: QLabel
|
|
||||||
self.languageCombo: QComboBox
|
|
||||||
self.theme_layout: QHBoxLayout
|
|
||||||
self.themeLabel: QLabel
|
|
||||||
self.themeCombo: QComboBox
|
|
||||||
|
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def setup_ui(self) -> None:
|
def setup_ui(self) -> None:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app_name": "Application",
|
"app_name": "Youtube MP3 Downloader",
|
||||||
"app_os": "Windows",
|
"app_os": "Windows",
|
||||||
"app_version": "1.0.0",
|
"app_version": "1.0.0",
|
||||||
"architecture": "x64",
|
"architecture": "x64",
|
||||||
@ -7,7 +7,7 @@
|
|||||||
"splash_image": "splash",
|
"splash_image": "splash",
|
||||||
"main_script": "main.py",
|
"main_script": "main.py",
|
||||||
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate",
|
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate",
|
||||||
"enable_licensing": true,
|
"enable_licensing": false,
|
||||||
"features_by_license": {
|
"features_by_license": {
|
||||||
"basic": [
|
"basic": [
|
||||||
"support"
|
"support"
|
||||||
|
|||||||
1
data/assets/edit.svg
Normal file
1
data/assets/edit.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M21.2799 6.40005L11.7399 15.94C10.7899 16.89 7.96987 17.33 7.33987 16.7C6.70987 16.07 7.13987 13.25 8.08987 12.3L17.6399 2.75002C17.8754 2.49308 18.1605 2.28654 18.4781 2.14284C18.7956 1.99914 19.139 1.92124 19.4875 1.9139C19.8359 1.90657 20.1823 1.96991 20.5056 2.10012C20.8289 2.23033 21.1225 2.42473 21.3686 2.67153C21.6147 2.91833 21.8083 3.21243 21.9376 3.53609C22.0669 3.85976 22.1294 4.20626 22.1211 4.55471C22.1128 4.90316 22.0339 5.24635 21.8894 5.5635C21.7448 5.88065 21.5375 6.16524 21.2799 6.40005V6.40005Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M11 4H6C4.93913 4 3.92178 4.42142 3.17163 5.17157C2.42149 5.92172 2 6.93913 2 8V18C2 19.0609 2.42149 20.0783 3.17163 20.8284C3.92178 21.5786 4.93913 22 6 22H17C19.21 22 20 20.2 20 18V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
x
Reference in New Issue
Block a user