cryptor-app 0.0.49__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cryptor_app/__init__.py +0 -0
- cryptor_app/__main__.py +14 -0
- cryptor_app/app_files/run_cookie.py +57 -0
- cryptor_app/app_files/welcome.py +22 -0
- cryptor_app/config_files/ai_texter.py +234 -0
- cryptor_app/config_files/alert.py +63 -0
- cryptor_app/config_files/clock_frame.py +35 -0
- cryptor_app/config_files/files_list.py +95 -0
- cryptor_app/config_files/label_frame.py +121 -0
- cryptor_app/config_files/line_numbers.py +34 -0
- cryptor_app/config_files/monitor_cookie.py +140 -0
- cryptor_app/config_files/progress.py +140 -0
- cryptor_app/config_files/side_panel.py +9 -0
- cryptor_app/config_files/styles.py +89 -0
- cryptor_app/cryp.ico +0 -0
- cryptor_app/extras/encryt.py +89 -0
- cryptor_app/extras/generate_secrets.py +17 -0
- cryptor_app/extras/init_run.py +81 -0
- cryptor_app/extras/models.py +235 -0
- cryptor_app/main.py +147 -0
- cryptor_app/tabs/base_frame_tab.py +252 -0
- cryptor_app/tabs/sign_in_tab.py +76 -0
- cryptor_app/tabs/sign_up_tab.py +104 -0
- cryptor_app-0.0.49.dist-info/METADATA +112 -0
- cryptor_app-0.0.49.dist-info/RECORD +29 -0
- cryptor_app-0.0.49.dist-info/WHEEL +5 -0
- cryptor_app-0.0.49.dist-info/entry_points.txt +2 -0
- cryptor_app-0.0.49.dist-info/licenses/LICENSE +674 -0
- cryptor_app-0.0.49.dist-info/top_level.txt +1 -0
cryptor_app/__init__.py
ADDED
|
File without changes
|
cryptor_app/__main__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from cryptor_app.main import run_dependency_check, run_connection, create_main_app
|
|
2
|
+
|
|
3
|
+
def main():
|
|
4
|
+
proceed_to_app = run_dependency_check()
|
|
5
|
+
|
|
6
|
+
if proceed_to_app:
|
|
7
|
+
print("Dependencies verified. Initializing secure application database engines...")
|
|
8
|
+
run_connection()
|
|
9
|
+
create_main_app()
|
|
10
|
+
else:
|
|
11
|
+
print("Application startup terminated by user.")
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
def Run_Cookie(root, cookie, create_main_app):
|
|
4
|
+
# Lazy imports to keep startup snappy
|
|
5
|
+
from cryptor_app.extras.models import logout_func
|
|
6
|
+
from cryptor_app.config_files.monitor_cookie import cookie_monitor
|
|
7
|
+
|
|
8
|
+
if cookie is None:
|
|
9
|
+
print("Not logged in.")
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
if not hasattr(root, "monitor_active"):
|
|
13
|
+
root.monitor_active = False
|
|
14
|
+
|
|
15
|
+
expire_time = datetime.fromisoformat(cookie.cookie_expire_time)
|
|
16
|
+
time_remaining = expire_time - datetime.now()
|
|
17
|
+
|
|
18
|
+
# --- SCENARIO 1: Cookie has completely expired at 0 seconds ---
|
|
19
|
+
if time_remaining <= timedelta(seconds=0):
|
|
20
|
+
print("Session expired! Forcibly terminating environment...")
|
|
21
|
+
|
|
22
|
+
# 🛡️ Dynamic Guard: Forcibly kill the lingering popup alert box if it's still open on screen
|
|
23
|
+
if hasattr(root, "active_cookie_popup") and root.active_cookie_popup is not None:
|
|
24
|
+
try:
|
|
25
|
+
root.active_cookie_popup.destroy()
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
root.active_cookie_popup = None
|
|
29
|
+
|
|
30
|
+
if hasattr(root, "check_run_id") and root.check_run_id is not None:
|
|
31
|
+
root.after_cancel(root.check_run_id)
|
|
32
|
+
root.check_run_id = None
|
|
33
|
+
|
|
34
|
+
logout_func(cookie[0])
|
|
35
|
+
root.destroy()
|
|
36
|
+
create_main_app()
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# --- SCENARIO 2: Cookie expires soon (<= 3 minutes remaining) ---
|
|
40
|
+
elif time_remaining <= timedelta(minutes=3):
|
|
41
|
+
# Only spawn a new window if one isn't currently alive
|
|
42
|
+
if not root.monitor_active:
|
|
43
|
+
root.monitor_active = True
|
|
44
|
+
print(f"Session expiring soon! {time_remaining.total_seconds():.0f}s left.")
|
|
45
|
+
|
|
46
|
+
# Instantiate the window panel
|
|
47
|
+
cookie_window = cookie_monitor(root)
|
|
48
|
+
|
|
49
|
+
# 🛡️ Keep a reference on root so our 1-second checker can access and destroy it at 0s
|
|
50
|
+
root.active_cookie_popup = cookie_window.cookie_box
|
|
51
|
+
|
|
52
|
+
# REMOVED: root.wait_window() - We allow the main thread loop to keep ticking!
|
|
53
|
+
|
|
54
|
+
# --- SCENARIO 3: Session is perfectly safe (> 3 minutes remaining) ---
|
|
55
|
+
else:
|
|
56
|
+
root.monitor_active = False
|
|
57
|
+
root.active_cookie_popup = None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# # # # #
|
|
2
|
+
# Installed Modules
|
|
3
|
+
# # # # #
|
|
4
|
+
from tkinter.ttk import Frame, Notebook
|
|
5
|
+
|
|
6
|
+
# # # #
|
|
7
|
+
# Local folders Modules
|
|
8
|
+
# # # #
|
|
9
|
+
from cryptor_app.tabs.sign_in_tab import sign_in_tab
|
|
10
|
+
from cryptor_app.tabs.sign_up_tab import sign_up_tab
|
|
11
|
+
|
|
12
|
+
def welcome_frame(root, create_main_app):
|
|
13
|
+
root.geometry('256x332')
|
|
14
|
+
|
|
15
|
+
welcome_fr = Frame(root)
|
|
16
|
+
|
|
17
|
+
notebook = Notebook(welcome_fr, style="Notebook.TNotebook")
|
|
18
|
+
notebook.pack(fill='both', pady=2, padx=2, expand=1)
|
|
19
|
+
sign_in_tab(notebook, root, create_main_app)
|
|
20
|
+
sign_up_tab(notebook, root)
|
|
21
|
+
|
|
22
|
+
return welcome_fr
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import Frame, Button, Label, Combobox
|
|
3
|
+
from tkinter.messagebox import showerror, askokcancel
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import threading
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
class AITexterPanel(Frame):
|
|
9
|
+
def __init__(self, master, text_scroll, title_entry, for_var, editor_container):
|
|
10
|
+
super().__init__(master, style="Header.TFrame")
|
|
11
|
+
|
|
12
|
+
self.text_scroll = text_scroll
|
|
13
|
+
self.title_entry = title_entry
|
|
14
|
+
self.for_var = for_var
|
|
15
|
+
self.editor_container = editor_container
|
|
16
|
+
|
|
17
|
+
# Context Preset Dropdown Setup
|
|
18
|
+
self.preset_var = tk.StringVar(value="Generate Secure Password")
|
|
19
|
+
self.ai_dropdown = Combobox(self, textvariable=self.preset_var, values=["Generate Secure Password", "Draft Cryptographic Note", "Custom Prompt..."], state="readonly", width=22)
|
|
20
|
+
self.ai_dropdown.pack(side='left', padx=(0, 6))
|
|
21
|
+
|
|
22
|
+
# Prompt Entry Field
|
|
23
|
+
self.prompt_var = tk.StringVar(value="Create a unique 16-character complex password with symbols.")
|
|
24
|
+
self.prompt_entry = tk.Entry(self, textvariable=self.prompt_var, bg="#dd3663", fg="#ffffff", insertbackground="#FA0909", relief="flat")
|
|
25
|
+
self.prompt_entry.pack(side='left', fill='x', expand=True, padx=(0, 6))
|
|
26
|
+
|
|
27
|
+
self.ai_dropdown.bind("<<ComboboxSelected>>", self.handle_preset_change)
|
|
28
|
+
|
|
29
|
+
# Action Button
|
|
30
|
+
self.ai_btn = Button(
|
|
31
|
+
self,
|
|
32
|
+
text="●",
|
|
33
|
+
style="RoundAI.TButton",
|
|
34
|
+
command=self.trigger_ai_generation,
|
|
35
|
+
cursor="hand2"
|
|
36
|
+
)
|
|
37
|
+
self.ai_btn.pack(side='left', padx=(2, 0))
|
|
38
|
+
|
|
39
|
+
# Tracking state parameters for the animation engine loop
|
|
40
|
+
self.is_generating = False
|
|
41
|
+
self.blink_state = False
|
|
42
|
+
self.overlay = None
|
|
43
|
+
|
|
44
|
+
def handle_preset_change(self, event):
|
|
45
|
+
choice = self.preset_var.get()
|
|
46
|
+
if choice == "Generate Secure Password":
|
|
47
|
+
self.prompt_var.set("Create a unique 16-character complex password with symbols.")
|
|
48
|
+
elif choice == "Draft Cryptographic Note":
|
|
49
|
+
self.prompt_var.set("Draft a secure template for storing multi-factor authentication backup keys.")
|
|
50
|
+
elif choice == "Custom Prompt...":
|
|
51
|
+
self.prompt_var.set("")
|
|
52
|
+
self.prompt_entry.focus()
|
|
53
|
+
|
|
54
|
+
def trigger_ai_generation(self):
|
|
55
|
+
prompt_text = self.prompt_var.get().strip()
|
|
56
|
+
if not prompt_text:
|
|
57
|
+
showerror("Prompt Empty", "Please type an AI instruction or select a security preset first.")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Smart Insertion Guard
|
|
61
|
+
current_content = self.text_scroll.get(1.0, 'end-1c').strip()
|
|
62
|
+
if current_content:
|
|
63
|
+
confirm = askokcancel("Overwrite Warning", "The workspace contains active content. Generating fresh text will wipe the screen. Continue?")
|
|
64
|
+
if not confirm:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Engage generation flags to trigger the blinking loop sequence
|
|
68
|
+
self.is_generating = True
|
|
69
|
+
self.ai_btn.config(state="disabled")
|
|
70
|
+
self.animate_blinking()
|
|
71
|
+
self.winfo_toplevel().update_idletasks()
|
|
72
|
+
|
|
73
|
+
# Instantiate and map our sleek loading overlay right over the editor frame zone!
|
|
74
|
+
self.overlay = AILoadingOverlay(self.editor_container)
|
|
75
|
+
|
|
76
|
+
# 🚀 FIX: Pass a synchronous runner method as the thread target to prevent window freezing
|
|
77
|
+
worker = threading.Thread(target=self._thread_run_loop, args=(prompt_text,))
|
|
78
|
+
worker.daemon = True
|
|
79
|
+
worker.start()
|
|
80
|
+
|
|
81
|
+
def _thread_run_loop(self, prompt_text):
|
|
82
|
+
""" Secondary isolation bridge that safely initializes a fresh async loop inside the thread """
|
|
83
|
+
asyncio.run(self.async_ai_worker(prompt_text))
|
|
84
|
+
|
|
85
|
+
def animate_blinking(self):
|
|
86
|
+
""" Continuous style-toggling function running on a background loop """
|
|
87
|
+
if not self.winfo_exists() or not self.is_generating:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Toggle between two visual style variations
|
|
91
|
+
if self.blink_state:
|
|
92
|
+
self.ai_btn.config(text="○", style="RoundAI.TButton")
|
|
93
|
+
else:
|
|
94
|
+
self.ai_btn.config(text="●", style="RoundAI.TButton")
|
|
95
|
+
|
|
96
|
+
self.blink_state = not self.blink_state
|
|
97
|
+
|
|
98
|
+
# Keep ticking every 200 milliseconds
|
|
99
|
+
self.after(200, self.animate_blinking)
|
|
100
|
+
|
|
101
|
+
async def async_ai_worker(self, prompt_text):
|
|
102
|
+
try:
|
|
103
|
+
import secrets, string
|
|
104
|
+
from ollama import AsyncClient
|
|
105
|
+
|
|
106
|
+
# Use non-blocking async sleep instead of time.sleep()
|
|
107
|
+
await asyncio.sleep(1)
|
|
108
|
+
|
|
109
|
+
client = AsyncClient()
|
|
110
|
+
|
|
111
|
+
if "password" in prompt_text.lower():
|
|
112
|
+
alphabet = string.ascii_letters + string.digits + "!@#$%^&*()_+"
|
|
113
|
+
generated_result = "".join(secrets.choice(alphabet) for _ in range(16))
|
|
114
|
+
generated_result = f"--- GENERATED SECURE CREDENTIAL ---\n\nPassword: {generated_result}\n\nGenerated On: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n[Keep this note encrypted for maximum safety]"
|
|
115
|
+
else:
|
|
116
|
+
response = await client.chat(
|
|
117
|
+
model='llama3:8b',
|
|
118
|
+
messages=[
|
|
119
|
+
{
|
|
120
|
+
'role': 'system',
|
|
121
|
+
'content': 'You are a secure local cryptographic helper. Output requested text, credentials, or password data structures immediately. Do not introduce conversational chatter or say "Here is your output".'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
'role': 'user',
|
|
125
|
+
'content': prompt_text
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
generated_result = response['message']['content']
|
|
130
|
+
|
|
131
|
+
# Route data output back to GUI loop safely
|
|
132
|
+
self.winfo_toplevel().after(0, lambda: self.direct_ai_output_to_editor(generated_result))
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.winfo_toplevel().after(0, lambda: self.handle_worker_error(str(e)))
|
|
136
|
+
|
|
137
|
+
def direct_ai_output_to_editor(self, result_text):
|
|
138
|
+
self.is_generating = False
|
|
139
|
+
|
|
140
|
+
if self.overlay:
|
|
141
|
+
self.overlay.destroy()
|
|
142
|
+
self.overlay = None
|
|
143
|
+
|
|
144
|
+
self.text_scroll.delete(1.0, 'end')
|
|
145
|
+
self.text_scroll.insert(1.0, result_text)
|
|
146
|
+
|
|
147
|
+
if "Password" in result_text:
|
|
148
|
+
self.title_entry.insert("1.0","Generated Access Token")
|
|
149
|
+
self.for_var.set("Secure Credential Access Key")
|
|
150
|
+
|
|
151
|
+
self.ai_btn.config(state="normal", text="●", style="RoundAI.TButton")
|
|
152
|
+
self.text_scroll.focus()
|
|
153
|
+
|
|
154
|
+
def handle_worker_error(self, error_message):
|
|
155
|
+
self.is_generating = False
|
|
156
|
+
if self.overlay:
|
|
157
|
+
self.overlay.destroy()
|
|
158
|
+
self.overlay = None
|
|
159
|
+
|
|
160
|
+
showerror("AI API Error", f"Transaction failed:\n{error_message}")
|
|
161
|
+
self.ai_btn.config(state="normal", text="●", style="RoundAI.TButton")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ----------------------------------------------------
|
|
165
|
+
# 🛡️ SLEEK HIGH-REACTIVITY LOADING OVERLAY PANEL
|
|
166
|
+
# ----------------------------------------------------
|
|
167
|
+
class AILoadingOverlay(Frame):
|
|
168
|
+
def __init__(self, master):
|
|
169
|
+
super().__init__(master, relief="flat")
|
|
170
|
+
|
|
171
|
+
self.place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
172
|
+
self.configure(style="Header.TFrame")
|
|
173
|
+
|
|
174
|
+
# Centered structural layout alignment holder
|
|
175
|
+
center_box = Frame(self, style="Header.TFrame")
|
|
176
|
+
center_box.place(relx=0, rely=0.45, relwidth=1, anchor="w")
|
|
177
|
+
|
|
178
|
+
# 🔄 Native Vector Spinner Canvas Instance
|
|
179
|
+
self.spinner_canvas = tk.Canvas(
|
|
180
|
+
center_box,
|
|
181
|
+
width=60,
|
|
182
|
+
height=60,
|
|
183
|
+
bg="#111111",
|
|
184
|
+
highlightthickness=0
|
|
185
|
+
)
|
|
186
|
+
self.spinner_canvas.pack(pady=10)
|
|
187
|
+
|
|
188
|
+
self.status_lbl = Label(
|
|
189
|
+
center_box,
|
|
190
|
+
text="AI COPILOT IS WORKING\nGenerating secure cryptographic content, please wait...",
|
|
191
|
+
font=('Arial', 11, 'bold'),
|
|
192
|
+
foreground="#ffffff",
|
|
193
|
+
background="#111111",
|
|
194
|
+
justify="center",
|
|
195
|
+
anchor="center"
|
|
196
|
+
)
|
|
197
|
+
self.status_lbl.pack(pady=5)
|
|
198
|
+
|
|
199
|
+
self.angle_step = 0
|
|
200
|
+
self.animate_spinner()
|
|
201
|
+
|
|
202
|
+
def animate_spinner(self):
|
|
203
|
+
""" Generates a smooth, native non-blocking vector rotation loop """
|
|
204
|
+
if not self.winfo_exists():
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
# Clear the previous drawing frame to prevent memory accumulation leaks
|
|
208
|
+
self.spinner_canvas.delete("all")
|
|
209
|
+
|
|
210
|
+
# Calculate the active starting angle offset frame
|
|
211
|
+
start_angle = (self.angle_step * 24) % 360
|
|
212
|
+
|
|
213
|
+
# Draw the primary background matte tracking track ring
|
|
214
|
+
self.spinner_canvas.create_oval(
|
|
215
|
+
6, 6, 54, 54,
|
|
216
|
+
outline="#252526",
|
|
217
|
+
width=3
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Render the active accent loading arc highlight
|
|
221
|
+
self.spinner_canvas.create_arc(
|
|
222
|
+
6, 6, 54, 54,
|
|
223
|
+
start=start_angle,
|
|
224
|
+
extent=90,
|
|
225
|
+
style="arc",
|
|
226
|
+
outline="#3fa8a5",
|
|
227
|
+
width=4,
|
|
228
|
+
activedash=None
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self.angle_step += 1
|
|
232
|
+
|
|
233
|
+
# Ticks every 45ms for an incredibly fluid, hardware-accelerated motion effect
|
|
234
|
+
self.after(45, self.animate_spinner)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import Frame, Button, Label
|
|
3
|
+
|
|
4
|
+
class alert_poper(Frame):
|
|
5
|
+
def __init__(self, master=None):
|
|
6
|
+
super().__init__(master)
|
|
7
|
+
|
|
8
|
+
self.stringMsg = tk.StringVar()
|
|
9
|
+
self.selected_notebook = tk.StringVar()
|
|
10
|
+
|
|
11
|
+
# 1. Initialize modern dark flat modal window dimensions
|
|
12
|
+
self.pop = tk.Toplevel(master, relief="flat", takefocus=True, padx=12, pady=12)
|
|
13
|
+
self.pop.attributes('-topmost', True)
|
|
14
|
+
self.pop.resizable(0, 0)
|
|
15
|
+
self.pop.title("System Alert")
|
|
16
|
+
self.pop.configure(bg="#1e1e1e") # Dark fallback background color
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
self.pop.wm_iconbitmap('cryp.ico')
|
|
20
|
+
except:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
# 2. Apply theme label styling flags
|
|
24
|
+
Label(
|
|
25
|
+
self.pop,
|
|
26
|
+
text="Missing information from the database.",
|
|
27
|
+
font=('Arial', 10, 'bold'),
|
|
28
|
+
foreground="#ffffff",
|
|
29
|
+
background="#1e1e1e"
|
|
30
|
+
).pack(side='top', fill='both', pady=(4, 0))
|
|
31
|
+
|
|
32
|
+
# Inner container layout wrap block
|
|
33
|
+
self.main_frm = Frame(self.pop, padding=(4, 16), style="Header.TFrame")
|
|
34
|
+
self.main_frm.pack(fill="both", side="bottom")
|
|
35
|
+
|
|
36
|
+
# 3. Dynamic layout action button integration matching design theme configurations
|
|
37
|
+
self.yes_btn = Button(
|
|
38
|
+
self.main_frm,
|
|
39
|
+
text='Go to register',
|
|
40
|
+
style="Signup.TButton",
|
|
41
|
+
command=lambda: self.setVal("True"),
|
|
42
|
+
cursor="hand2"
|
|
43
|
+
)
|
|
44
|
+
self.yes_btn.pack(side="left", padx=(0, 4))
|
|
45
|
+
|
|
46
|
+
self.no_btn = Button(
|
|
47
|
+
self.main_frm,
|
|
48
|
+
text="Cancel",
|
|
49
|
+
style="Delete.TButton",
|
|
50
|
+
command=lambda: self.pop.destroy(),
|
|
51
|
+
cursor="hand2"
|
|
52
|
+
)
|
|
53
|
+
self.no_btn.pack(side="right", padx=(4, 0))
|
|
54
|
+
|
|
55
|
+
self.pack()
|
|
56
|
+
|
|
57
|
+
def setVal(self, val):
|
|
58
|
+
""" Sets the control variables and dismisses the alert frame cleanly """
|
|
59
|
+
self.selected_notebook.set(val)
|
|
60
|
+
self.pop.destroy()
|
|
61
|
+
|
|
62
|
+
def manageWindow(self, winvar):
|
|
63
|
+
self.stringMsg.set(winvar)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import *
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
class ClockFrame(LabelFrame):
|
|
6
|
+
def __init__(self, master):
|
|
7
|
+
super().__init__(master)
|
|
8
|
+
self['text'] = 'Clock'
|
|
9
|
+
|
|
10
|
+
# change the background color to black
|
|
11
|
+
self.label = Label(
|
|
12
|
+
self,
|
|
13
|
+
style='Clock.TLabel',
|
|
14
|
+
text=self.time_string(),
|
|
15
|
+
font=('Digital-7', 16))
|
|
16
|
+
|
|
17
|
+
self.label.pack(expand=True, fill='both')
|
|
18
|
+
|
|
19
|
+
# schedule an update every 1 second
|
|
20
|
+
self.label.after(1000, self.update)
|
|
21
|
+
|
|
22
|
+
def time_string(self):
|
|
23
|
+
hr = time.strftime('%H')
|
|
24
|
+
mn = time.strftime('%M')
|
|
25
|
+
sc = time.strftime('%S')
|
|
26
|
+
return f'{hr} hr\n{mn} min\n{sc} s'
|
|
27
|
+
|
|
28
|
+
def update(self):
|
|
29
|
+
""" update the label every 1 second """
|
|
30
|
+
self.label.configure(text=self.time_string())
|
|
31
|
+
|
|
32
|
+
# schedule another timer
|
|
33
|
+
self.label.after(1000, self.update)
|
|
34
|
+
|
|
35
|
+
self.grid(row=4, column=0, padx=5, pady=(5,50), ipady=5)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import *
|
|
3
|
+
from cryptor_app.extras.models import retrieveFiles, verifyCookie
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
lifont = ('Times', 12, 'italic')
|
|
7
|
+
|
|
8
|
+
class file_list(Frame):
|
|
9
|
+
def __init__(self, master=None):
|
|
10
|
+
super().__init__(master)
|
|
11
|
+
self.pack(fill='both', expand=True)
|
|
12
|
+
|
|
13
|
+
session_cookie = verifyCookie()
|
|
14
|
+
self.all_files = retrieveFiles(session_cookie[2])
|
|
15
|
+
|
|
16
|
+
self.doc_id = tk.StringVar()
|
|
17
|
+
self.deleted_id = tk.StringVar()
|
|
18
|
+
|
|
19
|
+
Label(self, text=f"Total Saved Files: {len(self.all_files)}", font=lifont, anchor='center', padding=4).pack(side='top', fill='x')
|
|
20
|
+
|
|
21
|
+
self.list_frame = Frame(self)
|
|
22
|
+
self.list_frame.pack(fill='both', expand=True, padx=2, pady=2)
|
|
23
|
+
|
|
24
|
+
self.lst_files = Treeview(self.list_frame, selectmode='browse')
|
|
25
|
+
self.lst_scrbar = Scrollbar(self.list_frame, command=self.lst_files.yview)
|
|
26
|
+
self.lst_scrbar.pack(side='right', fill='y')
|
|
27
|
+
self.lst_files['yscrollcommand'] = self.lst_scrbar.set
|
|
28
|
+
|
|
29
|
+
# Change columns to display Title and Purpose ("For")
|
|
30
|
+
self.lst_files['columns'] = ('title', 'purpose')
|
|
31
|
+
|
|
32
|
+
self.lst_files.column('#0', width=0, stretch='no')
|
|
33
|
+
self.lst_files.column('title', width=130, anchor='w')
|
|
34
|
+
self.lst_files.column('purpose', width=130, anchor='w')
|
|
35
|
+
|
|
36
|
+
# Set readable column headers
|
|
37
|
+
self.lst_files.heading('#0', text='', anchor='center')
|
|
38
|
+
self.lst_files.heading('title', text='Title', anchor='w')
|
|
39
|
+
self.lst_files.heading('purpose', text='Purpose / For', anchor='w')
|
|
40
|
+
|
|
41
|
+
self.populate_tree()
|
|
42
|
+
|
|
43
|
+
self.lst_files.bind('<<TreeviewSelect>>', self.select_record)
|
|
44
|
+
self.lst_files.pack(fill='both', expand=True)
|
|
45
|
+
|
|
46
|
+
def select_record(self, event):
|
|
47
|
+
""" Fires automatically whenever an item inside the file explorer sidebar is highlighted """
|
|
48
|
+
widget = event.widget
|
|
49
|
+
selected_item = widget.focus()
|
|
50
|
+
|
|
51
|
+
if selected_item:
|
|
52
|
+
# 🛡️ FIX: Pull the actual file_id out of text, NOT the title values array
|
|
53
|
+
file_id_str = widget.item(selected_item, 'text')
|
|
54
|
+
if file_id_str:
|
|
55
|
+
self.doc_id.set(file_id_str)
|
|
56
|
+
|
|
57
|
+
def populate_tree(self):
|
|
58
|
+
""" Sorts files chronologically and builds the visible treeview entries """
|
|
59
|
+
|
|
60
|
+
# ⏱️ Sort files dynamically: Latest updated entries bubble directly to the top
|
|
61
|
+
try:
|
|
62
|
+
# item[7] is our last_updated ISO string timestamp from your database schema
|
|
63
|
+
sorted_files = sorted(
|
|
64
|
+
self.all_files,
|
|
65
|
+
key=lambda x: datetime.fromisoformat(x[7]) if (len(x) > 7 and x[7]) else datetime.min,
|
|
66
|
+
reverse=True
|
|
67
|
+
)
|
|
68
|
+
except Exception:
|
|
69
|
+
# Fallback to unsorted state if any database timestamps happen to be corrupted
|
|
70
|
+
sorted_files = self.all_files
|
|
71
|
+
|
|
72
|
+
count = 0
|
|
73
|
+
for item in sorted_files:
|
|
74
|
+
# item[0] = file_id, item[8] = file_title, item[9] = file_for
|
|
75
|
+
file_id_str = item[0].decode('utf-8') if isinstance(item[0], bytes) else str(item[0])
|
|
76
|
+
title_str = item[8] if (len(item) > 8 and item[8]) else "Untitled"
|
|
77
|
+
for_str = item[9] if (len(item) > 9 and item[9]) else "General"
|
|
78
|
+
|
|
79
|
+
# We store the underlying hard file_id inside the item tags or text value
|
|
80
|
+
# so auto_load_file can still capture it instantly behind the scenes!
|
|
81
|
+
self.lst_files.insert(
|
|
82
|
+
parent='',
|
|
83
|
+
index='end',
|
|
84
|
+
iid=count,
|
|
85
|
+
text=file_id_str, # Secretly store the file_id here
|
|
86
|
+
values=(title_str, for_str)
|
|
87
|
+
)
|
|
88
|
+
count += 1
|
|
89
|
+
|
|
90
|
+
def refresh_list(self):
|
|
91
|
+
session_cookie = verifyCookie()
|
|
92
|
+
self.all_files = retrieveFiles(session_cookie[2])
|
|
93
|
+
for item in self.lst_files.get_children():
|
|
94
|
+
self.lst_files.delete(item)
|
|
95
|
+
self.populate_tree()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import *
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
class LicencesFrame(LabelFrame):
|
|
6
|
+
def __init__(self, master):
|
|
7
|
+
# Explicitly configure LabelFrame style properties to match dark themes
|
|
8
|
+
super().__init__(master, text='Licences')
|
|
9
|
+
|
|
10
|
+
# Create two side-by-side buttons instead of vertical stacking to save vertical space in the sidebar
|
|
11
|
+
self.columnconfigure(0, weight=1)
|
|
12
|
+
self.columnconfigure(1, weight=1)
|
|
13
|
+
|
|
14
|
+
# We pass self.master (the top-level window/root) down to the popups so they position correctly
|
|
15
|
+
Button(
|
|
16
|
+
self,
|
|
17
|
+
text='Licence',
|
|
18
|
+
style="Signup.TButton",
|
|
19
|
+
command=lambda: self.wait_window(LicenceDetails(self.winfo_toplevel()).top),
|
|
20
|
+
cursor='hand2'
|
|
21
|
+
).grid(row=0, column=0, padx=5, pady=8, sticky='ew')
|
|
22
|
+
|
|
23
|
+
Button(
|
|
24
|
+
self,
|
|
25
|
+
text='Copyright',
|
|
26
|
+
style="Signup.TButton",
|
|
27
|
+
command=lambda: self.wait_window(Copyright(self.winfo_toplevel()).top),
|
|
28
|
+
cursor='hand2'
|
|
29
|
+
).grid(row=0, column=1, padx=5, pady=8, sticky='ew')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LicenceDetails(Frame):
|
|
34
|
+
def __init__(self, master=None):
|
|
35
|
+
super().__init__(master)
|
|
36
|
+
|
|
37
|
+
self.top = tk.Toplevel(master, relief='flat')
|
|
38
|
+
self.top.geometry("340x260")
|
|
39
|
+
self.top.resizable(0,0)
|
|
40
|
+
self.top.title('Licence Agreement')
|
|
41
|
+
self.top.attributes('-topmost', True)
|
|
42
|
+
self.top.configure(bg="#1e1e1e")
|
|
43
|
+
|
|
44
|
+
# The icon
|
|
45
|
+
try:
|
|
46
|
+
self.top.wm_iconbitmap("cryp.ico")
|
|
47
|
+
except:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
# Dark mode flat text block configuration
|
|
51
|
+
self.text_fr = tk.Text(
|
|
52
|
+
self.top,
|
|
53
|
+
height=10,
|
|
54
|
+
relief='flat',
|
|
55
|
+
bg='#2d2d2d',
|
|
56
|
+
fg='#ffffff',
|
|
57
|
+
font=('Arial', 10),
|
|
58
|
+
padx=10,
|
|
59
|
+
pady=10,
|
|
60
|
+
wrap='word'
|
|
61
|
+
)
|
|
62
|
+
self.text_fr.pack(fill='both', expand=True, side='top', padx=10, pady=10)
|
|
63
|
+
|
|
64
|
+
licence_text = (
|
|
65
|
+
"This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n\n"
|
|
66
|
+
"This is free software, and you are welcome to redistribute it under certain conditions; "
|
|
67
|
+
"type `show c' for details."
|
|
68
|
+
)
|
|
69
|
+
self.text_fr.insert(1.0, licence_text)
|
|
70
|
+
self.text_fr['state'] = 'disabled'
|
|
71
|
+
|
|
72
|
+
# Styled copyright indicator string label
|
|
73
|
+
lbl = Label(self.top, background="#2d2d2b", text=f"© 2021 - {time.strftime('%Y')} by Mapenzi Mudimba", font=('Arial', 9))
|
|
74
|
+
lbl.pack(side='bottom', pady=(0, 10))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Copyright(Frame):
|
|
78
|
+
def __init__(self, master=None):
|
|
79
|
+
super().__init__(master)
|
|
80
|
+
|
|
81
|
+
self.top = tk.Toplevel(master, relief='flat')
|
|
82
|
+
self.top.geometry("360x280")
|
|
83
|
+
self.top.resizable(0,0)
|
|
84
|
+
self.top.title('Copyright Metadata')
|
|
85
|
+
self.top.attributes('-topmost', True)
|
|
86
|
+
self.top.configure(bg="#1e1e1e")
|
|
87
|
+
|
|
88
|
+
# The icon
|
|
89
|
+
try:
|
|
90
|
+
self.top.wm_iconbitmap("cryp.ico")
|
|
91
|
+
except:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
# Container wrap panel
|
|
95
|
+
self.frame_one = Frame(self.top, relief='flat')
|
|
96
|
+
self.frame_one.pack(fill='both', expand=True, padx=10, pady=10)
|
|
97
|
+
|
|
98
|
+
self.text_f = tk.Text(
|
|
99
|
+
self.frame_one,
|
|
100
|
+
height=10,
|
|
101
|
+
relief='flat',
|
|
102
|
+
bg='#2d2d2d',
|
|
103
|
+
fg='#ffffff',
|
|
104
|
+
font=('Arial', 10),
|
|
105
|
+
padx=10,
|
|
106
|
+
pady=10,
|
|
107
|
+
wrap='word'
|
|
108
|
+
)
|
|
109
|
+
self.text_f.pack(fill='both', expand=True)
|
|
110
|
+
|
|
111
|
+
copyright_text = (
|
|
112
|
+
"This page is licensed under the Python Software Foundation License Version 2.\n\n"
|
|
113
|
+
"Examples, recipes, and other code in the documentation are additionally licensed "
|
|
114
|
+
"under the Zero Clause BSD License.\n\n"
|
|
115
|
+
"All rights reserved."
|
|
116
|
+
)
|
|
117
|
+
self.text_f.insert(1.0, copyright_text)
|
|
118
|
+
self.text_f['state'] = 'disabled'
|
|
119
|
+
|
|
120
|
+
lbl = Label(self.top, text=f"© 2021 - {time.strftime('%Y')} by Mapenzi Mudimba", font=('Arial', 9), background="#2d2d2b", foreground="#a89a76")
|
|
121
|
+
lbl.pack(side='bottom', pady=(0, 10))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
|
|
3
|
+
class TextLineNumbers(tk.Canvas):
|
|
4
|
+
def __init__(self, *args, **kwargs):
|
|
5
|
+
tk.Canvas.__init__(self, *args, **kwargs)
|
|
6
|
+
self.textwidget = None
|
|
7
|
+
|
|
8
|
+
def attach(self, text_widget):
|
|
9
|
+
self.textwidget = text_widget
|
|
10
|
+
|
|
11
|
+
def redraw(self, *args):
|
|
12
|
+
# 🛡️ Enhanced Safety check: Stop if canvas OR the attached text box is missing
|
|
13
|
+
if not self.winfo_exists() or self.textwidget is None or not self.textwidget.winfo_exists():
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
'''redraw line numbers'''
|
|
17
|
+
self.delete("all")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
i = self.textwidget.index("@0,0")
|
|
21
|
+
while True :
|
|
22
|
+
dline = self.textwidget.dlineinfo(i)
|
|
23
|
+
if dline is None:
|
|
24
|
+
break
|
|
25
|
+
y = dline[1]
|
|
26
|
+
linenum = str(i).split(".")[0]
|
|
27
|
+
self.create_text(2, y, anchor="nw", text=linenum, fill='#ff1')
|
|
28
|
+
i = self.textwidget.index("%s+1line" % i)
|
|
29
|
+
except tk.TclError:
|
|
30
|
+
# Catch-all container in case a redraw fires mid-destruction
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Refreshes the canvas widget 30fps
|
|
34
|
+
self.after(30, self.redraw)
|