GameSentenceMiner 2.14.9__py3-none-any.whl → 2.14.10__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.
- GameSentenceMiner/ai/__init__.py +0 -0
- GameSentenceMiner/ai/ai_prompting.py +473 -0
- GameSentenceMiner/ocr/__init__.py +0 -0
- GameSentenceMiner/ocr/gsm_ocr_config.py +174 -0
- GameSentenceMiner/ocr/ocrconfig.py +129 -0
- GameSentenceMiner/ocr/owocr_area_selector.py +629 -0
- GameSentenceMiner/ocr/owocr_helper.py +638 -0
- GameSentenceMiner/ocr/ss_picker.py +140 -0
- GameSentenceMiner/owocr/owocr/__init__.py +1 -0
- GameSentenceMiner/owocr/owocr/__main__.py +9 -0
- GameSentenceMiner/owocr/owocr/config.py +148 -0
- GameSentenceMiner/owocr/owocr/lens_betterproto.py +1238 -0
- GameSentenceMiner/owocr/owocr/ocr.py +1690 -0
- GameSentenceMiner/owocr/owocr/run.py +1818 -0
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +109 -0
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/tools/audio_offset_selector.py +215 -0
- GameSentenceMiner/tools/ss_selector.py +135 -0
- GameSentenceMiner/tools/window_transparency.py +214 -0
- GameSentenceMiner/util/__init__.py +0 -0
- GameSentenceMiner/util/communication/__init__.py +22 -0
- GameSentenceMiner/util/communication/send.py +7 -0
- GameSentenceMiner/util/communication/websocket.py +94 -0
- GameSentenceMiner/util/configuration.py +1199 -0
- GameSentenceMiner/util/db.py +408 -0
- GameSentenceMiner/util/downloader/Untitled_json.py +472 -0
- GameSentenceMiner/util/downloader/__init__.py +0 -0
- GameSentenceMiner/util/downloader/download_tools.py +194 -0
- GameSentenceMiner/util/downloader/oneocr_dl.py +250 -0
- GameSentenceMiner/util/electron_config.py +259 -0
- GameSentenceMiner/util/ffmpeg.py +571 -0
- GameSentenceMiner/util/get_overlay_coords.py +366 -0
- GameSentenceMiner/util/gsm_utils.py +323 -0
- GameSentenceMiner/util/model.py +206 -0
- GameSentenceMiner/util/notification.py +157 -0
- GameSentenceMiner/util/text_log.py +214 -0
- GameSentenceMiner/util/win10toast/__init__.py +154 -0
- GameSentenceMiner/util/win10toast/__main__.py +22 -0
- GameSentenceMiner/web/__init__.py +0 -0
- GameSentenceMiner/web/service.py +132 -0
- GameSentenceMiner/web/static/__init__.py +0 -0
- GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- GameSentenceMiner/web/static/favicon.ico +0 -0
- GameSentenceMiner/web/static/favicon.svg +3 -0
- GameSentenceMiner/web/static/site.webmanifest +21 -0
- GameSentenceMiner/web/static/style.css +292 -0
- GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- GameSentenceMiner/web/templates/__init__.py +0 -0
- GameSentenceMiner/web/templates/index.html +50 -0
- GameSentenceMiner/web/templates/text_replacements.html +238 -0
- GameSentenceMiner/web/templates/utility.html +483 -0
- GameSentenceMiner/web/texthooking_page.py +584 -0
- GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/METADATA +1 -1
- gamesentenceminer-2.14.10.dist-info/RECORD +79 -0
- gamesentenceminer-2.14.9.dist-info/RECORD +0 -24
- {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
from __future__ import absolute_import
|
2
|
+
from __future__ import print_function
|
3
|
+
from __future__ import unicode_literals
|
4
|
+
|
5
|
+
__all__ = ['ToastNotifier']
|
6
|
+
|
7
|
+
# #############################################################################
|
8
|
+
# ########## Libraries #############
|
9
|
+
# ##################################
|
10
|
+
# standard library
|
11
|
+
import logging
|
12
|
+
import threading
|
13
|
+
from os import path
|
14
|
+
from time import sleep
|
15
|
+
|
16
|
+
# 3rd party modules
|
17
|
+
from win32api import GetModuleHandle
|
18
|
+
from win32api import PostQuitMessage
|
19
|
+
from win32con import CW_USEDEFAULT
|
20
|
+
from win32con import IDI_APPLICATION
|
21
|
+
from win32con import IMAGE_ICON
|
22
|
+
from win32con import LR_DEFAULTSIZE
|
23
|
+
from win32con import LR_LOADFROMFILE
|
24
|
+
from win32con import WM_DESTROY
|
25
|
+
from win32con import WM_USER
|
26
|
+
from win32con import WS_OVERLAPPED
|
27
|
+
from win32con import WS_SYSMENU
|
28
|
+
from win32gui import CreateWindow
|
29
|
+
from win32gui import DestroyWindow
|
30
|
+
from win32gui import LoadIcon
|
31
|
+
from win32gui import LoadImage
|
32
|
+
from win32gui import NIF_ICON
|
33
|
+
from win32gui import NIF_INFO
|
34
|
+
from win32gui import NIF_MESSAGE
|
35
|
+
from win32gui import NIF_TIP
|
36
|
+
from win32gui import NIM_ADD
|
37
|
+
from win32gui import NIM_DELETE
|
38
|
+
from win32gui import NIM_MODIFY
|
39
|
+
from win32gui import RegisterClass
|
40
|
+
from win32gui import UnregisterClass
|
41
|
+
from win32gui import Shell_NotifyIcon
|
42
|
+
from win32gui import UpdateWindow
|
43
|
+
from win32gui import WNDCLASS
|
44
|
+
|
45
|
+
# ############################################################################
|
46
|
+
# ########### Classes ##############
|
47
|
+
# ##################################
|
48
|
+
|
49
|
+
|
50
|
+
class ToastNotifier(object):
|
51
|
+
"""Create a Windows 10 toast notification.
|
52
|
+
|
53
|
+
from: https://github.com/jithurjacob/Windows-10-Toast-Notifications
|
54
|
+
"""
|
55
|
+
|
56
|
+
def __init__(self):
|
57
|
+
"""Initialize."""
|
58
|
+
self._thread = None
|
59
|
+
|
60
|
+
def _show_toast(self, title, msg,
|
61
|
+
icon_path, duration):
|
62
|
+
"""Notification settings.
|
63
|
+
|
64
|
+
:title: notification title
|
65
|
+
:msg: notification message
|
66
|
+
:icon_path: path to the .ico file to custom notification
|
67
|
+
:duration: delay in seconds before notification self-destruction
|
68
|
+
"""
|
69
|
+
message_map = {WM_DESTROY: self.on_destroy, }
|
70
|
+
|
71
|
+
# Register the window class.
|
72
|
+
self.wc = WNDCLASS()
|
73
|
+
self.hinst = self.wc.hInstance = GetModuleHandle(None)
|
74
|
+
self.wc.lpszClassName = str("PythonTaskbar") # must be a string
|
75
|
+
self.wc.lpfnWndProc = message_map # could also specify a wndproc.
|
76
|
+
try:
|
77
|
+
self.classAtom = RegisterClass(self.wc)
|
78
|
+
except:
|
79
|
+
pass #not sure of this
|
80
|
+
style = WS_OVERLAPPED | WS_SYSMENU
|
81
|
+
self.hwnd = CreateWindow(self.classAtom, "Taskbar", style,
|
82
|
+
0, 0, CW_USEDEFAULT,
|
83
|
+
CW_USEDEFAULT,
|
84
|
+
0, 0, self.hinst, None)
|
85
|
+
UpdateWindow(self.hwnd)
|
86
|
+
|
87
|
+
# icon
|
88
|
+
if icon_path is not None:
|
89
|
+
icon_path = path.realpath(icon_path)
|
90
|
+
else:
|
91
|
+
icon_path = path.join(path.dirname(path.abspath(__file__)), 'data', 'python.ico')
|
92
|
+
icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
|
93
|
+
try:
|
94
|
+
hicon = LoadImage(self.hinst, icon_path,
|
95
|
+
IMAGE_ICON, 0, 0, icon_flags)
|
96
|
+
except Exception as e:
|
97
|
+
logging.error("Some trouble with the icon ({}): {}"
|
98
|
+
.format(icon_path, e))
|
99
|
+
hicon = LoadIcon(0, IDI_APPLICATION)
|
100
|
+
|
101
|
+
# Taskbar icon
|
102
|
+
flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
|
103
|
+
nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "Tooltip")
|
104
|
+
Shell_NotifyIcon(NIM_ADD, nid)
|
105
|
+
Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO,
|
106
|
+
WM_USER + 20,
|
107
|
+
hicon, "Balloon Tooltip", msg, 200,
|
108
|
+
title))
|
109
|
+
# take a rest then destroy
|
110
|
+
sleep(duration)
|
111
|
+
DestroyWindow(self.hwnd)
|
112
|
+
UnregisterClass(self.wc.lpszClassName, None)
|
113
|
+
return None
|
114
|
+
|
115
|
+
def show_toast(self, title="Notification", msg="Here comes the message",
|
116
|
+
icon_path=None, duration=5, threaded=False):
|
117
|
+
"""Notification settings.
|
118
|
+
|
119
|
+
:title: notification title
|
120
|
+
:msg: notification message
|
121
|
+
:icon_path: path to the .ico file to custom notification
|
122
|
+
:duration: delay in seconds before notification self-destruction
|
123
|
+
"""
|
124
|
+
if not threaded:
|
125
|
+
self._show_toast(title, msg, icon_path, duration)
|
126
|
+
else:
|
127
|
+
if self.notification_active():
|
128
|
+
# We have an active notification, let is finish so we don't spam them
|
129
|
+
return False
|
130
|
+
|
131
|
+
self._thread = threading.Thread(target=self._show_toast, args=(title, msg, icon_path, duration))
|
132
|
+
self._thread.start()
|
133
|
+
return True
|
134
|
+
|
135
|
+
def notification_active(self):
|
136
|
+
"""See if we have an active notification showing"""
|
137
|
+
if self._thread != None and self._thread.is_alive():
|
138
|
+
# We have an active notification, let is finish we don't spam them
|
139
|
+
return True
|
140
|
+
return False
|
141
|
+
|
142
|
+
def on_destroy(self, hwnd, msg, wparam, lparam):
|
143
|
+
"""Clean after notification ended.
|
144
|
+
|
145
|
+
:hwnd:
|
146
|
+
:msg:
|
147
|
+
:wparam:
|
148
|
+
:lparam:
|
149
|
+
"""
|
150
|
+
nid = (self.hwnd, 0)
|
151
|
+
Shell_NotifyIcon(NIM_DELETE, nid)
|
152
|
+
PostQuitMessage(0)
|
153
|
+
|
154
|
+
return 0
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from win10toast import ToastNotifier
|
2
|
+
import time
|
3
|
+
|
4
|
+
# #############################################################################
|
5
|
+
# ###### Stand alone program ########
|
6
|
+
# ###################################
|
7
|
+
if __name__ == "__main__":
|
8
|
+
# Example
|
9
|
+
toaster = ToastNotifier()
|
10
|
+
toaster.show_toast(
|
11
|
+
"Hello World!!!",
|
12
|
+
"Python is 10 seconds awsm!",
|
13
|
+
duration=10)
|
14
|
+
toaster.show_toast(
|
15
|
+
"Example two",
|
16
|
+
"This notification is in it's own thread!",
|
17
|
+
icon_path=None,
|
18
|
+
duration=5,
|
19
|
+
threaded=True
|
20
|
+
)
|
21
|
+
# Wait for threaded notification to finish
|
22
|
+
while toaster.notification_active(): time.sleep(0.1)
|
File without changes
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import subprocess
|
4
|
+
import threading
|
5
|
+
|
6
|
+
|
7
|
+
from GameSentenceMiner import anki
|
8
|
+
from GameSentenceMiner.util import ffmpeg, notification
|
9
|
+
from GameSentenceMiner.util.configuration import gsm_state, logger, get_config, get_temporary_directory
|
10
|
+
from GameSentenceMiner.util.ffmpeg import get_video_timings
|
11
|
+
from GameSentenceMiner.util.text_log import GameLine
|
12
|
+
|
13
|
+
|
14
|
+
def set_get_audio_from_video_callback(func):
|
15
|
+
global get_audio_from_video
|
16
|
+
get_audio_from_video = func
|
17
|
+
|
18
|
+
|
19
|
+
def handle_texthooker_button(video_path=''):
|
20
|
+
try:
|
21
|
+
if gsm_state.line_for_audio:
|
22
|
+
line: GameLine = gsm_state.line_for_audio
|
23
|
+
gsm_state.line_for_audio = None
|
24
|
+
if line == gsm_state.previous_line_for_audio:
|
25
|
+
logger.info("Line is the same as the last one, skipping processing.")
|
26
|
+
if get_config().advanced.audio_player_path:
|
27
|
+
play_audio_in_external(gsm_state.previous_audio)
|
28
|
+
elif get_config().advanced.video_player_path:
|
29
|
+
play_video_in_external(line, video_path)
|
30
|
+
else:
|
31
|
+
import sounddevice as sd
|
32
|
+
data, samplerate = gsm_state.previous_audio
|
33
|
+
sd.play(data, samplerate)
|
34
|
+
sd.wait()
|
35
|
+
return
|
36
|
+
gsm_state.previous_line_for_audio = line
|
37
|
+
if get_config().advanced.audio_player_path:
|
38
|
+
audio = get_audio_from_video(line, line.next.time if line.next else None, video_path,
|
39
|
+
temporary=True)
|
40
|
+
play_audio_in_external(audio)
|
41
|
+
gsm_state.previous_audio = audio
|
42
|
+
elif get_config().advanced.video_player_path:
|
43
|
+
play_video_in_external(line, video_path)
|
44
|
+
else:
|
45
|
+
import sounddevice as sd
|
46
|
+
import soundfile as sf
|
47
|
+
audio = get_audio_from_video(line, line.next.time if line.next else None, video_path,
|
48
|
+
temporary=True)
|
49
|
+
data, samplerate = sf.read(audio)
|
50
|
+
sd.play(data, samplerate)
|
51
|
+
sd.wait()
|
52
|
+
gsm_state.previous_audio = (data, samplerate)
|
53
|
+
return
|
54
|
+
if gsm_state.line_for_screenshot:
|
55
|
+
line: GameLine = gsm_state.line_for_screenshot
|
56
|
+
gsm_state.line_for_screenshot = None
|
57
|
+
gsm_state.previous_line_for_screenshot = line
|
58
|
+
screenshot = ffmpeg.get_screenshot_for_line(video_path, line, True)
|
59
|
+
if gsm_state.anki_note_for_screenshot:
|
60
|
+
gsm_state.anki_note_for_screenshot = None
|
61
|
+
encoded_image = ffmpeg.process_image(screenshot)
|
62
|
+
if get_config().anki.update_anki and get_config().screenshot.screenshot_hotkey_updates_anki:
|
63
|
+
last_note = anki.get_last_anki_card()
|
64
|
+
if last_note:
|
65
|
+
anki.add_image_to_card(last_note, encoded_image)
|
66
|
+
notification.send_screenshot_updated(last_note.get_field(get_config().anki.word_field))
|
67
|
+
if get_config().features.open_anki_edit:
|
68
|
+
notification.open_anki_card(last_note.noteId)
|
69
|
+
else:
|
70
|
+
notification.send_screenshot_saved(encoded_image)
|
71
|
+
else:
|
72
|
+
notification.send_screenshot_saved(encoded_image)
|
73
|
+
else:
|
74
|
+
os.startfile(screenshot)
|
75
|
+
return
|
76
|
+
except Exception as e:
|
77
|
+
logger.error(f"Error Playing Audio/Video: {e}")
|
78
|
+
logger.debug(f"Error Playing Audio/Video: {e}", exc_info=True)
|
79
|
+
return
|
80
|
+
finally:
|
81
|
+
gsm_state.previous_replay = video_path
|
82
|
+
gsm_state.videos_to_remove.add(video_path)
|
83
|
+
|
84
|
+
|
85
|
+
def play_audio_in_external(filepath):
|
86
|
+
exe = get_config().advanced.audio_player_path
|
87
|
+
|
88
|
+
filepath = os.path.normpath(filepath)
|
89
|
+
|
90
|
+
command = [exe, "--no-video", filepath]
|
91
|
+
|
92
|
+
try:
|
93
|
+
subprocess.Popen(command)
|
94
|
+
print(f"Opened {filepath} in {exe}.")
|
95
|
+
except Exception as e:
|
96
|
+
print(f"An error occurred: {e}")
|
97
|
+
|
98
|
+
|
99
|
+
def play_video_in_external(line, filepath):
|
100
|
+
command = [get_config().advanced.video_player_path]
|
101
|
+
|
102
|
+
start, _, _, _ = get_video_timings(filepath, line)
|
103
|
+
|
104
|
+
if start:
|
105
|
+
if "vlc" in get_config().advanced.video_player_path:
|
106
|
+
command.extend(["--start-time", convert_to_vlc_seconds(start), '--one-instance'])
|
107
|
+
else:
|
108
|
+
command.extend(["--start", convert_to_vlc_seconds(start)])
|
109
|
+
command.append(os.path.normpath(filepath))
|
110
|
+
|
111
|
+
logger.info(" ".join(command))
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
try:
|
116
|
+
subprocess.Popen(command)
|
117
|
+
logger.info(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
|
118
|
+
except FileNotFoundError:
|
119
|
+
logger.error("VLC not found. Make sure it's installed and in your PATH.")
|
120
|
+
except Exception as e:
|
121
|
+
logger.error(f"An error occurred: {e}")
|
122
|
+
|
123
|
+
|
124
|
+
def convert_to_vlc_seconds(time_str):
|
125
|
+
"""Converts HH:MM:SS.milliseconds to VLC-compatible seconds."""
|
126
|
+
try:
|
127
|
+
hours, minutes, seconds_ms = time_str.split(":")
|
128
|
+
seconds, milliseconds = seconds_ms.split(".")
|
129
|
+
total_seconds = (int(hours) * 3600) + (int(minutes) * 60) + int(seconds) + (int(milliseconds) / 1000.0)
|
130
|
+
return str(total_seconds)
|
131
|
+
except ValueError:
|
132
|
+
return "Invalid time format"
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="67" height="64" viewBox="0 0 67 64"><image width="67" height="64" xlink:href="data:image/png;base64,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"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
|
2
|
+
@media (prefers-color-scheme: dark) { :root { filter: none; } }
|
3
|
+
</style></svg>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"name": "MyWebSite",
|
3
|
+
"short_name": "MySite",
|
4
|
+
"icons": [
|
5
|
+
{
|
6
|
+
"src": "/web-app-manifest-192x192.png",
|
7
|
+
"sizes": "192x192",
|
8
|
+
"type": "image/png",
|
9
|
+
"purpose": "maskable"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"src": "/web-app-manifest-512x512.png",
|
13
|
+
"sizes": "512x512",
|
14
|
+
"type": "image/png",
|
15
|
+
"purpose": "maskable"
|
16
|
+
}
|
17
|
+
],
|
18
|
+
"theme_color": "#ffffff",
|
19
|
+
"background_color": "#ffffff",
|
20
|
+
"display": "standalone"
|
21
|
+
}
|
@@ -0,0 +1,292 @@
|
|
1
|
+
body {
|
2
|
+
background-color: #121212;
|
3
|
+
color: #e0e0e0;
|
4
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
5
|
+
margin: 20px; /* Keep existing margin */
|
6
|
+
}
|
7
|
+
|
8
|
+
h1 {
|
9
|
+
color: #ffffff;
|
10
|
+
text-align: center;
|
11
|
+
font-weight: 300;
|
12
|
+
margin-bottom: 24px; /* Added margin to match previous layout */
|
13
|
+
}
|
14
|
+
|
15
|
+
/* Styles for the main container */
|
16
|
+
.container {
|
17
|
+
max-width: 64rem; /* Equivalent to max-w-4xl */
|
18
|
+
margin-left: auto;
|
19
|
+
margin-right: auto;
|
20
|
+
background-color: #1e1e1e; /* Darker background for the container */
|
21
|
+
padding: 32px; /* Equivalent to p-8 */
|
22
|
+
border-radius: 8px; /* rounded-lg */
|
23
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* shadow-md */
|
24
|
+
}
|
25
|
+
|
26
|
+
|
27
|
+
.inputField {
|
28
|
+
background-color: #1e1e1e;
|
29
|
+
color: #e0e0e0;
|
30
|
+
border: 1px solid #333;
|
31
|
+
padding: 10px;
|
32
|
+
font-size: 16px;
|
33
|
+
/* margin-bottom: 15px; Removed as it's part of flex/gap now */
|
34
|
+
border-radius: 5px;
|
35
|
+
flex-grow: 1; /* Added to make it fill space in flex container */
|
36
|
+
}
|
37
|
+
/* Placeholder color for the search input */
|
38
|
+
.inputField::placeholder {
|
39
|
+
color: #a0a0a0; /* Slightly lighter placeholder */
|
40
|
+
}
|
41
|
+
.inputField:focus {
|
42
|
+
border-color: #1a73e8; /* Highlight color on focus */
|
43
|
+
outline: none;
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
.control {
|
48
|
+
margin-bottom: 24px; /* Adjusted margin to match previous layout */
|
49
|
+
display: flex;
|
50
|
+
flex-direction: column; /* Default to column for small screens */
|
51
|
+
gap: 16px; /* Space between items */
|
52
|
+
}
|
53
|
+
|
54
|
+
@media (min-width: 640px) { /* sm breakpoint equivalent */
|
55
|
+
.control {
|
56
|
+
flex-direction: row; /* Row layout for larger screens */
|
57
|
+
gap: 16px; /* Space between items */
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
button {
|
63
|
+
background-color: #1a73e8;
|
64
|
+
color: #ffffff;
|
65
|
+
border: none;
|
66
|
+
padding: 10px 20px;
|
67
|
+
font-size: 16px;
|
68
|
+
cursor: pointer;
|
69
|
+
transition: background-color 0.3s;
|
70
|
+
border-radius: 5px;
|
71
|
+
}
|
72
|
+
|
73
|
+
button:disabled {
|
74
|
+
background-color: #444;
|
75
|
+
cursor: not-allowed;
|
76
|
+
}
|
77
|
+
|
78
|
+
button:hover:not(:disabled) {
|
79
|
+
background-color: #1669c1;
|
80
|
+
}
|
81
|
+
|
82
|
+
/* Specific button styles based on previous Tailwind colors */
|
83
|
+
.button-blue {
|
84
|
+
background-color: #1a73e8; /* Match existing button style */
|
85
|
+
}
|
86
|
+
.button-blue:hover:not(:disabled) {
|
87
|
+
background-color: #1669c1; /* Match existing button hover */
|
88
|
+
}
|
89
|
+
|
90
|
+
.button-green {
|
91
|
+
background-color: #34a853; /* Google Green */
|
92
|
+
}
|
93
|
+
.button-green:hover:not(:disabled) {
|
94
|
+
background-color: #2e8b4a;
|
95
|
+
}
|
96
|
+
|
97
|
+
.button-gray {
|
98
|
+
background-color: #5f6368; /* Google Gray */
|
99
|
+
}
|
100
|
+
.button-gray:hover:not(:disabled) {
|
101
|
+
background-color: #54575c;
|
102
|
+
}
|
103
|
+
|
104
|
+
|
105
|
+
select {
|
106
|
+
background-color: #1e1e1e;
|
107
|
+
color: #e0e0e0;
|
108
|
+
border: 1px solid #333;
|
109
|
+
padding: 10px;
|
110
|
+
width: 220px;
|
111
|
+
font-size: 16px;
|
112
|
+
margin-left: 5px;
|
113
|
+
margin-right: 5px;
|
114
|
+
border-radius: 5px;
|
115
|
+
}
|
116
|
+
|
117
|
+
/* Removed generic div margin to avoid conflicts */
|
118
|
+
/* div {
|
119
|
+
margin-bottom: 15px;
|
120
|
+
} */
|
121
|
+
|
122
|
+
/* Table Styles */
|
123
|
+
.table-container {
|
124
|
+
overflow-x: auto;
|
125
|
+
border-radius: 8px;
|
126
|
+
border: 1px solid #333;
|
127
|
+
}
|
128
|
+
|
129
|
+
.data-table {
|
130
|
+
width: 100%; /* Use width instead of min-width */
|
131
|
+
border-collapse: collapse;
|
132
|
+
}
|
133
|
+
|
134
|
+
.data-table thead {
|
135
|
+
background-color: #333;
|
136
|
+
}
|
137
|
+
|
138
|
+
.data-table th {
|
139
|
+
padding: 12px 24px;
|
140
|
+
text-align: left;
|
141
|
+
font-size: 0.75rem;
|
142
|
+
font-weight: 500;
|
143
|
+
color: #b0b0b0; /* Lighter gray for header text */
|
144
|
+
text-transform: uppercase;
|
145
|
+
letter-spacing: 0.05em;
|
146
|
+
}
|
147
|
+
.data-table th:last-child {
|
148
|
+
text-align: right;
|
149
|
+
}
|
150
|
+
|
151
|
+
.data-table tbody tr {
|
152
|
+
background-color: #1e1e1e; /* Match input field background */
|
153
|
+
border-bottom: 1px solid #333; /* Border between rows */
|
154
|
+
}
|
155
|
+
|
156
|
+
.data-table tbody tr:last-child {
|
157
|
+
border-bottom: none; /* No border on the last row */
|
158
|
+
}
|
159
|
+
|
160
|
+
|
161
|
+
.data-table td {
|
162
|
+
padding: 16px 24px;
|
163
|
+
white-space: nowrap;
|
164
|
+
font-size: 0.875rem;
|
165
|
+
color: #e0e0e0; /* Default text color for cells */
|
166
|
+
}
|
167
|
+
|
168
|
+
.data-table td:nth-child(1) {
|
169
|
+
font-weight: 500;
|
170
|
+
color: #ffffff; /* White color for the key */
|
171
|
+
}
|
172
|
+
|
173
|
+
.data-table td:last-child {
|
174
|
+
text-align: right;
|
175
|
+
font-weight: 500;
|
176
|
+
display: flex;
|
177
|
+
justify-content: flex-end;
|
178
|
+
gap: 8px;
|
179
|
+
}
|
180
|
+
|
181
|
+
/* Action buttons in the table */
|
182
|
+
/* Styles for Edit Button */
|
183
|
+
.action-button.edit-btn {
|
184
|
+
color: #a0c3ff; /* Lighter blue for edit */
|
185
|
+
cursor: pointer;
|
186
|
+
background: none; /* Ensure no background */
|
187
|
+
border: none; /* Ensure no border */
|
188
|
+
padding: 0; /* Remove padding */
|
189
|
+
font-size: 0.875rem;
|
190
|
+
text-decoration: underline; /* Add underline for link-like appearance */
|
191
|
+
transition: color 0.3s ease; /* Smooth transition for color */
|
192
|
+
}
|
193
|
+
.action-button.edit-btn:hover {
|
194
|
+
color: #7ba7ff; /* Darker blue on hover */
|
195
|
+
text-decoration: none; /* Remove underline on hover */
|
196
|
+
}
|
197
|
+
|
198
|
+
/* Styles for Delete Button */
|
199
|
+
.action-button.delete-btn {
|
200
|
+
color: #ff6b6b; /* Red for delete */
|
201
|
+
cursor: pointer;
|
202
|
+
background: none; /* Ensure no background */
|
203
|
+
border: none; /* Ensure no border */
|
204
|
+
padding: 0; /* Remove padding */
|
205
|
+
font-size: 0.875rem;
|
206
|
+
text-decoration: underline; /* Add underline for link-like appearance */
|
207
|
+
transition: color 0.3s ease; /* Smooth transition for color */
|
208
|
+
}
|
209
|
+
.action-button.delete-btn:hover {
|
210
|
+
color: #ff4c4c; /* Darker red on hover */
|
211
|
+
text-decoration: none; /* Remove underline on hover */
|
212
|
+
}
|
213
|
+
|
214
|
+
|
215
|
+
.no-entries-message {
|
216
|
+
color: #b0b0b0; /* Match header text color */
|
217
|
+
padding: 16px;
|
218
|
+
text-align: center;
|
219
|
+
}
|
220
|
+
|
221
|
+
/* Modal styles */
|
222
|
+
.modal {
|
223
|
+
display: none; /* Hidden by default */
|
224
|
+
position: fixed; /* Stay in place */
|
225
|
+
z-index: 1000; /* Sit on top */
|
226
|
+
left: 0;
|
227
|
+
top: 0;
|
228
|
+
width: 100%; /* Full width */
|
229
|
+
height: 100%; /* Full height */
|
230
|
+
overflow: auto; /* Enable scroll if needed */
|
231
|
+
background-color: rgba(0,0,0,0.6); /* Darker overlay */
|
232
|
+
align-items: center;
|
233
|
+
justify-content: center;
|
234
|
+
}
|
235
|
+
.modal-content {
|
236
|
+
background-color: #1e1e1e; /* Match input field background */
|
237
|
+
margin: auto;
|
238
|
+
padding: 20px;
|
239
|
+
border-radius: 8px;
|
240
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Darker shadow */
|
241
|
+
width: 90%;
|
242
|
+
max-width: 500px;
|
243
|
+
color: #e0e0e0;
|
244
|
+
}
|
245
|
+
.close-button {
|
246
|
+
color: #b0b0b0; /* Match header text color */
|
247
|
+
float: right;
|
248
|
+
font-size: 28px;
|
249
|
+
font-weight: bold;
|
250
|
+
}
|
251
|
+
.close-button:hover,
|
252
|
+
.close-button:focus {
|
253
|
+
color: #ffffff; /* White on hover */
|
254
|
+
text-decoration: none;
|
255
|
+
cursor: pointer;
|
256
|
+
}
|
257
|
+
|
258
|
+
.form-group {
|
259
|
+
margin-bottom: 15px; /* Consistent margin */
|
260
|
+
}
|
261
|
+
|
262
|
+
.form-label {
|
263
|
+
display: block;
|
264
|
+
font-size: 0.875rem;
|
265
|
+
font-weight: 500;
|
266
|
+
color: #b0b0b0; /* Match header text color */
|
267
|
+
margin-bottom: 5px; /* Spacing below label */
|
268
|
+
}
|
269
|
+
|
270
|
+
.form-input {
|
271
|
+
display: block;
|
272
|
+
width: 95%;
|
273
|
+
padding: 10px 12px; /* Slightly more padding */
|
274
|
+
border: 1px solid #333;
|
275
|
+
border-radius: 5px; /* Match other inputs */
|
276
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
277
|
+
outline: none;
|
278
|
+
background-color: #121212; /* Darker background for input */
|
279
|
+
color: #e0e0e0;
|
280
|
+
}
|
281
|
+
.form-input::placeholder {
|
282
|
+
color: #a0a0a0;
|
283
|
+
}
|
284
|
+
.form-input:focus {
|
285
|
+
border-color: #1a73e8;
|
286
|
+
box-shadow: 0 0 0 1px #1a73e8;
|
287
|
+
}
|
288
|
+
|
289
|
+
.flex-end {
|
290
|
+
display: flex;
|
291
|
+
justify-content: flex-end;
|
292
|
+
}
|
Binary file
|
Binary file
|
File without changes
|