From cab36a0fdbcd770231be098053c8d5b136cf39cc Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Mon, 23 Jan 2023 00:13:30 +0100 Subject: [PATCH] Still WIP --- classes/app.py | 39 ++++++-- classes/custom/scrollable_frame.py | 151 +++++++++++++++++++++++++++++ classes/custom/themed_frame.py | 4 +- classes/frames/devices.py | 108 +++++++++++++++++++-- classes/frames/settings.py | 73 +++++++++++--- main.py | 1 + 6 files changed, 343 insertions(+), 33 deletions(-) create mode 100644 classes/custom/scrollable_frame.py diff --git a/classes/app.py b/classes/app.py index d5114dd..16c34a5 100644 --- a/classes/app.py +++ b/classes/app.py @@ -1,15 +1,16 @@ from os import path -from tkinter import NW, E, N, S, W, messagebox, ttk +from tkinter import NSEW, NW, E, N, S, W, messagebox, ttk import requests import sv_ttk from ttkthemes import ThemedTk -from classes.custom.themed_frame import ThemedFrame -from classes.frames.devices import FrameDevices +from classes.custom.scrollable_frame import FIT_HEIGHT, FIT_WIDTH +from classes.custom.themed_frame import ThemedFrame +from classes.frames.devices import FrameDevices, FrameDevicesEmpty +from classes.frames.errors import FrameErrorConnection, FrameErrorSavesFolder, FrameErrorUnconfigured from classes.frames.saves import FrameSaves from classes.frames.settings import FrameSettings -from classes.frames.errors import FrameErrorConnection, FrameErrorSavesFolder, FrameErrorUnconfigured from classes.toplevel.welcome import ToplevelWelcome from modules.theme_titlebar import theme_title_bar from modules.utils import configGet, get_string_mode, use_dark_mode @@ -21,7 +22,9 @@ class App(ThemedTk): super().__init__() - self.window_width = 650 + self.__version__ = "0.1.0" + + self.window_width = 600 self.window_height = 400 self.screen_width = self.winfo_screenwidth() @@ -66,16 +69,30 @@ class App(ThemedTk): def frame_saves(self): + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) self.frame_saves_object = FrameSaves(self) - self.frame_saves_object.grid(column=1, row=0, sticky=N+S+W+E) + self.frame_saves_object.grid(column=1, row=0, sticky=NSEW) return def frame_devices(self): - self.frame_devices_object = FrameDevices(self) - self.frame_devices_object.grid(column=1, row=0, sticky=N+S+W+E) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) + self.frame_devices_devices = requests.get(f'{configGet("address")}/devices', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) + if self.frame_devices_devices.status_code == 200 and isinstance(self.frame_devices_devices.json(), list) is True and len(self.frame_devices_devices.json()) > 0: + self.frame_devices_object = FrameDevices(self, self.frame_devices_devices.json(), vscroll=True) + # self.frame_devices_object.resize(FIT_HEIGHT) + else: + self.frame_devices_object = FrameDevicesEmpty(self) + #self.frame_devices_object = FrameDevices(self, hscroll=False, vscroll=True) + #self.frame_devices_object.resize(FIT_WIDTH) + #self.frame_devices_object.resize(FIT_HEIGHT) + self.frame_devices_object.grid(column=1, row=0, sticky=NSEW) return def frame_settings(self): + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) self.frame_settings_object = FrameSettings(self) self.frame_settings_object.grid(column=1, row=0, sticky=N+S+W+E) return @@ -113,6 +130,8 @@ class App(ThemedTk): if configGet("address") in ["", None] or configGet("apikey") in ["", None]: self.item_saves.state(["disabled"]) self.item_devices.state(["disabled"]) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) self.connection_error = FrameErrorUnconfigured(self) self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) # messagebox.showerror(title="Configuration error", message="Your client is not properly configured.") @@ -121,6 +140,8 @@ class App(ThemedTk): if self.verify_authorization() is False: self.item_saves.state(["disabled"]) self.item_devices.state(["disabled"]) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) self.connection_error = FrameErrorConnection(self) self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) messagebox.showerror(title="Authentication error", message="Your API key seems to be invalid.") @@ -128,6 +149,8 @@ class App(ThemedTk): if self.verify_saves_dir() is False: self.item_saves.state(["disabled"]) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) self.connection_error = FrameErrorSavesFolder(self) self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) messagebox.showerror(title="Configuration error", message="Saves folder seems to be invalid.") diff --git a/classes/custom/scrollable_frame.py b/classes/custom/scrollable_frame.py new file mode 100644 index 0000000..cddce87 --- /dev/null +++ b/classes/custom/scrollable_frame.py @@ -0,0 +1,151 @@ +from tkinter import NSEW, Canvas, Event, ttk + +from classes.custom.themed_frame import ThemedFrame + +FIT_WIDTH = "fit_width" +FIT_HEIGHT = "fit_height" + + +class ScrollableFrame(ThemedFrame): + """ + There is no way to scroll so we are + going to create a canvas and place the frame there. + Scrolling the canvas will give the illusion of scrolling + the frame + Partly taken from: + https://blog.tecladocode.com/tkinter-scrollable-frames/ + https://stackoverflow.com/a/17457843/11106801 + master_frame--------------------------------------------------------- + | dummy_canvas----------------------------------------- y_scroll-- | + | | self--------------------------------------------- | | | | + | | | | | | | | + | | | | | | | | + | | | | | | | | + | | ------------------------------------------------ | | | | + | ---------------------------------------------------- --------- | + | x_scroll--------------------------------------------- | + | | | | + | ---------------------------------------------------- | + -------------------------------------------------------------------- + """ + def __init__(self, master=None, scroll_speed:int=2, hscroll:bool=False, vscroll:bool=True, scrollbar_kwargs={}, **kwargs): + assert isinstance(scroll_speed, int), "`scroll_speed` must be an int" + self.scroll_speed = scroll_speed + + self.master_frame = ThemedFrame(master) + self.master_frame.grid_rowconfigure(0, weight=1) + self.master_frame.grid_columnconfigure(0, weight=1) + self.dummy_canvas = Canvas(self.master_frame, highlightthickness=0, **kwargs) + super().__init__(self.dummy_canvas) + + # Create the 2 scrollbars + if vscroll: + self.v_scrollbar = ttk.Scrollbar(self.master_frame, orient="vertical", command=self.dummy_canvas.yview, **scrollbar_kwargs) + self.v_scrollbar.grid(row=0, column=1, sticky=NSEW) + self.dummy_canvas.configure(yscrollcommand=self.v_scrollbar.set) + if hscroll: + self.h_scrollbar = ttk.Scrollbar(self.master_frame, orient="horizontal", command=self.dummy_canvas.xview, **scrollbar_kwargs) + self.h_scrollbar.grid(row=1, column=0, sticky=NSEW) + self.dummy_canvas.configure(xscrollcommand=self.h_scrollbar.set) + + # Bind to the mousewheel scrolling + self.dummy_canvas.bind_all("", self.scrolling_windows, add=True) + self.dummy_canvas.bind_all("", self.scrolling_linux, add=True) + self.dummy_canvas.bind_all("", self.scrolling_linux, add=True) + self.bind("", self.scrollbar_scrolling, add=True) + + # Place `self` inside `dummy_canvas` + self.dummy_canvas.create_window((0, 0), window=self, anchor="nw") + # Place `dummy_canvas` inside `master_frame` + self.dummy_canvas.grid(row=0, column=0, sticky=NSEW) + + self.pack = self.master_frame.pack + self.grid = self.master_frame.grid + self.place = self.master_frame.place + self.pack_forget = self.master_frame.pack_forget + self.grid_forget = self.master_frame.grid_forget + self.place_forget = self.master_frame.place_forget + + def scrolling_windows(self, event:Event) -> None: + assert event.delta != 0, "On Windows, `event.delta` should never be 0" + y_steps = int(-event.delta/abs(event.delta)*self.scroll_speed) + self.dummy_canvas.yview_scroll(y_steps, "units") + + def scrolling_linux(self, event:Event) -> None: + y_steps = self.scroll_speed + if event.num == 4: + y_steps *= -1 + self.dummy_canvas.yview_scroll(y_steps, "units") + + def scrollbar_scrolling(self, event:Event) -> None: + region = list(self.dummy_canvas.bbox("all")) + region[2] = max(self.dummy_canvas.winfo_width(), region[2]) + region[3] = max(self.dummy_canvas.winfo_height(), region[3]) + self.dummy_canvas.configure(scrollregion=region) + + def resize(self, fit:str=None, height:int=None, width:int=None) -> None: + """ + Resizes the frame to fit the widgets inside. You must either + specify (the `fit`) or (the `height` or/and the `width`) parameter. + Parameters: + fit:str `fit` can be either `FIT_WIDTH` or `FIT_HEIGHT`. + `FIT_WIDTH` makes sure that the frame's width can + fit all of the widgets. `FIT_HEIGHT` is simmilar + height:int specifies the height of the frame in pixels + width:int specifies the width of the frame in pixels + To do: + ALWAYS_FIT_WIDTH + ALWAYS_FIT_HEIGHT + """ + if height is not None: + self.dummy_canvas.config(height=height) + if width is not None: + self.dummy_canvas.config(width=width) + if fit == FIT_WIDTH: + super().update() + self.dummy_canvas.config(width=super().winfo_width()) + elif fit == FIT_HEIGHT: + super().update() + self.dummy_canvas.config(height=super().winfo_height()) + else: + raise ValueError("Unknow value for the `fit` parameter.") + fit = resize + +# import tkinter as tk +# from tkinter import ttk +# from classes.custom.themed_frame import ThemedFrame + +# class ScrollableFrame(ThemedFrame): + +# def __init__(self, container, *args, **kwargs): + +# super().__init__(container, *args, **kwargs) + +# self.canvas = tk.Canvas(self) +# scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) + +# self.canvas.bind_all("", self._on_mousewheel) + +# self.scrollable_frame = ThemedFrame(self.canvas) + +# self.scrollable_frame.bind( +# "", +# lambda e: self.canvas.configure( +# scrollregion=self.canvas.bbox("all") +# ) +# ) + +# self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") + +# self.canvas.configure(yscrollcommand=scrollbar.set) + +# self.canvas.pack(side="left", fill="both", expand=True) +# scrollbar.pack(side="right", fill="y") + +# def _on_mousewheel(self, event): +# shift = (event.state & 0x1) != 0 +# scroll = -1 if event.delta > 0 else 1 +# if shift: +# self.canvas.xview_scroll(scroll, "units") +# else: +# self.canvas.yview_scroll(scroll, "units") \ No newline at end of file diff --git a/classes/custom/themed_frame.py b/classes/custom/themed_frame.py index ca4596b..68fb1a7 100644 --- a/classes/custom/themed_frame.py +++ b/classes/custom/themed_frame.py @@ -1,9 +1,11 @@ from tkinter import Misc -import sv_ttk from tkinter.ttk import Frame +import sv_ttk + from modules.utils import get_string_mode + class ThemedFrame(Frame): def __init__(self, master: Misc, **kwargs) -> None: diff --git a/classes/frames/devices.py b/classes/frames/devices.py index ee892a3..f7f2f70 100644 --- a/classes/frames/devices.py +++ b/classes/frames/devices.py @@ -1,11 +1,101 @@ -from tkinter import EW, NS, ttk +from datetime import datetime +from functools import partial +from tkinter import LEFT, NSEW, E, W, ttk +from tkinter.messagebox import askyesno +import requests from ttkthemes import ThemedTk +from classes.custom.scrollable_frame import ScrollableFrame from classes.custom.themed_frame import ThemedFrame +from modules.utils import configGet -class FrameDevices(ThemedFrame): +class FrameDevices(ScrollableFrame): + + def __init__(self, master: ThemedTk, devices: list, **kwargs) -> None: + + super().__init__(master, **kwargs) + + master.title("Devices - Stardew Sync") + + # self["borderwidth"] = 1 + # self["relief"] = "solid" + + self.devices = devices + + # self.grid_columnconfigure(0, weight=1) + # self.grid_rowconfigure(0, weight=2) + + master.columnconfigure(1, weight=1) + + self.render_devices() + + def render_devices(self): + + i = 0 + for device in self.devices: + + device_frame = ThemedFrame(self, style="Card.TFrame") + device_frame.grid(column=0, row=i, pady=9, padx=9, sticky=NSEW) + + # self.device_frame["borderwidth"] = 1 + # self.device_frame["relief"] = "solid" + + device_frame.grid_columnconfigure(0, weight=1) + device_frame.grid_columnconfigure(1, weight=3) + device_frame.grid_columnconfigure(2, weight=3) + + device_title = ttk.Label(device_frame, text=device["name"], font=("SunValleyBodyFont", 12), justify=LEFT, width=46) + device_title.grid(column=0, row=0, padx=9, pady=9, sticky=W) + + last_upload = "N/A" if device["last_save"] == 0 else datetime.utcfromtimestamp(device["last_save"]).strftime("%d.%m.%Y %H:%M") + device_description = ttk.Label(device_frame, text=f'OS: {device["os"]}\nClient: {device["client"]}\nLast upload: {last_upload}', width=46) + device_description.grid(column=0, row=1, padx=9, pady=9, sticky=W) + + buttons_frame = ThemedFrame(device_frame) + buttons_frame.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9) + buttons_frame.grid_columnconfigure(0, weight=1) + + button_device_rename = ttk.Button(buttons_frame, text="Rename", width=11) + button_device_rename.grid(column=0, row=0, padx=9, sticky=E) + + button_device_delete_action = partial(self.device_delete, device["name"]) + button_device_delete = ttk.Button(buttons_frame, text="Delete", style="Accent.TButton", width=11, command=button_device_delete_action) + button_device_delete.grid(column=1, row=0, sticky=W) + + if device["name"] == configGet("name"): + button_device_rename.state(["disabled"]) + button_device_delete.state(["disabled"]) + + i += 1 + + if i+1 != len(self.devices): + divider = ttk.Separator(self, orient="horizontal") + divider.grid(column=0, row=i+1, pady=9) + i += 1 + + def device_delete(self, name: str): + + decision = askyesno(title="Device removal", message=f"You are about to remove the device '{name}' and this will also remove all the save files uploaded by this device. Are you sure you want to continue?") + + if decision is False: + return + + requests.delete(f'{configGet("address")}/devices/{name}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) + + for k in range(len(self.devices)): + print(k) + if self.devices[k]["name"] == name: + del self.devices[k] + break + + for widget in self.winfo_children(): + widget.destroy() + + self.render_devices() + +class FrameDevicesEmpty(ThemedFrame): def __init__(self, master: ThemedTk, **kwargs) -> None: @@ -13,11 +103,13 @@ class FrameDevices(ThemedFrame): master.title("Devices - Stardew Sync") - self["borderwidth"] = 2 - self["relief"] = "solid" + # self["borderwidth"] = 1 + # self["relief"] = "solid" - for btn in range(0, 10): - ttk.Button(self, text="DEVICES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=2) - self.scrollbar = ttk.Scrollbar(self, orient="vertical") - self.scrollbar.grid(column=1, row=0, sticky=NS) \ No newline at end of file + master.columnconfigure(1, weight=1) + + self.label = ttk.Label(self, text="No devices found") + self.label.grid(column=0, row=0, padx=9, pady=9) \ No newline at end of file diff --git a/classes/frames/settings.py b/classes/frames/settings.py index ba0bc86..eeab385 100644 --- a/classes/frames/settings.py +++ b/classes/frames/settings.py @@ -1,4 +1,5 @@ from os import getenv, path +import platform from tkinter import N, NSEW, S, W, E, END, IntVar, StringVar, filedialog, messagebox, ttk from ttkthemes import ThemedTk @@ -7,6 +8,7 @@ from modules.theme_titlebar import theme_title_bar from modules.utils import configGet, configSet, get_string_mode, use_dark_mode from modules.logger import logger +from urllib.parse import urlencode, quote import requests import sv_ttk @@ -17,29 +19,41 @@ class FrameSettings(ThemedFrame): super().__init__(master, **kwargs) - master.title("Configuration") + master.title("Settings - Stardew Sync") self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=3) master.columnconfigure(1, weight=1) + # Name + self.name_label = ttk.Label(self, text="Name:") + self.name_label.grid(column=0, row=0, sticky=W, padx=9, pady=9) + + self.name_entry = ttk.Entry(self) + self.name_entry.grid(column=1, row=0, sticky=N+S+E+W, padx=9, pady=9) + if configGet("name") is not None: + self.name_entry.insert(0, configGet("name")) + else: + self.name_entry.insert(0, str(platform.node())) + # ==== + # Address self.address_label = ttk.Label(self, text="Address:") - self.address_label.grid(column=0, row=0, sticky=W, padx=9, pady=9) + self.address_label.grid(column=0, row=1, sticky=W, padx=9, pady=9) self.address_entry = ttk.Entry(self) - self.address_entry.grid(column=1, row=0, sticky=N+S+E+W, padx=9, pady=9) + self.address_entry.grid(column=1, row=1, sticky=N+S+E+W, padx=9, pady=9) if configGet("address") is not None: self.address_entry.insert(0, configGet("address")) # ======= # API Key self.apikey_label = ttk.Label(self, text="API key:") - self.apikey_label.grid(column=0, row=1, sticky=W, padx=9, pady=9) + self.apikey_label.grid(column=0, row=2, sticky=W, padx=9, pady=9) self.apikey_entry = ttk.Entry(self) - self.apikey_entry.grid(column=1, row=1, sticky=N+S+E+W, padx=9, pady=9) + self.apikey_entry.grid(column=1, row=2, sticky=N+S+E+W, padx=9, pady=9) if configGet("apikey") is not None: self.apikey_entry.insert(0, configGet("apikey")) # ======= @@ -50,19 +64,19 @@ class FrameSettings(ThemedFrame): else: self.self_signed_check_bool = IntVar() self.self_signed_check = ttk.Checkbutton(self, text="Allow self-signed certificates", variable=self.self_signed_check_bool) - self.self_signed_check.grid(column=1, row=2, sticky=W, padx=9, pady=9) + self.self_signed_check.grid(column=1, row=3, sticky=W, padx=9, pady=9) # =========== # Saves location self.saves_location_label = ttk.Label(self, text="Saves location:") - self.saves_location_label.grid(column=0, row=3, sticky=W, padx=9, pady=9) + self.saves_location_label.grid(column=0, row=4, sticky=W, padx=9, pady=9) self.saves_frame = ThemedFrame(self) - self.saves_frame.grid(column=1, row=3, sticky=N+S+E+W, padx=9, pady=9) + self.saves_frame.grid(column=1, row=4, sticky=N+S+E+W, padx=9, pady=9) self.saves_frame.grid_columnconfigure(0, weight=1) self.saves_frame.grid_columnconfigure(1, weight=3) - self.saves_location_entry = ttk.Entry(self.saves_frame, width=36) + self.saves_location_entry = ttk.Entry(self.saves_frame, width=30) self.saves_location_entry.grid(column=0, row=0, sticky=NSEW) if configGet("saves_location") is not None: self.saves_location_entry.insert(0, configGet("saves_location")) @@ -87,7 +101,7 @@ class FrameSettings(ThemedFrame): # ============== self.buttons_frame = ThemedFrame(self) - self.buttons_frame.grid(column=0, columnspan=2, sticky=NSEW, padx=9, pady=9) + self.buttons_frame.grid(column=0, columnspan=2, row=6, sticky=NSEW, padx=9, pady=9) self.buttons_frame.grid_columnconfigure(0, weight=1) self.validate_button = ttk.Button(self.buttons_frame, text="Validate", width=11, command=self.validate_configuration) @@ -129,7 +143,25 @@ class FrameSettings(ThemedFrame): else: self.address_text = None - configSet(["address"], self.address_text) + # ========================= + if self.name_entry.get().strip() == configGet("name"): + existing_device = requests.get(f'{self.address_entry.get()}/devices/{quote(configGet("name").encode("utf-8"))}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + if existing_device.status_code != 200: + requests.post(f'{self.address_entry.get()}/devices?{urlencode({"name": quote(configGet("name").encode("utf-8")), "os": platform.system()+" "+platform.release(), "client": f"SyncTk {self.master.__version__}"})}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + else: + if configGet("name") is not None: + existing_device_before = requests.get(f'{self.address_entry.get()}/devices/{quote(configGet("name").encode("utf-8"))}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + if existing_device_before.status_code == 200: + requests.patch(f'{self.address_entry.get()}/devices/{quote(configGet("name").encode("utf-8"))}?{urlencode({"new_name": quote(self.name_entry.get().strip().encode("utf-8")), "os": platform.system()+" "+platform.release(), "client": f"SyncTk {self.master.__version__}"})}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + else: + device_created = requests.post(f'{self.address_entry.get()}/devices?{urlencode({"name": quote(self.name_entry.get().strip().encode("utf-8")), "os": platform.system()+" "+platform.release(), "client": f"SyncTk {self.master.__version__}"})}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + if device_created.status_code != 204: + messagebox.showerror(title="Name error", message=f"Could not register device in database using name '{self.name_entry.get().strip()}' with error:\n\n{device_created.json()}") + return + # ========================= + + configSet(["name"], self.name_entry.get().strip()) + configSet(["address"], self.address_entry.get()) configSet(["apikey"], self.apikey_entry.get()) configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) configSet(["saves_location"], self.saves_location_entry.get()) @@ -151,6 +183,11 @@ class FrameSettings(ThemedFrame): self.master.item_devices.state(["!disabled"]) def validate_configuration(self): + + if self.name_entry.get().strip() == "": + logger.error(f"Name {self.name_entry.get().strip()} is not a valid name") + messagebox.showerror(title="Name error", message="Provided device name is not valid. Please provide a valid one.") + return if len(self.address_entry.get()) > 0: if self.address_entry.get().endswith("/"): @@ -185,11 +222,15 @@ class FrameSettings(ThemedFrame): messagebox.showerror(title="Location error", message="Saves folder seems to be invalid. Please provide a valid directory path where Stardew Valley's save files (and folders) are stored.") return - configSet(["address"], self.address_entry.get()) - configSet(["apikey"], self.apikey_entry.get()) - configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) - configSet(["saves_location"], self.saves_location_entry.get()) + # ========================= + if self.name_entry.get().strip() != configGet("name"): + existing_device_after = requests.get(f'{self.address_entry.get()}/devices/{self.name_entry.get().strip()}', headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + if existing_device_after == 200: + logger.error(f"Device with name {self.name_entry.get().strip()} already exists") + messagebox.showerror(title="Name error", message=f"Device with name '{self.name_entry.get().strip()}' already exists on the server. Please choose another name or rename that device first.") + return + # ========================= self.save_button.state(["!disabled"]) - messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!") \ No newline at end of file + # messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!") \ No newline at end of file diff --git a/main.py b/main.py index 4a9d6d5..359e0d1 100644 --- a/main.py +++ b/main.py @@ -5,6 +5,7 @@ from modules.utils import jsonSave if not path.exists("config.json"): jsonSave( { + "name": None, "address": None, "apikey": None, "allow_self_signed": False,