develop #1
| @@ -8,6 +8,8 @@ A template project for Python with Gtk applications. | |||||||
| * sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy) | * sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy) | ||||||
|  |  | ||||||
| ### Note | ### Note | ||||||
|  | * Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered. | ||||||
|  | * In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> . | ||||||
| There are a "\<change_me\>" strings and files that need to be set according to your app's name located at: | There are a "\<change_me\>" strings and files that need to be set according to your app's name located at: | ||||||
| * \_\_builtins\_\_.py | * \_\_builtins\_\_.py | ||||||
| * user_config/bin/app_name | * user_config/bin/app_name | ||||||
|   | |||||||
| @@ -6,25 +6,29 @@ import sys | |||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from utils.models import engine | # from libs.db import DB | ||||||
| from utils.event_system import EventSystem | from libs.event_system import EventSystem | ||||||
| from utils.endpoint_registry import EndpointRegistry | from libs.endpoint_registry import EndpointRegistry | ||||||
| from utils.keybindings import Keybindings | from libs.keybindings import Keybindings | ||||||
| from utils.logger import Logger | from libs.logger import Logger | ||||||
| from utils.settings_manager.manager import SettingsManager | from libs.settings_manager.manager import SettingsManager | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Threads WILL NOT die with parent's destruction. | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
| def threaded_wrapper(fn): | def threaded_wrapper(fn): | ||||||
|     def wrapper(*args, **kwargs): |     def wrapper(*args, **kwargs): | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() |         thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = False) | ||||||
|  |         thread.start() | ||||||
|  |         return thread | ||||||
|     return wrapper |     return wrapper | ||||||
|  |  | ||||||
| # NOTE: Threads WILL die with parent's destruction. | # NOTE: Threads WILL die with parent's destruction. | ||||||
| def daemon_threaded_wrapper(fn): | def daemon_threaded_wrapper(fn): | ||||||
|     def wrapper(*args, **kwargs): |     def wrapper(*args, **kwargs): | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |         thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = True) | ||||||
|  |         thread.start() | ||||||
|  |         return thread | ||||||
|     return wrapper |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -32,12 +36,12 @@ def daemon_threaded_wrapper(fn): | |||||||
| # NOTE: Just reminding myself we can add to builtins two different ways... | # NOTE: Just reminding myself we can add to builtins two different ways... | ||||||
| # __builtins__.update({"event_system": Builtins()}) | # __builtins__.update({"event_system": Builtins()}) | ||||||
| builtins.app_name          = "<change_me>" | builtins.app_name          = "<change_me>" | ||||||
| builtins.db                = engine |  | ||||||
|  |  | ||||||
| builtins.keybindings       = Keybindings() | builtins.keybindings       = Keybindings() | ||||||
| builtins.event_system      = EventSystem() | builtins.event_system      = EventSystem() | ||||||
| builtins.endpoint_registry = EndpointRegistry() | builtins.endpoint_registry = EndpointRegistry() | ||||||
| builtins.settings_manager  = SettingsManager() | builtins.settings_manager  = SettingsManager() | ||||||
|  | # builtins.db                = DB() | ||||||
|  |  | ||||||
| settings_manager.load_settings() | settings_manager.load_settings() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,9 +10,6 @@ import tracemalloc | |||||||
| tracemalloc.start() | tracemalloc.start() | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from __builtins__ import * | from __builtins__ import * | ||||||
| @@ -20,34 +17,38 @@ from app import Application | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(args, unknownargs): | ||||||
|  |     setproctitle(f'{app_name}') | ||||||
|  |  | ||||||
|  |     if args.debug == "true": | ||||||
|  |         settings_manager.set_debug(True) | ||||||
|  |  | ||||||
|  |     if args.trace_debug == "true": | ||||||
|  |         settings_manager.set_trace_debug(True) | ||||||
|  |  | ||||||
|  |     settings_manager.do_dirty_start_check() | ||||||
|  |     Application(args, unknownargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     ''' Set process title, get arguments, and create GTK main thread. ''' |     ''' Set process title, get arguments, and create GTK main thread. ''' | ||||||
|  |  | ||||||
|  |     parser = argparse.ArgumentParser() | ||||||
|  |     # Add long and short arguments | ||||||
|  |     parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") | ||||||
|  |     parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") | ||||||
|  |     parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.") | ||||||
|  |  | ||||||
|  |     parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.") | ||||||
|  |     parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.") | ||||||
|  |  | ||||||
|  |     # Read arguments (If any...) | ||||||
|  |     args, unknownargs = parser.parse_known_args() | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         setproctitle(f'{app_name}') |  | ||||||
|         faulthandler.enable()  # For better debug info |         faulthandler.enable()  # For better debug info | ||||||
|  |         main(args, unknownargs) | ||||||
|         parser = argparse.ArgumentParser() |  | ||||||
|         # Add long and short arguments |  | ||||||
|         parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") |  | ||||||
|         parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") |  | ||||||
|         parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.") |  | ||||||
|         parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.") |  | ||||||
|         parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.") |  | ||||||
|  |  | ||||||
|         # Read arguments (If any...) |  | ||||||
|         args, unknownargs = parser.parse_known_args() |  | ||||||
|  |  | ||||||
|         if args.debug == "true": |  | ||||||
|             settings_manager.set_debug(True) |  | ||||||
|  |  | ||||||
|         if args.trace_debug == "true": |  | ||||||
|             settings_manager.set_trace_debug(True) |  | ||||||
|  |  | ||||||
|         settings_manager.do_dirty_start_check() |  | ||||||
|         Application(args, unknownargs) |  | ||||||
|         Gtk.main() |  | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         traceback.print_exc() |         traceback.print_exc() | ||||||
|         quit() |         quit() | ||||||
							
								
								
									
										39
									
								
								src/app.py
									
									
									
									
									
								
							
							
						
						| @@ -5,8 +5,8 @@ import os | |||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from utils.debugging import debug_signal_handler | from libs.debugging import debug_signal_handler | ||||||
| from utils.ipc_server import IPCServer | from libs.ipc_server import IPCServer | ||||||
| from core.window import Window | from core.window import Window | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -15,35 +15,40 @@ class AppLaunchException(Exception): | |||||||
|     ... |     ... | ||||||
|  |  | ||||||
|  |  | ||||||
| class Application(IPCServer): |  | ||||||
|  | class Application: | ||||||
|     """ docstring for Application. """ |     """ docstring for Application. """ | ||||||
|  |  | ||||||
|     def __init__(self, args, unknownargs): |     def __init__(self, args, unknownargs): | ||||||
|         super(Application, self).__init__() |         super(Application, self).__init__() | ||||||
|  |  | ||||||
|         if not settings_manager.is_trace_debug(): |         if not settings_manager.is_trace_debug(): | ||||||
|             self.socket_realization_check() |             self.load_ipc(args, unknownargs) | ||||||
|  |  | ||||||
|             if not self.is_ipc_alive: |  | ||||||
|                 for arg in unknownargs + [args.new_tab,]: |  | ||||||
|                     if os.path.isfile(arg): |  | ||||||
|                         message = f"FILE|{arg}" |  | ||||||
|                         self.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...") |  | ||||||
|  |  | ||||||
|         self.setup_debug_hook() |         self.setup_debug_hook() | ||||||
|         Window(args, unknownargs) |         Window(args, unknownargs).main() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def socket_realization_check(self): |     def load_ipc(self, args, unknownargs): | ||||||
|  |         ipc_server = IPCServer() | ||||||
|  |         self.ipc_realization_check(ipc_server) | ||||||
|  |  | ||||||
|  |         if not ipc_server.is_ipc_alive: | ||||||
|  |             for arg in unknownargs + [args.new_tab,]: | ||||||
|  |                 if os.path.isfile(arg): | ||||||
|  |                     message = f"FILE|{arg}" | ||||||
|  |                     ipc_server.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |             raise AppLaunchException(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...") | ||||||
|  |  | ||||||
|  |     def ipc_realization_check(self, ipc_server): | ||||||
|         try: |         try: | ||||||
|             self.create_ipc_listener() |             ipc_server.create_ipc_listener() | ||||||
|         except Exception: |         except Exception: | ||||||
|             self.send_test_ipc_message() |             ipc_server.send_test_ipc_message() | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             self.create_ipc_listener() |             ipc_server.create_ipc_listener() | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             ... |             ... | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,9 +6,8 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .left_container import LeftContainer | from .header_container import HeaderContainer | ||||||
| from .center_container import CenterContainer | from .body_container import BodyContainer | ||||||
| from .right_container import RightContainer |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -38,9 +37,8 @@ class BaseContainer(Gtk.Box): | |||||||
|         event_system.subscribe("remove_transparency", self._remove_transparency) |         event_system.subscribe("remove_transparency", self._remove_transparency) | ||||||
|  |  | ||||||
|     def _load_widgets(self): |     def _load_widgets(self): | ||||||
|         self.add(LeftContainer()) |         self.add(HeaderContainer()) | ||||||
|         self.add(CenterContainer()) |         self.add(BodyContainer()) | ||||||
|         self.add(RightContainer()) |  | ||||||
|  |  | ||||||
|     def _update_transparency(self): |     def _update_transparency(self): | ||||||
|         self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}") |         self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}") | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								src/core/containers/body_container.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .left_container import LeftContainer | ||||||
|  | from .center_container import CenterContainer | ||||||
|  | from .right_container import RightContainer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BodyContainer(Gtk.Box): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(BodyContainer, self).__init__() | ||||||
|  |  | ||||||
|  |         self.ctx = self.get_style_context() | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_orientation(Gtk.Orientation.HORIZONTAL) | ||||||
|  |         self.ctx.add_class("body-container") | ||||||
|  |         self.set_homogeneous(True) | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         self.add(LeftContainer()) | ||||||
|  |         self.add(CenterContainer()) | ||||||
|  |         self.add(RightContainer()) | ||||||
| @@ -6,7 +6,8 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from ..widgets.transparency_scale import TransparencyScale | from ..widgets.webkit.webkit_ui import WebkitUI | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CenterContainer(Gtk.Box): | class CenterContainer(Gtk.Box): | ||||||
| @@ -35,21 +36,13 @@ class CenterContainer(Gtk.Box): | |||||||
|  |  | ||||||
|     def _load_widgets(self): |     def _load_widgets(self): | ||||||
|         glade_box = self._builder.get_object("glade_box") |         glade_box = self._builder.get_object("glade_box") | ||||||
|         glade_box = self._builder.get_object("glade_box") |         button   = Gtk.Button(label = "Click Me!") | ||||||
|         button    = Gtk.Button(label = "Interactive Debug") |  | ||||||
|         button2   = Gtk.Button(label = "Click Me!") |  | ||||||
|  |  | ||||||
|         button.connect("clicked", self._interactive_debug) |         button.connect("clicked", self._hello_world) | ||||||
|         button2.connect("clicked", self._hello_world) |  | ||||||
|  |  | ||||||
|         self.add(TransparencyScale()) |  | ||||||
|         self.add(button) |         self.add(button) | ||||||
|         self.add(button2) |  | ||||||
|         self.add(glade_box) |         self.add(glade_box) | ||||||
|  |         self.add( WebkitUI() ) | ||||||
|  |  | ||||||
|     def _interactive_debug(self, widget = None, eve = None): |  | ||||||
|         event_system.emit("load_interactive_debug") |  | ||||||
|  |  | ||||||
|     def _hello_world(self, widget = None, eve = None): |     def _hello_world(self, widget = None, eve = None): | ||||||
|         logger.debug("Hello, World!") |         logger.debug("Hello, World!") | ||||||
							
								
								
									
										46
									
								
								src/core/containers/header_container.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from ..widgets.transparency_scale import TransparencyScale | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class HeaderContainer(Gtk.Box): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(HeaderContainer, self).__init__() | ||||||
|  |  | ||||||
|  |         self.ctx = self.get_style_context() | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_orientation(Gtk.Orientation.HORIZONTAL) | ||||||
|  |         self.ctx.add_class("header-container") | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         button    = Gtk.Button(label = "Interactive Debug") | ||||||
|  |         button.connect("clicked", self._interactive_debug) | ||||||
|  |  | ||||||
|  |         self.add(TransparencyScale()) | ||||||
|  |         self.add(button) | ||||||
|  |  | ||||||
|  |     def _interactive_debug(self, widget = None, eve = None): | ||||||
|  |         event_system.emit("load_interactive_debug") | ||||||
							
								
								
									
										3
									
								
								src/core/controllers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Controllers Module | ||||||
|  | """ | ||||||
| @@ -7,22 +7,24 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .mixins.signals_mixins import SignalsMixins | from libs.mixins.ipc_signals_mixin import IPCSignalsMixin | ||||||
| from .mixins.dummy_mixin import DummyMixin | from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin | ||||||
| from .controller_data import ControllerData | 
 | ||||||
| from .containers.base_container import BaseContainer | from ..containers.base_container import BaseContainer | ||||||
|  | 
 | ||||||
|  | from .base_controller_data import BaseControllerData | ||||||
|  | from .bridge_controller import BridgeController | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Controller(DummyMixin, SignalsMixins, ControllerData): | class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData): | ||||||
|     def __init__(self, args, unknownargs): |     def __init__(self, args, unknownargs): | ||||||
|         self.setup_controller_data() |         self.setup_controller_data() | ||||||
| 
 | 
 | ||||||
|         self._setup_styling() |         self._setup_styling() | ||||||
|         self._setup_signals() |         self._setup_signals() | ||||||
|         self._subscribe_to_events() |         self._subscribe_to_events() | ||||||
| 
 |         self._load_controllers() | ||||||
|         self.print_hello_world() # A mixin method from the DummyMixin file |  | ||||||
| 
 | 
 | ||||||
|         if args.no_plugins == "false": |         if args.no_plugins == "false": | ||||||
|             self.plugins.launch_plugins() |             self.plugins.launch_plugins() | ||||||
| @@ -53,6 +55,9 @@ class Controller(DummyMixin, SignalsMixins, ControllerData): | |||||||
|         event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc) |         event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc) | ||||||
|         event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar) |         event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar) | ||||||
| 
 | 
 | ||||||
|  |     def _load_controllers(self): | ||||||
|  |         BridgeController() | ||||||
|  | 
 | ||||||
|     def _tggl_top_main_menubar(self): |     def _tggl_top_main_menubar(self): | ||||||
|         logger.debug("_tggl_top_main_menubar > stub...") |         logger.debug("_tggl_top_main_menubar > stub...") | ||||||
| 
 | 
 | ||||||
| @@ -10,8 +10,8 @@ from plugins.plugins_controller import PluginsController | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ControllerData: | class BaseControllerData: | ||||||
|     ''' ControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' |     ''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' | ||||||
| 
 | 
 | ||||||
|     def setup_controller_data(self) -> None: |     def setup_controller_data(self) -> None: | ||||||
|         self.window      = settings_manager.get_main_window() |         self.window      = settings_manager.get_main_window() | ||||||
							
								
								
									
										43
									
								
								src/core/controllers/bridge_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | |||||||
|  | # Python imports | ||||||
|  | import base64 | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BridgeController: | ||||||
|  |     def __init__(self): | ||||||
|  |  | ||||||
|  |         self.opened_files = {} | ||||||
|  |  | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         event_system.subscribe("handle_bridge_event", self.handle_bridge_event) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def handle_bridge_event(self, event): | ||||||
|  |         match event.topic: | ||||||
|  |             case "save": | ||||||
|  |                 event_system.emit(f"handle_file_event_{event.originator}", (event,)) | ||||||
|  |             case "close": | ||||||
|  |                 event_system.emit(f"handle_file_event_{event.originator}", (event,)) | ||||||
|  |             case "load_buffer": | ||||||
|  |                 event_system.emit(f"handle_file_event_{event.originator}", (event,)) | ||||||
|  |             case "load_file": | ||||||
|  |                 event_system.emit(f"handle_file_event_{event.originator}", (event,)) | ||||||
|  |             case "alert": | ||||||
|  |                 content = base64.b64decode( event.content.encode() ).decode("utf-8") | ||||||
|  |                 logger.info(f"\nMessage Topic:  {event.topic}\nMessage Content:  {content}") | ||||||
|  |             case "error": | ||||||
|  |                 content = base64.b64decode( event.content.encode() ).decode("utf-8") | ||||||
|  |                 logger.info(content) | ||||||
|  |             case _: | ||||||
|  |                 ... | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Generic Mixins Module |  | ||||||
| """ |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DummyMixin: |  | ||||||
|     """ DummyMixin is an example of how mixins are used and structured in a project. """ |  | ||||||
|  |  | ||||||
|     def print_hello_world(self) -> None: |  | ||||||
|         logger.debug("Hello, World!") |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Signals module |  | ||||||
| """ |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .signals.ipc_signals_mixin import IPCSignalsMixin |  | ||||||
| from .signals.keyboard_signals_mixin import KeyboardSignalsMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SignalsMixins(KeyboardSignalsMixin, IPCSignalsMixin): |  | ||||||
|     ... |  | ||||||
| @@ -26,7 +26,7 @@ class TransparencyScale(Gtk.Scale): | |||||||
|         self.set_digits(0) |         self.set_digits(0) | ||||||
|         self.set_value_pos(Gtk.PositionType.RIGHT) |         self.set_value_pos(Gtk.PositionType.RIGHT) | ||||||
|         self.add_mark(50.0, Gtk.PositionType.TOP, "50%") |         self.add_mark(50.0, Gtk.PositionType.TOP, "50%") | ||||||
|  |         self.set_hexpand(True) | ||||||
|  |  | ||||||
|     def _setup_signals(self): |     def _setup_signals(self): | ||||||
|         self.connect("value-changed", self._update_transparency) |         self.connect("value-changed", self._update_transparency) | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								src/core/widgets/webkit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     WebKit2 UI Module | ||||||
|  | """ | ||||||
							
								
								
									
										108
									
								
								src/core/widgets/webkit/webkit_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,108 @@ | |||||||
|  | # Python imports | ||||||
|  | import json | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | gi.require_version('WebKit2', '4.0') | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import WebKit2 | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from libs.data_types import Event | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUI(WebKit2.WebView): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUI, self).__init__() | ||||||
|  |  | ||||||
|  |         # self.get_context().set_sandbox_enabled(False) | ||||||
|  |  | ||||||
|  |         self._load_settings() | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |         self._load_view() | ||||||
|  |         self._setup_content_manager() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |         if settings_manager.is_debug(): | ||||||
|  |             inspector = self.get_inspector() | ||||||
|  |             inspector.show() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_vexpand(True) | ||||||
|  |         self.set_hexpand(True) | ||||||
|  |         self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         event_system.subscribe(f"ui_message", self.ui_message) | ||||||
|  |      | ||||||
|  |     def _load_settings(self): | ||||||
|  |         self.set_settings( WebkitUISettings() ) | ||||||
|  |  | ||||||
|  |     def _load_view(self): | ||||||
|  |         path = settings_manager.get_context_path() | ||||||
|  |         data = None | ||||||
|  |  | ||||||
|  |         with open(f"{path}/index.html", "r") as f: | ||||||
|  |             data = f.read() | ||||||
|  |  | ||||||
|  |         self.load_html(content = data, base_uri = f"file://{path}/") | ||||||
|  |  | ||||||
|  |     def _setup_content_manager(self): | ||||||
|  |         content_manager = self.get_user_content_manager() | ||||||
|  |  | ||||||
|  |         content_manager.connect("script-message-received", self._process_js_message) | ||||||
|  |         content_manager.register_script_message_handler("backend") | ||||||
|  |  | ||||||
|  |     def _process_js_message(self, user_content_manager, js_result): | ||||||
|  |         js_value = js_result.get_js_value() | ||||||
|  |         message  = js_value.to_string() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             event = Event( **json.loads(message) ) | ||||||
|  |             event_system.emit("handle_bridge_event", (event,)) | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.info(e) | ||||||
|  |  | ||||||
|  |     def ui_message(self, mtype, message): | ||||||
|  |         command = f"displayMessage('{message}', '{mtype}', '3')" | ||||||
|  |         self.run_javascript(command, None, None) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUISettings(WebKit2.Settings): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUISettings, self).__init__() | ||||||
|  |          | ||||||
|  |         self._set_default_settings() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Note: Highly insecure setup but most "app" like setup I could think of. | ||||||
|  |     #       Audit heavily any scripts/links ran/clicked under this setup!  | ||||||
|  |     def _set_default_settings(self): | ||||||
|  |         # self.set_enable_xss_auditor(True) | ||||||
|  |         # self.set_enable_hyperlink_auditing(True) | ||||||
|  |         self.set_enable_xss_auditor(False) | ||||||
|  |         self.set_enable_hyperlink_auditing(False) | ||||||
|  |         self.set_enable_dns_prefetching(False) | ||||||
|  |         self.set_allow_file_access_from_file_urls(True) | ||||||
|  |         self.set_allow_universal_access_from_file_urls(True) | ||||||
|  |         # self.set_enable_java(True) | ||||||
|  |  | ||||||
|  |         self.set_enable_page_cache(False) | ||||||
|  |         self.set_enable_offline_web_application_cache(False) | ||||||
|  |         self.set_enable_html5_local_storage(False) | ||||||
|  |         self.set_enable_html5_database(False) | ||||||
|  |  | ||||||
|  |         self.set_print_backgrounds(False) | ||||||
|  |         self.set_enable_tabs_to_links(False) | ||||||
|  |         self.set_enable_fullscreen(True) | ||||||
|  |         self.set_enable_developer_extras(True) | ||||||
|  |         self.set_enable_webrtc(True) | ||||||
|  |         self.set_enable_webaudio(True) | ||||||
|  |         self.set_enable_accelerated_2d_canvas(True) | ||||||
|  |  | ||||||
|  |         self.set_user_agent(f"Mozilla/5.0 {app_name}") | ||||||
							
								
								
									
										64
									
								
								src/core/widgets/webkit_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,64 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | gi.require_version('WebKit2', '4.0') | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import WebKit2 | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from libs.settings_manager.other.webkit_ui_settings import WebkitUISettings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUI(WebKit2.WebView): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUI, self).__init__() | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |         self._load_view() | ||||||
|  |         self._setup_content_manager() | ||||||
|  |          | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_vexpand(True) | ||||||
|  |         self.set_hexpand(True) | ||||||
|  |         self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         event_system.subscribe(f"ui_message", self.ui_message) | ||||||
|  |      | ||||||
|  |     def _load_settings(self): | ||||||
|  |         self.set_settings( WebkitUISettings() ) | ||||||
|  |  | ||||||
|  |     def _load_view(self): | ||||||
|  |         path = settings_manager.get_context_path() | ||||||
|  |         data = None | ||||||
|  |  | ||||||
|  |         with open(f"{path}/index.html", "r") as f: | ||||||
|  |             data = f.read() | ||||||
|  |  | ||||||
|  |         self.load_html(content = data, base_uri = f"file://{path}/") | ||||||
|  |  | ||||||
|  |     def _setup_content_manager(self): | ||||||
|  |         content_manager = self.get_user_content_manager() | ||||||
|  |         content_manager.connect("script-message-received", self._process_js_message) | ||||||
|  |         content_manager.register_script_message_handler("backend") | ||||||
|  |  | ||||||
|  |     def _process_js_message(self, user_content_manager, js_result): | ||||||
|  |         js_value = js_result.get_js_value() | ||||||
|  |         message  = js_value.to_string() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             event = Event( **json.loads(message) ) | ||||||
|  |             event_system.emit("handle_bridge_event", (event,)) | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.info(e) | ||||||
|  |  | ||||||
|  |     def ui_message(self, message, mtype): | ||||||
|  |         command = f"displayMessage('{message}', '{mtype}', '3')" | ||||||
|  |         self.run_javascript(command, None, None) | ||||||
|  |  | ||||||
| @@ -11,7 +11,8 @@ from gi.repository import Gdk | |||||||
| from gi.repository import GLib | from gi.repository import GLib | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from core.controller import Controller | from core.controllers.base_controller import BaseController | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ControllerStartExceptiom(Exception): | class ControllerStartExceptiom(Exception): | ||||||
| @@ -19,7 +20,6 @@ class ControllerStartExceptiom(Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Window(Gtk.ApplicationWindow): | class Window(Gtk.ApplicationWindow): | ||||||
|     """ docstring for Window. """ |     """ docstring for Window. """ | ||||||
|  |  | ||||||
| @@ -29,13 +29,14 @@ class Window(Gtk.ApplicationWindow): | |||||||
|  |  | ||||||
|         self._controller = None |         self._controller = None | ||||||
|  |  | ||||||
|         self._set_window_data() |  | ||||||
|         self._setup_styling() |         self._setup_styling() | ||||||
|         self._setup_signals() |         self._setup_signals() | ||||||
|         self._subscribe_to_events() |         self._subscribe_to_events() | ||||||
|         self._load_widgets(args, unknownargs) |         self._load_widgets(args, unknownargs) | ||||||
|  |  | ||||||
|  |         self._set_window_data() | ||||||
|         self._set_size_constraints() |         self._set_size_constraints() | ||||||
|  |  | ||||||
|         self.show() |         self.show() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -50,8 +51,11 @@ class Window(Gtk.ApplicationWindow): | |||||||
|         ctx.add_class(f"mw_transparency_{settings.theming.transparency}") |         ctx.add_class(f"mw_transparency_{settings.theming.transparency}") | ||||||
|  |  | ||||||
|     def _setup_signals(self): |     def _setup_signals(self): | ||||||
|         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down) |         self.connect("focus-in-event", self._on_focus_in_event) | ||||||
|  |         self.connect("focus-out-event", self._on_focus_out_event) | ||||||
|  |  | ||||||
|         self.connect("delete-event", self._tear_down) |         self.connect("delete-event", self._tear_down) | ||||||
|  |         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down) | ||||||
|  |  | ||||||
|     def _subscribe_to_events(self): |     def _subscribe_to_events(self): | ||||||
|         event_system.subscribe("tear_down", self._tear_down) |         event_system.subscribe("tear_down", self._tear_down) | ||||||
| @@ -61,9 +65,9 @@ class Window(Gtk.ApplicationWindow): | |||||||
|         if settings_manager.is_debug(): |         if settings_manager.is_debug(): | ||||||
|             self.set_interactive_debugging(True) |             self.set_interactive_debugging(True) | ||||||
|  |  | ||||||
|         self._controller = Controller(args, unknownargs) |         self._controller = BaseController(args, unknownargs) | ||||||
|         if not self._controller: |         if not self._controller: | ||||||
|             raise ControllerStartException("Controller exited and doesn't exist...") |             raise ControllerStartException("BaseController exited and doesn't exist...") | ||||||
|  |  | ||||||
|         self.add( self._controller.get_base_container() ) |         self.add( self._controller.get_base_container() ) | ||||||
|  |  | ||||||
| @@ -101,6 +105,13 @@ class Window(Gtk.ApplicationWindow): | |||||||
|         cr.paint() |         cr.paint() | ||||||
|         cr.set_operator(cairo.OPERATOR_OVER) |         cr.set_operator(cairo.OPERATOR_OVER) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _on_focus_in_event(self, widget, event): | ||||||
|  |         event_system.emit("pause_dnd_signals") | ||||||
|  |  | ||||||
|  |     def _on_focus_out_event(self, widget, event): | ||||||
|  |         event_system.emit("listen_dnd_signals") | ||||||
|  |  | ||||||
|     def _load_interactive_debug(self): |     def _load_interactive_debug(self): | ||||||
|         self.set_interactive_debugging(True) |         self.set_interactive_debugging(True) | ||||||
|  |  | ||||||
| @@ -108,7 +119,7 @@ class Window(Gtk.ApplicationWindow): | |||||||
|     def _tear_down(self, widget = None, eve = None): |     def _tear_down(self, widget = None, eve = None): | ||||||
|         event_system.emit("shutting_down") |         event_system.emit("shutting_down") | ||||||
|  |  | ||||||
|         size = self.get_default_size() |         size = self.get_size() | ||||||
|         pos  = self.get_position() |         pos  = self.get_position() | ||||||
|  |  | ||||||
|         settings_manager.set_main_window_width(size.width) |         settings_manager.set_main_window_width(size.width) | ||||||
| @@ -119,3 +130,6 @@ class Window(Gtk.ApplicationWindow): | |||||||
|  |  | ||||||
|         settings_manager.clear_pid() |         settings_manager.clear_pid() | ||||||
|         Gtk.main_quit() |         Gtk.main_quit() | ||||||
|  |  | ||||||
|  |     def main(self): | ||||||
|  |         Gtk.main() | ||||||
							
								
								
									
										5
									
								
								src/libs/data_types/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | |||||||
|  | """ | ||||||
|  |     Dasta Class module | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from .event import Event | ||||||
							
								
								
									
										14
									
								
								src/libs/data_types/event.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | # Python imports | ||||||
|  | from dataclasses import dataclass, field | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class Event: | ||||||
|  |     topic: str | ||||||
|  |     content: str | ||||||
|  |     raw_content: str | ||||||
							
								
								
									
										42
									
								
								src/libs/db.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | |||||||
|  | # Python imports | ||||||
|  | from typing import Optional | ||||||
|  | from os import path | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from sqlmodel import Session, create_engine | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .models import SQLModel, User | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DB: | ||||||
|  |     def __init__(self): | ||||||
|  |         super(DB, self).__init__() | ||||||
|  |          | ||||||
|  |         self.engine = None | ||||||
|  |  | ||||||
|  |         self.create_engine() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def create_engine(self): | ||||||
|  |         db_path     = f"sqlite:///{settings_manager.get_home_config_path()}/database.db" | ||||||
|  |         self.engine = create_engine(db_path) | ||||||
|  |  | ||||||
|  |         SQLModel.metadata.create_all(self.engine) | ||||||
|  |  | ||||||
|  |     def _add_entry(self, entry): | ||||||
|  |         with Session(self.engine) as session: | ||||||
|  |             session.add(entry) | ||||||
|  |             session.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def add_user_entry(self, name = None, password = None, email = None): | ||||||
|  |         if not name or not password or not email: return | ||||||
|  |  | ||||||
|  |         user = User() | ||||||
|  |         user.name     = name | ||||||
|  |         user.password = password | ||||||
|  |         user.email    = email | ||||||
|  |  | ||||||
|  |         self._add_entry(user) | ||||||
							
								
								
									
										3
									
								
								src/libs/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Utils/Mixins module | ||||||
|  | """ | ||||||
							
								
								
									
										70
									
								
								src/libs/mixins/dnd_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,70 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DnDMixin: | ||||||
|  |  | ||||||
|  |     def _setup_dnd(self): | ||||||
|  |         flags   = Gtk.DestDefaults.ALL | ||||||
|  |  | ||||||
|  |         PLAIN_TEXT_TARGET_TYPE = 70 | ||||||
|  |         URI_TARGET_TYPE        = 80 | ||||||
|  |  | ||||||
|  |         text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE) | ||||||
|  |         uri_target  = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) | ||||||
|  |  | ||||||
|  |         # targets     = [ text_target, uri_target ] | ||||||
|  |         targets     = [ uri_target ] | ||||||
|  |  | ||||||
|  |         action      = Gdk.DragAction.COPY | ||||||
|  |  | ||||||
|  |         # self.drag_dest_set_target_list(targets) | ||||||
|  |         self.drag_dest_set(flags, targets, action) | ||||||
|  |  | ||||||
|  |         self._setup_dnd_signals() | ||||||
|  |  | ||||||
|  |     def _setup_dnd_signals(self): | ||||||
|  |         # self.connect("drag-motion",        self._on_drag_motion) | ||||||
|  |         # self.connect('drag-drop',          self._on_drag_set) | ||||||
|  |         self.connect("drag-data-received", self._on_drag_data_received) | ||||||
|  |  | ||||||
|  |     def _on_drag_motion(self, widget, drag_context, x, y, time): | ||||||
|  |         Gdk.drag_status(drag_context, drag_context.get_actions(), time) | ||||||
|  |  | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def _on_drag_set(self, widget, drag_context, data, info, time): | ||||||
|  |         self.drag_get_data(drag_context, drag_context.list_targets()[-1], time) | ||||||
|  |  | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time): | ||||||
|  |         if info == 70: return | ||||||
|  |  | ||||||
|  |         if info == 80: | ||||||
|  |             uris   = data.get_uris() | ||||||
|  |             files  = [] | ||||||
|  |  | ||||||
|  |             if len(uris) == 0: | ||||||
|  |                 uris = data.get_text().split("\n") | ||||||
|  |  | ||||||
|  |             for uri in uris: | ||||||
|  |                 gfile = None | ||||||
|  |                 try: | ||||||
|  |                     gfile = Gio.File.new_for_uri(uri) | ||||||
|  |                 except Exception as e: | ||||||
|  |                     gfile = Gio.File.new_for_path(uri) | ||||||
|  |  | ||||||
|  |                 files.append(gfile) | ||||||
|  |  | ||||||
|  |             event_system.emit('set_pre_drop_dnd', (files,)) | ||||||
| @@ -20,11 +20,19 @@ class KeyboardSignalsMixin: | |||||||
|     """ KeyboardSignalsMixin keyboard hooks controller. """ |     """ KeyboardSignalsMixin keyboard hooks controller. """ | ||||||
| 
 | 
 | ||||||
|     # TODO: Need to set methods that use this to somehow check the keybindings state instead. |     # TODO: Need to set methods that use this to somehow check the keybindings state instead. | ||||||
|     def unset_keys_and_data(self, widget=None, eve=None): |     def unset_keys_and_data(self, widget = None, eve = None): | ||||||
|         self.ctrl_down    = False |         self.ctrl_down    = False | ||||||
|         self.shift_down   = False |         self.shift_down   = False | ||||||
|         self.alt_down     = False |         self.alt_down     = False | ||||||
| 
 | 
 | ||||||
|  |     def unmap_special_keys(self, keyname): | ||||||
|  |         if "control" in keyname: | ||||||
|  |             self.ctrl_down    = False | ||||||
|  |         if "shift" in keyname: | ||||||
|  |             self.shift_down   = False | ||||||
|  |         if "alt" in keyname: | ||||||
|  |             self.alt_down     = False | ||||||
|  | 
 | ||||||
|     def on_global_key_press_controller(self, eve, user_data): |     def on_global_key_press_controller(self, eve, user_data): | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |         keyname = Gdk.keyval_name(user_data.keyval).lower() | ||||||
|         modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK) |         modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK) | ||||||
| @@ -46,15 +54,8 @@ class KeyboardSignalsMixin: | |||||||
| 
 | 
 | ||||||
|         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: |         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: | ||||||
|             should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down) |             should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down) | ||||||
|  |             self.unmap_special_keys(keyname) | ||||||
| 
 | 
 | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrl_down    = False |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shift_down   = False |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.alt_down     = False |  | ||||||
| 
 |  | ||||||
|             # NOTE: In effect a filter after releasing a modifier and we have a modifier mapped |  | ||||||
|             if should_return: |             if should_return: | ||||||
|                 self.was_midified_key = False |                 self.was_midified_key = False | ||||||
|                 return |                 return | ||||||
| @@ -65,30 +66,33 @@ class KeyboardSignalsMixin: | |||||||
|         logger.debug(f"on_global_key_release_controller > mapping > {mapping}") |         logger.debug(f"on_global_key_release_controller > mapping > {mapping}") | ||||||
| 
 | 
 | ||||||
|         if mapping: |         if mapping: | ||||||
|             # See if in controller scope |             self.handle_mapped_key_event(mapping) | ||||||
|             try: |  | ||||||
|                 getattr(self, mapping)() |  | ||||||
|                 return True |  | ||||||
|             except Exception: |  | ||||||
|                 # Must be plugins scope, event call, OR we forgot to add method to controller scope |  | ||||||
|                 if "||" in mapping: |  | ||||||
|                     sender, eve_type = mapping.split("||") |  | ||||||
|                 else: |  | ||||||
|                     sender = "" |  | ||||||
|                     eve_type = mapping |  | ||||||
| 
 |  | ||||||
|                 self.handle_key_event_system(sender, eve_type) |  | ||||||
|         else: |         else: | ||||||
|             logger.debug(f"on_global_key_release_controller > key > {keyname}") |             self.handle_as_key_event_scope(mapping) | ||||||
| 
 | 
 | ||||||
|             if self.ctrl_down: |     def handle_mapped_key_event(self, mapping): | ||||||
|                 if not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: |         try: | ||||||
|                     self.handle_key_event_system(None, mapping) |             self.handle_as_controller_scope() | ||||||
|                 else: |         except Exception: | ||||||
|                     ... |             self.handle_as_plugin_scope(mapping) | ||||||
|  | 
 | ||||||
|  |     def handle_as_controller_scope(self, mapping): | ||||||
|  |         getattr(self, mapping)() | ||||||
|  | 
 | ||||||
|  |     def handle_as_plugin_scope(self, mapping): | ||||||
|  |         if "||" in mapping: | ||||||
|  |             sender, eve_type = mapping.split("||") | ||||||
|  |         else: | ||||||
|  |             sender = "" | ||||||
|  |             eve_type = mapping | ||||||
|  | 
 | ||||||
|  |         self.handle_as_key_event_system(sender, eve_type) | ||||||
|  | 
 | ||||||
|  |     def handle_as_key_event_scope(self, mapping): | ||||||
|  |         logger.debug(f"on_global_key_release_controller > key > {keyname}") | ||||||
|  | 
 | ||||||
|  |         if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: | ||||||
|  |             self.handle_key_event_system(None, mapping) | ||||||
| 
 | 
 | ||||||
|     def handle_key_event_system(self, sender, eve_type): |     def handle_key_event_system(self, sender, eve_type): | ||||||
|         event_system.emit(eve_type) |         event_system.emit(eve_type) | ||||||
| 
 |  | ||||||
|     def keyboard_close_tab(self): |  | ||||||
|         ... |  | ||||||
							
								
								
									
										15
									
								
								src/libs/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | |||||||
|  | # Python imports | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from sqlmodel import SQLModel, Field | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class User(SQLModel, table = True): | ||||||
|  |     id: Optional[int] = Field(default = None, primary_key = True) | ||||||
|  |     name: str | ||||||
|  |     password: str | ||||||
|  |     email: Optional[str] = None | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import signal |  | ||||||
| import io |  | ||||||
| import json |  | ||||||
| import inspect | import inspect | ||||||
|  | import json | ||||||
| import zipfile | import zipfile | ||||||
| 
 | 
 | ||||||
| from os import path | from os import path | ||||||
| @@ -30,6 +28,7 @@ class SettingsManager(StartCheckMixin, Singleton): | |||||||
|         self._USR_PATH          = f"/usr/share/{app_name.lower()}" |         self._USR_PATH          = f"/usr/share/{app_name.lower()}" | ||||||
|         self._USR_CONFIG_FILE   = f"{self._USR_PATH}/settings.json" |         self._USR_CONFIG_FILE   = f"{self._USR_PATH}/settings.json" | ||||||
| 
 | 
 | ||||||
|  |         self._CONTEXT_PATH      = f"{self._HOME_CONFIG_PATH}/context_path" | ||||||
|         self._PLUGINS_PATH      = f"{self._HOME_CONFIG_PATH}/plugins" |         self._PLUGINS_PATH      = f"{self._HOME_CONFIG_PATH}/plugins" | ||||||
|         self._DEFAULT_ICONS     = f"{self._HOME_CONFIG_PATH}/icons" |         self._DEFAULT_ICONS     = f"{self._HOME_CONFIG_PATH}/icons" | ||||||
|         self._CONFIG_FILE       = f"{self._HOME_CONFIG_PATH}/settings.json" |         self._CONFIG_FILE       = f"{self._HOME_CONFIG_PATH}/settings.json" | ||||||
| @@ -146,6 +145,7 @@ class SettingsManager(StartCheckMixin, Singleton): | |||||||
|     def get_ui_widgets_path(self)    -> str: return self._UI_WIDEGTS_PATH |     def get_ui_widgets_path(self)    -> str: return self._UI_WIDEGTS_PATH | ||||||
|     def get_context_menu_data(self)  -> str: return self._context_menu_data |     def get_context_menu_data(self)  -> str: return self._context_menu_data | ||||||
| 
 | 
 | ||||||
|  |     def get_context_path(self)     -> str:   return self._CONTEXT_PATH | ||||||
|     def get_plugins_path(self)     -> str:   return self._PLUGINS_PATH |     def get_plugins_path(self)     -> str:   return self._PLUGINS_PATH | ||||||
|     def get_icon_theme(self)       -> str:   return self._ICON_THEME |     def get_icon_theme(self)       -> str:   return self._ICON_THEME | ||||||
|     def get_css_file(self)         -> str:   return self._CSS_FILE |     def get_css_file(self)         -> str:   return self._CSS_FILE | ||||||
							
								
								
									
										3
									
								
								src/libs/settings_manager/other/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Settings Other module | ||||||
|  | """ | ||||||
							
								
								
									
										42
									
								
								src/libs/settings_manager/other/webkit_ui_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('WebKit2', '4.0') | ||||||
|  | from gi.repository import WebKit2 | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUISettings(WebKit2.Settings): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUISettings, self).__init__() | ||||||
|  |          | ||||||
|  |         self._set_default_settings() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Note: Highly insecure setup but most "app" like setup I could think of. | ||||||
|  |     #       Audit heavily any scripts/links ran/clicked under this setup!  | ||||||
|  |     def _set_default_settings(self): | ||||||
|  |         self.set_enable_xss_auditor(True) | ||||||
|  |         self.set_enable_hyperlink_auditing(True) | ||||||
|  |         # self.set_enable_xss_auditor(False) | ||||||
|  |         # self.set_enable_hyperlink_auditing(False) | ||||||
|  |         self.set_allow_file_access_from_file_urls(True) | ||||||
|  |         self.set_allow_universal_access_from_file_urls(True) | ||||||
|  |  | ||||||
|  |         self.set_enable_page_cache(False) | ||||||
|  |         self.set_enable_offline_web_application_cache(False) | ||||||
|  |         self.set_enable_html5_local_storage(False) | ||||||
|  |         self.set_enable_html5_database(False) | ||||||
|  |  | ||||||
|  |         self.set_enable_fullscreen(False) | ||||||
|  |         self.set_print_backgrounds(False) | ||||||
|  |         self.set_enable_tabs_to_links(False) | ||||||
|  |         self.set_enable_developer_extras(True) | ||||||
|  |         self.set_enable_webrtc(True) | ||||||
|  |         self.set_enable_webaudio(True) | ||||||
|  |         self.set_enable_accelerated_2d_canvas(True) | ||||||
|  |  | ||||||
|  |         self.set_user_agent(f"{app_name}") | ||||||
							
								
								
									
										63
									
								
								src/libs/settings_manager/start_check_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | import inspect | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StartCheckMixin: | ||||||
|  |     def is_dirty_start(self) -> bool: | ||||||
|  |         return self._dirty_start | ||||||
|  |  | ||||||
|  |     def clear_pid(self): | ||||||
|  |         if not self.is_trace_debug(): | ||||||
|  |             self._clean_pid() | ||||||
|  |  | ||||||
|  |     def do_dirty_start_check(self): | ||||||
|  |         if self.is_trace_debug(): | ||||||
|  |             pid = os.getpid() | ||||||
|  |             self._print_pid(pid) | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if os.path.exists(self._PID_FILE): | ||||||
|  |             with open(self._PID_FILE, "r") as f: | ||||||
|  |                 pid = f.readline().strip() | ||||||
|  |                 if pid not in ("", None): | ||||||
|  |                     if self.is_pid_alive( int(pid) ): | ||||||
|  |                         print("PID file exists and PID is alive... Letting downstream errors (sans debug args) handle app closure propigation.") | ||||||
|  |                         return | ||||||
|  |  | ||||||
|  |         self._write_new_pid() | ||||||
|  |  | ||||||
|  |     """ Check For the existence of a unix pid. """ | ||||||
|  |     def is_pid_alive(self, pid): | ||||||
|  |         print(f"PID Found: {pid}") | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             os.kill(pid, 0) | ||||||
|  |         except OSError: | ||||||
|  |             print(f"{app_name} PID file exists but PID is irrelevant; starting dirty...") | ||||||
|  |             self._dirty_start = True | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  |     def _write_new_pid(self): | ||||||
|  |         pid = os.getpid() | ||||||
|  |         self._write_pid(pid) | ||||||
|  |         self._print_pid(pid) | ||||||
|  |  | ||||||
|  |     def _print_pid(self, pid): | ||||||
|  |         print(f"{app_name} PID:  {pid}") | ||||||
|  |  | ||||||
|  |     def _clean_pid(self): | ||||||
|  |         os.unlink(self._PID_FILE) | ||||||
|  |  | ||||||
|  |     def _write_pid(self, pid): | ||||||
|  |         with open(self._PID_FILE, "w") as _pid: | ||||||
|  |             _pid.write(f"{pid}") | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| from typing import Optional |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| from sqlmodel import Field, Session, SQLModel, create_engine |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class User(SQLModel, table = True): |  | ||||||
|     id: Optional[int] = Field(default = None, primary_key = True) |  | ||||||
|     name: str |  | ||||||
|     password: str |  | ||||||
|     email: Optional[str] = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: for sake of example we create an admin user with no password set. |  | ||||||
| user   = User(name = "Admin", password = "", email = "admin@domain.com") |  | ||||||
| engine = create_engine("sqlite:///database.db") |  | ||||||
| SQLModel.metadata.create_all(engine) |  | ||||||
|  |  | ||||||
| with Session(engine) as session: |  | ||||||
|     session.add(user) |  | ||||||
|     session.commit() |  | ||||||
| @@ -1,51 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import json |  | ||||||
| import inspect |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StartCheckMixin: |  | ||||||
|     def is_dirty_start(self) -> bool: return self._dirty_start |  | ||||||
|     def clear_pid(self): self._clean_pid() |  | ||||||
|  |  | ||||||
|     def do_dirty_start_check(self): |  | ||||||
|         if not os.path.exists(self._PID_FILE): |  | ||||||
|             self._write_new_pid() |  | ||||||
|         else: |  | ||||||
|             with open(self._PID_FILE, "r") as _pid: |  | ||||||
|                 pid = _pid.readline().strip() |  | ||||||
|                 if pid not in ("", None): |  | ||||||
|                     self._check_alive_status(int(pid)) |  | ||||||
|                 else: |  | ||||||
|                     self._write_new_pid() |  | ||||||
|  |  | ||||||
|     """ Check For the existence of a unix pid. """ |  | ||||||
|     def _check_alive_status(self, pid): |  | ||||||
|         print(f"PID Found: {pid}") |  | ||||||
|         try: |  | ||||||
|             os.kill(pid, 0) |  | ||||||
|         except OSError: |  | ||||||
|             print(f"{app_name} is starting dirty...") |  | ||||||
|             self._dirty_start = True |  | ||||||
|             self._write_new_pid() |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.") |  | ||||||
|  |  | ||||||
|     def _write_new_pid(self): |  | ||||||
|         pid = os.getpid() |  | ||||||
|         self._write_pid(pid) |  | ||||||
|         print(f"{app_name} PID:  {pid}") |  | ||||||
|  |  | ||||||
|     def _clean_pid(self): |  | ||||||
|         os.unlink(self._PID_FILE) |  | ||||||
|  |  | ||||||
|     def _write_pid(self, pid): |  | ||||||
|         with open(self._PID_FILE, "w") as _pid: |  | ||||||
|             _pid.write(f"{pid}") |  | ||||||
							
								
								
									
										47
									
								
								user_config/usr/share/app_name/context_path/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,47 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en" dir="ltr"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <title>Gtk + HTML + Python App</title> | ||||||
|  |         <!-- Bootstrap CSS --> | ||||||
|  |         <link rel="stylesheet" href="resources/css/libs/bootstrap5/bootstrap.min.css"> | ||||||
|  |         <link rel="stylesheet" href="resources/css/libs/bootstrap-icons/bootstrap-icons.css"> | ||||||
|  |  | ||||||
|  |         <!-- Site CSS --> | ||||||
|  |         <!--  <link rel="stylesheet" href="resources/css/context-menu.css">  --> | ||||||
|  |         <link rel="stylesheet" href="resources/css/main.css"> | ||||||
|  |         <link rel="stylesheet" href="resources/css/overrides.css"> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |  | ||||||
|  |     <div class="container"> | ||||||
|  |         <div class="row"> | ||||||
|  |             <div id="page-alert-zone" class="col"> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="row"> | ||||||
|  |             <div class="col text-center scroller"> | ||||||
|  |                     <button id="helloBttn" class=" btn btn-success">Button in WebView</button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <!-- For internal scripts... --> | ||||||
|  |     <!--  <script src="js/libs/jquery-3.7.1.min.js"></script>  --> | ||||||
|  |  | ||||||
|  |     <!-- For Bootstrap... --> | ||||||
|  |     <script src="resources/js/libs/bootstrap5/bootstrap.bundle.min.js"></script> | ||||||
|  |  | ||||||
|  |     <!-- For Application... --> | ||||||
|  |     <script src="resources/js/globals.js"></script> | ||||||
|  |  | ||||||
|  |     <!--  <script src="resources/js/post-ajax.js"></script>  --> | ||||||
|  |     <!--  <script src="resources/js/ajax.js"></script>  --> | ||||||
|  |  | ||||||
|  |     <script src="resources/js/utils.js"></script> | ||||||
|  |     <script src="resources/js/ui-logic.js"></script> | ||||||
|  |     <script src="resources/js/events.js"></script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | .menu { | ||||||
|  |     width: 165px; | ||||||
|  |     z-index: 999; | ||||||
|  |     box-shadow: 0 4px 5px 3px rgba(0, 0, 0, 0.2); | ||||||
|  |     background-color: rgba(0, 0, 0, 0.64); | ||||||
|  |     position: fixed; | ||||||
|  |     display: none; | ||||||
|  |     transition: 0.2s display ease-in; | ||||||
|  | } | ||||||
|  | .menu .menu-options { | ||||||
|  |     list-style: none; | ||||||
|  |     padding: 10px 0; | ||||||
|  |     z-index: 1; | ||||||
|  | } | ||||||
|  | .menu .menu-options .menu-option { | ||||||
|  |     font-weight: 500; | ||||||
|  |     z-index: 1; | ||||||
|  |     padding: 10px 40px 10px 20px; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .menu .menu-options .menu-option:hover { | ||||||
|  |     background: rgba(255, 255, 255, 0.64); | ||||||
|  |     color: rgba(0, 0, 0, 0.5); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button { | ||||||
|  |     background: grey; | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  | button .next { | ||||||
|  |     color: green; | ||||||
|  | } | ||||||
|  | button[disabled="false"]:hover .next { | ||||||
|  |     color: red; | ||||||
|  |     animation: move 0.5s; | ||||||
|  |     animation-iteration-count: 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes move { | ||||||
|  |     from { | ||||||
|  |         transform: translate(0%); | ||||||
|  |     } | ||||||
|  |     50% { | ||||||
|  |         transform: translate(-40%); | ||||||
|  |     } | ||||||
|  |     to { | ||||||
|  |         transform: transform(0%); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-123" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961h1.174Zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057h1.138Zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75v.96Z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 870 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-activity" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2Z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 367 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 626 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/> | ||||||
|  |   <path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 711 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16"> | ||||||
|  |   <rect width="4" height="12" x="6" y="1" rx="1"/> | ||||||
|  |   <path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 271 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 315 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/> | ||||||
|  |   <path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 318 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 316 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/> | ||||||
|  |   <path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 318 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16"> | ||||||
|  |   <rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/> | ||||||
|  |   <path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 287 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 326 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/> | ||||||
|  |   <path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 387 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 282 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/> | ||||||
|  |   <path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.5 KiB | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15h9.286zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1zM.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8H.8z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 359 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 401 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-down" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M4.854 14.854a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V3.5A2.5 2.5 0 0 1 6.5 1h8a.5.5 0 0 1 0 1h-8A1.5 1.5 0 0 0 5 3.5v9.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 350 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-left" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1.146 4.854a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H12.5A2.5 2.5 0 0 1 15 6.5v8a.5.5 0 0 1-1 0v-8A1.5 1.5 0 0 0 12.5 5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 349 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-right" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M14.854 4.854a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 4H3.5A2.5 2.5 0 0 0 1 6.5v8a.5.5 0 0 0 1 0v-8A1.5 1.5 0 0 1 3.5 5h9.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 350 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-up" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 349 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-down" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5zM8 6a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 0 1 .708-.708L7.5 12.293V6.5A.5.5 0 0 1 8 6z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 375 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-left" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 375 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-right" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M6 8a.5.5 0 0 0 .5.5h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L12.293 7.5H6.5A.5.5 0 0 0 6 8zm-2.5 7a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 375 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-up" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M8 10a.5.5 0 0 0 .5-.5V3.707l2.146 2.147a.5.5 0 0 0 .708-.708l-3-3a.5.5 0 0 0-.708 0l-3 3a.5.5 0 1 0 .708.708L7.5 3.707V9.5a.5.5 0 0 0 .5.5zm-7 2.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 376 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/> | ||||||
|  |   <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 352 B | 
| @@ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/> | ||||||
|  |   <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 359 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-circle-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v5.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V4.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 321 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-circle" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v5.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V4.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 370 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-circle-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M16 8A8 8 0 1 0 0 8a8 8 0 0 0 16 0zm-5.904-2.803a.5.5 0 1 1 .707.707L6.707 10h2.768a.5.5 0 0 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.525a.5.5 0 0 1 1 0v2.768l4.096-4.096z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 326 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-circle" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-5.904-2.854a.5.5 0 1 1 .707.708L6.707 9.95h2.768a.5.5 0 1 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.475a.5.5 0 1 1 1 0v2.768l4.096-4.097z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 377 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-square-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M2 16a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2zm8.096-10.803L6 9.293V6.525a.5.5 0 0 0-1 0V10.5a.5.5 0 0 0 .5.5h3.975a.5.5 0 0 0 0-1H6.707l4.096-4.096a.5.5 0 1 0-.707-.707z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 363 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-square" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm10.096 3.146a.5.5 0 1 1 .707.708L6.707 9.95h2.768a.5.5 0 1 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.475a.5.5 0 1 1 1 0v2.768l4.096-4.097z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 451 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M2 13.5a.5.5 0 0 0 .5.5h6a.5.5 0 0 0 0-1H3.707L13.854 2.854a.5.5 0 0 0-.708-.708L3 12.293V7.5a.5.5 0 0 0-1 0v6z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 286 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-circle-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm5.904-2.803a.5.5 0 1 0-.707.707L9.293 10H6.525a.5.5 0 0 0 0 1H10.5a.5.5 0 0 0 .5-.5V6.525a.5.5 0 0 0-1 0v2.768L5.904 5.197z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 326 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-circle" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.854 5.146a.5.5 0 1 0-.708.708L9.243 9.95H6.475a.5.5 0 1 0 0 1h3.975a.5.5 0 0 0 .5-.5V6.475a.5.5 0 1 0-1 0v2.768L5.854 5.146z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 379 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-square-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M14 16a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12zM5.904 5.197 10 9.293V6.525a.5.5 0 0 1 1 0V10.5a.5.5 0 0 1-.5.5H6.525a.5.5 0 0 1 0-1h2.768L5.197 5.904a.5.5 0 0 1 .707-.707z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 365 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-square" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm5.854 3.146a.5.5 0 1 0-.708.708L9.243 9.95H6.475a.5.5 0 1 0 0 1h3.975a.5.5 0 0 0 .5-.5V6.475a.5.5 0 1 0-1 0v2.768L5.854 5.146z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 453 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M14 13.5a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1 0-1h4.793L2.146 2.854a.5.5 0 1 1 .708-.708L13 12.293V7.5a.5.5 0 0 1 1 0v6z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 289 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-short" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5A.5.5 0 0 1 8 4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 314 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-square-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5a.5.5 0 0 1 1 0z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 359 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-square" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm8.5 2.5a.5.5 0 0 0-1 0v5.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V4.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 444 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-up" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5zm-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 457 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 309 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-circle-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.5 7.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 320 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-circle" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-4.5-.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 370 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-right" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 453 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-short" viewBox="0 0 16 16"> | ||||||
|  |   <path fill-rule="evenodd" d="M12 8a.5.5 0 0 1-.5.5H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5a.5.5 0 0 1 .5.5z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 314 B | 
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16"> | ||||||
|  |   <path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 362 B |