sqlshell 0.1.8__py3-none-any.whl → 0.1.9__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.
Potentially problematic release.
This version of sqlshell might be problematic. Click here for more details.
- sqlshell/__init__.py +1 -1
- sqlshell/db/__init__.py +5 -0
- sqlshell/db/database_manager.py +691 -0
- sqlshell/editor.py +546 -45
- sqlshell/main.py +1472 -889
- sqlshell/query_tab.py +172 -0
- sqlshell/resources/create_icon.py +106 -28
- sqlshell/resources/create_splash.py +41 -11
- sqlshell/resources/icon.png +0 -0
- sqlshell/resources/logo_large.png +0 -0
- sqlshell/resources/logo_medium.png +0 -0
- sqlshell/resources/logo_small.png +0 -0
- sqlshell/setup.py +1 -1
- sqlshell/splash_screen.py +276 -48
- sqlshell/ui/__init__.py +6 -0
- sqlshell/ui/bar_chart_delegate.py +49 -0
- sqlshell/ui/filter_header.py +403 -0
- {sqlshell-0.1.8.dist-info → sqlshell-0.1.9.dist-info}/METADATA +8 -6
- sqlshell-0.1.9.dist-info/RECORD +31 -0
- {sqlshell-0.1.8.dist-info → sqlshell-0.1.9.dist-info}/WHEEL +1 -1
- sqlshell-0.1.8.dist-info/RECORD +0 -21
- {sqlshell-0.1.8.dist-info → sqlshell-0.1.9.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.1.8.dist-info → sqlshell-0.1.9.dist-info}/top_level.txt +0 -0
sqlshell/query_tab.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
2
|
+
QPushButton, QFrame, QHeaderView, QTableWidget, QSplitter, QApplication)
|
|
3
|
+
from PyQt6.QtCore import Qt
|
|
4
|
+
from PyQt6.QtGui import QIcon
|
|
5
|
+
|
|
6
|
+
from sqlshell.editor import SQLEditor
|
|
7
|
+
from sqlshell.syntax_highlighter import SQLSyntaxHighlighter
|
|
8
|
+
from sqlshell.ui import FilterHeader
|
|
9
|
+
|
|
10
|
+
class QueryTab(QWidget):
|
|
11
|
+
def __init__(self, parent, results_title="RESULTS"):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.parent = parent
|
|
14
|
+
self.current_df = None
|
|
15
|
+
self.filter_widgets = []
|
|
16
|
+
self.results_title_text = results_title
|
|
17
|
+
self.init_ui()
|
|
18
|
+
|
|
19
|
+
def init_ui(self):
|
|
20
|
+
"""Initialize the tab's UI components"""
|
|
21
|
+
# Set main layout
|
|
22
|
+
main_layout = QVBoxLayout(self)
|
|
23
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
24
|
+
main_layout.setSpacing(0)
|
|
25
|
+
|
|
26
|
+
# Create splitter for query and results
|
|
27
|
+
self.splitter = QSplitter(Qt.Orientation.Vertical)
|
|
28
|
+
self.splitter.setHandleWidth(8)
|
|
29
|
+
self.splitter.setChildrenCollapsible(False)
|
|
30
|
+
|
|
31
|
+
# Top part - Query section
|
|
32
|
+
query_widget = QFrame()
|
|
33
|
+
query_widget.setObjectName("content_panel")
|
|
34
|
+
query_layout = QVBoxLayout(query_widget)
|
|
35
|
+
query_layout.setContentsMargins(16, 16, 16, 16)
|
|
36
|
+
query_layout.setSpacing(12)
|
|
37
|
+
|
|
38
|
+
# Query input
|
|
39
|
+
self.query_edit = SQLEditor()
|
|
40
|
+
# Apply syntax highlighting to the query editor
|
|
41
|
+
self.sql_highlighter = SQLSyntaxHighlighter(self.query_edit.document())
|
|
42
|
+
|
|
43
|
+
# Ensure a default completer is available
|
|
44
|
+
if not self.query_edit.completer:
|
|
45
|
+
from PyQt6.QtCore import QStringListModel
|
|
46
|
+
from PyQt6.QtWidgets import QCompleter
|
|
47
|
+
|
|
48
|
+
# Create a basic completer with SQL keywords if one doesn't exist
|
|
49
|
+
if hasattr(self.query_edit, 'all_sql_keywords'):
|
|
50
|
+
model = QStringListModel(self.query_edit.all_sql_keywords)
|
|
51
|
+
completer = QCompleter()
|
|
52
|
+
completer.setModel(model)
|
|
53
|
+
self.query_edit.set_completer(completer)
|
|
54
|
+
|
|
55
|
+
query_layout.addWidget(self.query_edit)
|
|
56
|
+
|
|
57
|
+
# Button row
|
|
58
|
+
button_layout = QHBoxLayout()
|
|
59
|
+
button_layout.setSpacing(8)
|
|
60
|
+
|
|
61
|
+
self.execute_btn = QPushButton('Execute Query')
|
|
62
|
+
self.execute_btn.setObjectName("primary_button")
|
|
63
|
+
self.execute_btn.setIcon(QIcon.fromTheme("media-playback-start"))
|
|
64
|
+
self.execute_btn.clicked.connect(self.execute_query)
|
|
65
|
+
|
|
66
|
+
self.clear_btn = QPushButton('Clear')
|
|
67
|
+
self.clear_btn.clicked.connect(self.clear_query)
|
|
68
|
+
|
|
69
|
+
button_layout.addWidget(self.execute_btn)
|
|
70
|
+
button_layout.addWidget(self.clear_btn)
|
|
71
|
+
button_layout.addStretch()
|
|
72
|
+
|
|
73
|
+
self.export_excel_btn = QPushButton('Export to Excel')
|
|
74
|
+
self.export_excel_btn.setIcon(QIcon.fromTheme("x-office-spreadsheet"))
|
|
75
|
+
self.export_excel_btn.clicked.connect(self.export_to_excel)
|
|
76
|
+
|
|
77
|
+
self.export_parquet_btn = QPushButton('Export to Parquet')
|
|
78
|
+
self.export_parquet_btn.setIcon(QIcon.fromTheme("application-octet-stream"))
|
|
79
|
+
self.export_parquet_btn.clicked.connect(self.export_to_parquet)
|
|
80
|
+
|
|
81
|
+
button_layout.addWidget(self.export_excel_btn)
|
|
82
|
+
button_layout.addWidget(self.export_parquet_btn)
|
|
83
|
+
|
|
84
|
+
query_layout.addLayout(button_layout)
|
|
85
|
+
|
|
86
|
+
# Bottom part - Results section
|
|
87
|
+
results_widget = QWidget()
|
|
88
|
+
results_layout = QVBoxLayout(results_widget)
|
|
89
|
+
results_layout.setContentsMargins(16, 16, 16, 16)
|
|
90
|
+
results_layout.setSpacing(12)
|
|
91
|
+
|
|
92
|
+
# Results header with row count
|
|
93
|
+
header_layout = QHBoxLayout()
|
|
94
|
+
self.results_title = QLabel(self.results_title_text)
|
|
95
|
+
self.results_title.setObjectName("header_label")
|
|
96
|
+
header_layout.addWidget(self.results_title)
|
|
97
|
+
|
|
98
|
+
header_layout.addStretch()
|
|
99
|
+
|
|
100
|
+
self.row_count_label = QLabel("")
|
|
101
|
+
self.row_count_label.setStyleSheet("color: #7F8C8D; font-style: italic;")
|
|
102
|
+
header_layout.addWidget(self.row_count_label)
|
|
103
|
+
|
|
104
|
+
results_layout.addLayout(header_layout)
|
|
105
|
+
|
|
106
|
+
# Results table with customized header
|
|
107
|
+
self.results_table = QTableWidget()
|
|
108
|
+
self.results_table.setAlternatingRowColors(True)
|
|
109
|
+
|
|
110
|
+
# Use custom FilterHeader for filtering
|
|
111
|
+
header = FilterHeader(self.results_table)
|
|
112
|
+
header.set_main_window(self.parent) # Set reference to main window
|
|
113
|
+
self.results_table.setHorizontalHeader(header)
|
|
114
|
+
|
|
115
|
+
# Set table properties for better performance with large datasets
|
|
116
|
+
self.results_table.setShowGrid(True)
|
|
117
|
+
self.results_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
|
118
|
+
self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
|
|
119
|
+
self.results_table.horizontalHeader().setStretchLastSection(True)
|
|
120
|
+
self.results_table.verticalHeader().setVisible(True)
|
|
121
|
+
|
|
122
|
+
results_layout.addWidget(self.results_table)
|
|
123
|
+
|
|
124
|
+
# Add widgets to splitter
|
|
125
|
+
self.splitter.addWidget(query_widget)
|
|
126
|
+
self.splitter.addWidget(results_widget)
|
|
127
|
+
|
|
128
|
+
# Set initial sizes - default 40% query, 60% results
|
|
129
|
+
# This will be better for most uses of the app
|
|
130
|
+
screen = QApplication.primaryScreen()
|
|
131
|
+
if screen:
|
|
132
|
+
# Get available screen height
|
|
133
|
+
available_height = screen.availableGeometry().height()
|
|
134
|
+
# Calculate reasonable query pane size (25-35% depending on screen size)
|
|
135
|
+
if available_height >= 1080: # Large screens
|
|
136
|
+
query_height = int(available_height * 0.3) # 30% for query area
|
|
137
|
+
self.splitter.setSizes([query_height, available_height - query_height])
|
|
138
|
+
else: # Smaller screens
|
|
139
|
+
self.splitter.setSizes([300, 500]) # Default values for smaller screens
|
|
140
|
+
else:
|
|
141
|
+
# Fallback to fixed values if screen detection fails
|
|
142
|
+
self.splitter.setSizes([300, 500])
|
|
143
|
+
|
|
144
|
+
main_layout.addWidget(self.splitter)
|
|
145
|
+
|
|
146
|
+
def get_query_text(self):
|
|
147
|
+
"""Get the current query text"""
|
|
148
|
+
return self.query_edit.toPlainText()
|
|
149
|
+
|
|
150
|
+
def set_query_text(self, text):
|
|
151
|
+
"""Set the query text"""
|
|
152
|
+
self.query_edit.setPlainText(text)
|
|
153
|
+
|
|
154
|
+
def execute_query(self):
|
|
155
|
+
"""Execute the current query"""
|
|
156
|
+
if hasattr(self.parent, 'execute_query'):
|
|
157
|
+
self.parent.execute_query()
|
|
158
|
+
|
|
159
|
+
def clear_query(self):
|
|
160
|
+
"""Clear the query editor"""
|
|
161
|
+
if hasattr(self.parent, 'clear_query'):
|
|
162
|
+
self.parent.clear_query()
|
|
163
|
+
|
|
164
|
+
def export_to_excel(self):
|
|
165
|
+
"""Export results to Excel"""
|
|
166
|
+
if hasattr(self.parent, 'export_to_excel'):
|
|
167
|
+
self.parent.export_to_excel()
|
|
168
|
+
|
|
169
|
+
def export_to_parquet(self):
|
|
170
|
+
"""Export results to Parquet"""
|
|
171
|
+
if hasattr(self.parent, 'export_to_parquet'):
|
|
172
|
+
self.parent.export_to_parquet()
|
|
@@ -1,53 +1,131 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import sys
|
|
2
3
|
from PIL import Image, ImageDraw, ImageFont
|
|
3
4
|
|
|
4
5
|
def create_sql_icon(output_path, size=256):
|
|
5
|
-
"""Create a
|
|
6
|
+
"""Create a professional SQL icon with improved readability"""
|
|
6
7
|
# Create a new image with a transparent background
|
|
7
8
|
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
|
8
9
|
draw = ImageDraw.Draw(img)
|
|
9
10
|
|
|
10
11
|
# Define colors
|
|
11
|
-
primary_color = (44, 62, 80, 255)
|
|
12
|
+
primary_color = (44, 62, 80, 255) # Dark blue-gray
|
|
12
13
|
secondary_color = (52, 152, 219, 255) # Bright blue
|
|
13
|
-
|
|
14
|
+
text_color = (236, 240, 241, 255) # Light gray for better readability
|
|
15
|
+
accent_color = (26, 188, 156, 255) # Teal
|
|
14
16
|
|
|
15
17
|
# Draw a rounded rectangle background
|
|
16
|
-
radius = size //
|
|
17
|
-
rect = [(size//
|
|
18
|
+
radius = size // 8
|
|
19
|
+
rect = [(size//12, size//12), (size - size//12, size - size//12)]
|
|
18
20
|
draw.rounded_rectangle(rect, radius, fill=primary_color)
|
|
19
21
|
|
|
20
|
-
# Try to load
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# Try to load fonts in order of preference
|
|
23
|
+
font_size = size // 4 # Slightly smaller for better readability
|
|
24
|
+
font = None
|
|
25
|
+
|
|
26
|
+
# Common fonts list to try
|
|
27
|
+
font_options = [
|
|
28
|
+
"Arial Bold", "Arial", "Helvetica Bold", "Helvetica",
|
|
29
|
+
"DejaVuSans-Bold.ttf", "DejaVuSans.ttf",
|
|
30
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
|
31
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for font_name in font_options:
|
|
25
35
|
try:
|
|
26
|
-
font = ImageFont.truetype(
|
|
36
|
+
font = ImageFont.truetype(font_name, font_size)
|
|
37
|
+
break
|
|
27
38
|
except IOError:
|
|
28
|
-
|
|
39
|
+
continue
|
|
29
40
|
|
|
30
|
-
|
|
41
|
+
if font is None:
|
|
42
|
+
font = ImageFont.load_default()
|
|
43
|
+
font_size = size // 5 # Adjust size for default font
|
|
44
|
+
|
|
45
|
+
# Draw "SQL" text with improved visibility
|
|
31
46
|
text = "SQL"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
draw
|
|
47
|
+
|
|
48
|
+
# Handle different PIL versions for text size calculation
|
|
49
|
+
if hasattr(draw, 'textsize'):
|
|
50
|
+
text_width, text_height = draw.textsize(text, font=font)
|
|
51
|
+
elif hasattr(font, 'getsize'):
|
|
52
|
+
text_width, text_height = font.getsize(text)
|
|
53
|
+
else:
|
|
54
|
+
# Fallback for newer PIL versions
|
|
55
|
+
try:
|
|
56
|
+
text_width, text_height = font.getbbox(text)[2:]
|
|
57
|
+
except:
|
|
58
|
+
text_width, text_height = font_size * 3, font_size
|
|
59
|
+
|
|
60
|
+
# Position text slightly higher
|
|
61
|
+
position = ((size - text_width) // 2, (size - text_height) // 2 - size//12)
|
|
62
|
+
|
|
63
|
+
# Draw text with subtle shadow for better readability
|
|
64
|
+
shadow_offset = max(1, size // 64)
|
|
65
|
+
draw.text((position[0] + shadow_offset, position[1] + shadow_offset),
|
|
66
|
+
text, fill=(0, 0, 0, 100), font=font)
|
|
67
|
+
draw.text(position, text, fill=text_color, font=font)
|
|
35
68
|
|
|
36
69
|
# Draw a small database icon
|
|
37
|
-
db_size = size //
|
|
70
|
+
db_size = size // 3
|
|
38
71
|
db_x = size // 2 - db_size // 2
|
|
39
|
-
db_y = size // 2 + text_height // 2 + db_size //
|
|
72
|
+
db_y = size // 2 + text_height // 2 + db_size // 4
|
|
73
|
+
|
|
74
|
+
# Draw database cylinder with improved shape
|
|
75
|
+
# Top ellipse
|
|
76
|
+
draw.ellipse([(db_x, db_y - db_size//4), (db_x + db_size, db_y)],
|
|
77
|
+
fill=secondary_color)
|
|
78
|
+
# Bottom ellipse
|
|
79
|
+
draw.ellipse([(db_x, db_y + db_size//2), (db_x + db_size, db_y + db_size//1.5)],
|
|
80
|
+
fill=secondary_color)
|
|
81
|
+
# Rectangle body
|
|
82
|
+
draw.rectangle([(db_x, db_y), (db_x + db_size, db_y + db_size//2)],
|
|
83
|
+
fill=secondary_color)
|
|
40
84
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
# Add subtle details to database
|
|
86
|
+
highlight_color = (72, 172, 240, 150) # Lighter blue for highlight
|
|
87
|
+
# Database line details
|
|
88
|
+
line_y1 = db_y + db_size//6
|
|
89
|
+
line_y2 = db_y + db_size//3
|
|
90
|
+
draw.line([(db_x + db_size//6, line_y1), (db_x + db_size - db_size//6, line_y1)],
|
|
91
|
+
fill=highlight_color, width=max(1, size//128))
|
|
92
|
+
draw.line([(db_x + db_size//6, line_y2), (db_x + db_size - db_size//6, line_y2)],
|
|
93
|
+
fill=highlight_color, width=max(1, size//128))
|
|
45
94
|
|
|
46
|
-
# Save the image
|
|
47
|
-
img.save(output_path)
|
|
48
|
-
print(f"
|
|
95
|
+
# Save the image with compression
|
|
96
|
+
img.save(output_path, optimize=True)
|
|
97
|
+
print(f"Professional SQL icon created at {output_path}")
|
|
98
|
+
return img
|
|
49
99
|
|
|
50
|
-
|
|
100
|
+
def create_logo(size=512):
|
|
101
|
+
"""Create a properly sized logo for different uses"""
|
|
51
102
|
output_dir = os.path.dirname(os.path.abspath(__file__))
|
|
52
|
-
|
|
53
|
-
|
|
103
|
+
project_dir = os.path.abspath(os.path.join(output_dir, '..', '..'))
|
|
104
|
+
|
|
105
|
+
# Create different sizes
|
|
106
|
+
sizes = {
|
|
107
|
+
'icon': 128, # For app icon
|
|
108
|
+
'logo_small': 256, # For UI elements
|
|
109
|
+
'logo_medium': 512, # For documentation
|
|
110
|
+
'logo_large': 1024 # For high-res displays
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for name, icon_size in sizes.items():
|
|
114
|
+
output_path = os.path.join(output_dir, f"{name}.png")
|
|
115
|
+
create_sql_icon(output_path, icon_size)
|
|
116
|
+
|
|
117
|
+
# Create the main logo file at the root directory
|
|
118
|
+
main_logo_path = os.path.join(project_dir, "sqlshell_logo.png")
|
|
119
|
+
logo = create_sql_icon(main_logo_path, size)
|
|
120
|
+
print(f"Main logo created at {main_logo_path}")
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
# Default size is 512 if no argument provided
|
|
124
|
+
size = 512
|
|
125
|
+
if len(sys.argv) > 1:
|
|
126
|
+
try:
|
|
127
|
+
size = int(sys.argv[1])
|
|
128
|
+
except ValueError:
|
|
129
|
+
print(f"Invalid size argument: {sys.argv[1]}. Using default size {size}.")
|
|
130
|
+
|
|
131
|
+
create_logo(size)
|
|
@@ -2,7 +2,7 @@ from PIL import Image, ImageDraw, ImageFont
|
|
|
2
2
|
import os
|
|
3
3
|
import math
|
|
4
4
|
|
|
5
|
-
def create_frame(size, progress, text=None):
|
|
5
|
+
def create_frame(size, progress, text=None):
|
|
6
6
|
# Create a new image with a dark background
|
|
7
7
|
img = Image.new('RGBA', size, (44, 62, 80, 255)) # Dark blue-gray background
|
|
8
8
|
draw = ImageDraw.Draw(img)
|
|
@@ -12,12 +12,31 @@ def create_frame(size, progress, text=None): # We won't use text parameter anym
|
|
|
12
12
|
center_x = width // 2
|
|
13
13
|
center_y = height // 2
|
|
14
14
|
|
|
15
|
-
# Create a
|
|
16
|
-
radius =
|
|
15
|
+
# Create a more visible circular pattern
|
|
16
|
+
radius = 80 # Larger radius
|
|
17
17
|
num_dots = 12
|
|
18
|
-
dot_size =
|
|
19
|
-
trail_length =
|
|
18
|
+
dot_size = 6 # Larger dots
|
|
19
|
+
trail_length = 8 # Longer trail
|
|
20
20
|
|
|
21
|
+
# Add a subtle gradient background
|
|
22
|
+
for y in range(height):
|
|
23
|
+
# Create a vertical gradient
|
|
24
|
+
gradient_factor = y / height
|
|
25
|
+
r = int(44 + (52 - 44) * gradient_factor)
|
|
26
|
+
g = int(62 + (152 - 62) * gradient_factor)
|
|
27
|
+
b = int(80 + (219 - 80) * gradient_factor)
|
|
28
|
+
draw.line([(0, y), (width, y)], fill=(r, g, b, 255))
|
|
29
|
+
|
|
30
|
+
# Draw a pulsing circle
|
|
31
|
+
pulse_size = 40 + 15 * math.sin(progress * 2 * math.pi)
|
|
32
|
+
draw.ellipse(
|
|
33
|
+
(center_x - pulse_size, center_y - pulse_size,
|
|
34
|
+
center_x + pulse_size, center_y + pulse_size),
|
|
35
|
+
outline=(52, 152, 219, 100),
|
|
36
|
+
width=2
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Draw orbiting dots with trails
|
|
21
40
|
for i in range(num_dots):
|
|
22
41
|
# Calculate dot position
|
|
23
42
|
angle = (progress * 360 - i * (360 / num_dots)) % 360
|
|
@@ -32,21 +51,32 @@ def create_frame(size, progress, text=None): # We won't use text parameter anym
|
|
|
32
51
|
for size_mult in [1.0, 0.8, 0.6]:
|
|
33
52
|
current_size = int(dot_size * size_mult)
|
|
34
53
|
current_opacity = int(opacity * size_mult)
|
|
54
|
+
|
|
55
|
+
# Use more vibrant color for dots
|
|
35
56
|
draw.ellipse(
|
|
36
57
|
(x - current_size, y - current_size,
|
|
37
58
|
x + current_size, y + current_size),
|
|
38
|
-
fill=(
|
|
59
|
+
fill=(41, 128, 185, current_opacity) # Brighter blue with fading opacity
|
|
39
60
|
)
|
|
40
61
|
|
|
62
|
+
# Add a subtle glow effect in the center
|
|
63
|
+
glow_radius = 60 + 10 * math.sin(progress * 4 * math.pi)
|
|
64
|
+
for r in range(int(glow_radius), 0, -5):
|
|
65
|
+
opacity = int(100 * (r / glow_radius))
|
|
66
|
+
draw.ellipse(
|
|
67
|
+
(center_x - r, center_y - r, center_x + r, center_y + r),
|
|
68
|
+
fill=(52, 152, 219, opacity // 8) # Very transparent blue
|
|
69
|
+
)
|
|
70
|
+
|
|
41
71
|
return img
|
|
42
72
|
|
|
43
73
|
def create_splash_gif():
|
|
44
74
|
size = (400, 300)
|
|
45
75
|
frames = []
|
|
46
76
|
|
|
47
|
-
# Create
|
|
48
|
-
for i in range(
|
|
49
|
-
progress = i /
|
|
77
|
+
# Create 30 frames for smooth animation (reduced from 60 for smaller file size)
|
|
78
|
+
for i in range(30):
|
|
79
|
+
progress = i / 30
|
|
50
80
|
frame = create_frame(size, progress)
|
|
51
81
|
frames.append(frame)
|
|
52
82
|
|
|
@@ -56,9 +86,9 @@ def create_splash_gif():
|
|
|
56
86
|
output_path,
|
|
57
87
|
save_all=True,
|
|
58
88
|
append_images=frames[1:],
|
|
59
|
-
duration=
|
|
89
|
+
duration=66, # 66ms per frame = ~15fps (reduced from 20fps for smaller file size)
|
|
60
90
|
loop=0,
|
|
61
|
-
optimize=
|
|
91
|
+
optimize=True # Enable optimization
|
|
62
92
|
)
|
|
63
93
|
print(f"Created splash screen GIF at: {output_path}")
|
|
64
94
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
sqlshell/setup.py
CHANGED
|
@@ -21,7 +21,7 @@ setup(
|
|
|
21
21
|
},
|
|
22
22
|
author="SQLShell Team",
|
|
23
23
|
description="A powerful SQL shell with GUI interface for data analysis",
|
|
24
|
-
long_description=open('README.md').read(),
|
|
24
|
+
long_description=open('README.md', encoding='utf-8').read(),
|
|
25
25
|
long_description_content_type="text/markdown",
|
|
26
26
|
keywords="sql, data analysis, gui, duckdb",
|
|
27
27
|
url="https://github.com/yourusername/sqlshell",
|