decksmith 0.9.1__py3-none-any.whl → 0.9.3__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.
- decksmith/card_builder.py +66 -35
- decksmith/deck_builder.py +95 -85
- decksmith/export.py +4 -1
- decksmith/gui/app.py +25 -20
- decksmith/gui/static/css/style.css +105 -33
- decksmith/gui/static/img/decksmith.ico +0 -0
- decksmith/gui/static/js/main.js +169 -115
- decksmith/gui/templates/index.html +184 -182
- decksmith/image_ops.py +11 -0
- decksmith/main.py +7 -6
- decksmith/project.py +35 -39
- decksmith/renderers/image.py +4 -6
- decksmith/renderers/text.py +153 -127
- decksmith/validate.py +14 -2
- {decksmith-0.9.1.dist-info → decksmith-0.9.3.dist-info}/METADATA +3 -1
- decksmith-0.9.3.dist-info/RECORD +27 -0
- decksmith-0.9.1.dist-info/RECORD +0 -26
- {decksmith-0.9.1.dist-info → decksmith-0.9.3.dist-info}/WHEEL +0 -0
- {decksmith-0.9.1.dist-info → decksmith-0.9.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,182 +1,184 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-theme="dark">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>DeckSmith</title>
|
|
7
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
11
|
-
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
12
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
|
|
13
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.0/split.min.js"></script>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div id="header">
|
|
17
|
-
<div class="app-branding">
|
|
18
|
-
<
|
|
19
|
-
<div class="app-title">DeckSmith</div>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="project-controls">
|
|
22
|
-
<div class="project-path-container">
|
|
23
|
-
<i class="fa-regular fa-folder"></i>
|
|
24
|
-
<span id="current-project-path" title="Current Project Path">No project open</span>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="button-group">
|
|
27
|
-
<button id="open-project-btn" class="btn small" title="Open Project"><i class="fa-solid fa-folder-open"></i> Open</button>
|
|
28
|
-
<button id="new-project-btn" class="btn small" title="New Project"><i class="fa-solid fa-plus"></i> New</button>
|
|
29
|
-
<button id="close-project-btn" class="btn small" title="Close Project"><i class="fa-solid fa-xmark"></i> Close</button>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="app-container">
|
|
35
|
-
<div id="left-pane" class="split split-horizontal">
|
|
36
|
-
<div id="editors-container">
|
|
37
|
-
<div id="yaml-pane" class="split split-vertical">
|
|
38
|
-
<div class="pane-header">
|
|
39
|
-
<span><i class="fa-solid fa-code"></i> Layout (YAML)</span>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<button id="
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<div class="
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<i id="status-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<div class="modal-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>DeckSmith</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
11
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
12
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
|
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.0/split.min.js"></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="header">
|
|
17
|
+
<div class="app-branding">
|
|
18
|
+
<img src="{{ url_for('static', filename='img/decksmith.ico') }}" alt="DeckSmith Logo" class="app-logo">
|
|
19
|
+
<div class="app-title">DeckSmith</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="project-controls">
|
|
22
|
+
<div class="project-path-container">
|
|
23
|
+
<i class="fa-regular fa-folder"></i>
|
|
24
|
+
<span id="current-project-path" title="Current Project Path">No project open</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="button-group">
|
|
27
|
+
<button id="open-project-btn" class="btn small" title="Open Project"><i class="fa-solid fa-folder-open"></i> Open</button>
|
|
28
|
+
<button id="new-project-btn" class="btn small" title="New Project"><i class="fa-solid fa-plus"></i> New</button>
|
|
29
|
+
<button id="close-project-btn" class="btn small" title="Close Project"><i class="fa-solid fa-xmark"></i> Close</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="app-container">
|
|
35
|
+
<div id="left-pane" class="split split-horizontal">
|
|
36
|
+
<div id="editors-container">
|
|
37
|
+
<div id="yaml-pane" class="split split-vertical">
|
|
38
|
+
<div class="pane-header">
|
|
39
|
+
<span><i class="fa-solid fa-code"></i> Layout (YAML)</span>
|
|
40
|
+
<button class="btn small header-btn" title="Syntax Guide" onclick="window.open('https://julynx.github.io/decksmith/docs.html#deck-yaml-syntax', '_blank')"><i class="fa-solid fa-book"></i> Syntax Guide</button>
|
|
41
|
+
</div>
|
|
42
|
+
<div id="yaml-editor"></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div id="csv-pane" class="split split-vertical">
|
|
45
|
+
<div class="pane-header">
|
|
46
|
+
<span><i class="fa-solid fa-table"></i> Data (CSV)</span>
|
|
47
|
+
</div>
|
|
48
|
+
<div id="csv-editor"></div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div id="right-pane" class="split split-horizontal">
|
|
53
|
+
<div class="preview-wrapper">
|
|
54
|
+
<div class="controls">
|
|
55
|
+
<div class="control-group">
|
|
56
|
+
<label for="card-selector"><i class="fa-solid fa-list"></i> Card:</label>
|
|
57
|
+
<div class="select-wrapper">
|
|
58
|
+
<select id="card-selector">
|
|
59
|
+
<option value="-1">Select a card...</option>
|
|
60
|
+
</select>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="control-group actions">
|
|
64
|
+
<button id="build-btn" class="btn primary"><i class="fa-solid fa-hammer"></i> Build Deck</button>
|
|
65
|
+
<button id="export-btn" class="btn danger"><i class="fa-solid fa-file-pdf"></i> Export PDF</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div id="status-line" class="status-line"></div>
|
|
69
|
+
<div class="preview-area">
|
|
70
|
+
<div id="loading-indicator" class="hidden">
|
|
71
|
+
<i class="fa-solid fa-circle-notch fa-spin fa-2x"></i>
|
|
72
|
+
</div>
|
|
73
|
+
<img id="preview-image" src="" alt="Card Preview" class="hidden">
|
|
74
|
+
<div id="placeholder-text">
|
|
75
|
+
<span>Select a card to preview</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div id="status-bar">
|
|
79
|
+
<i id="status-spinner" class="fa-solid fa-circle-notch fa-spin fa-fw hidden" style="font-size: 0.9em;"></i>
|
|
80
|
+
<i id="status-icon" class="fa-solid fa-circle-info fa-fw" style="font-size: 0.9em;"></i>
|
|
81
|
+
<span id="status-text">Ready</span>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="toast-container"></div>
|
|
87
|
+
|
|
88
|
+
<!-- Welcome Screen -->
|
|
89
|
+
<div id="welcome-screen" class="modal hidden">
|
|
90
|
+
<div class="modal-overlay"></div>
|
|
91
|
+
<div class="modal-content welcome-content">
|
|
92
|
+
<img src="{{ url_for('static', filename='img/decksmith.ico') }}" alt="DeckSmith Logo" class="welcome-logo">
|
|
93
|
+
<h1>Welcome to DeckSmith</h1>
|
|
94
|
+
<p>Create and manage your card decks with ease.<br>Select a project to get started.</p>
|
|
95
|
+
<div class="welcome-actions">
|
|
96
|
+
<button id="welcome-open-btn" class="btn primary large"><i class="fa-solid fa-folder-open"></i> Open Project</button>
|
|
97
|
+
<button id="welcome-new-btn" class="btn secondary large"><i class="fa-solid fa-plus"></i> New Project</button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Shutdown Screen -->
|
|
103
|
+
<div id="shutdown-screen" class="modal hidden">
|
|
104
|
+
<div class="modal-overlay"></div>
|
|
105
|
+
<div class="modal-content welcome-content">
|
|
106
|
+
<div style="margin-bottom: 20px;">
|
|
107
|
+
<i class="fa-solid fa-power-off" style="font-size: 3em; color: var(--danger-color);"></i>
|
|
108
|
+
</div>
|
|
109
|
+
<h1>Service Stopped</h1>
|
|
110
|
+
<p id="shutdown-reason" style="font-weight: 500; color: var(--text-primary);">The DeckSmith service stopped.</p>
|
|
111
|
+
<p>You can close this window.<br>To continue working, simply launch the app again.</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Modal -->
|
|
116
|
+
<div id="path-modal" class="modal hidden">
|
|
117
|
+
<div class="modal-overlay"></div>
|
|
118
|
+
<div class="modal-content">
|
|
119
|
+
<h3 id="modal-title">Select Project</h3>
|
|
120
|
+
<div class="form-group">
|
|
121
|
+
<label for="project-path-input" id="project-path-label">Folder Path:</label>
|
|
122
|
+
<div class="input-group">
|
|
123
|
+
<input type="text" id="project-path-input" placeholder="e.g. C:/Projects/MyDeck">
|
|
124
|
+
<button id="browse-btn" class="btn secondary">Browse...</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="form-group hidden" id="project-name-group">
|
|
128
|
+
<label for="project-name-input">Project Name:</label>
|
|
129
|
+
<input type="text" id="project-name-input" placeholder="MyNewDeck">
|
|
130
|
+
</div>
|
|
131
|
+
<div class="modal-actions">
|
|
132
|
+
<button id="modal-cancel-btn" class="btn secondary">Cancel</button>
|
|
133
|
+
<button id="modal-confirm-btn" class="btn primary">Confirm</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Export Modal -->
|
|
139
|
+
<div id="export-modal" class="modal hidden">
|
|
140
|
+
<div class="modal-overlay"></div>
|
|
141
|
+
<div class="modal-content">
|
|
142
|
+
<h3>Export PDF</h3>
|
|
143
|
+
<div class="form-grid">
|
|
144
|
+
<div class="form-group">
|
|
145
|
+
<label for="export-filename">Filename:</label>
|
|
146
|
+
<input type="text" id="export-filename" value="deck.pdf">
|
|
147
|
+
</div>
|
|
148
|
+
<div class="form-group">
|
|
149
|
+
<label for="export-page-size">Page Size:</label>
|
|
150
|
+
<select id="export-page-size">
|
|
151
|
+
<option value="A4" selected>A4</option>
|
|
152
|
+
<option value="Letter">Letter</option>
|
|
153
|
+
</select>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="form-group">
|
|
156
|
+
<label for="export-width">Width (mm):</label>
|
|
157
|
+
<input type="number" id="export-width" value="63.5" step="0.1">
|
|
158
|
+
</div>
|
|
159
|
+
<div class="form-group">
|
|
160
|
+
<label for="export-height">Height (mm):</label>
|
|
161
|
+
<input type="number" id="export-height" value="88.9" step="0.1">
|
|
162
|
+
</div>
|
|
163
|
+
<div class="form-group">
|
|
164
|
+
<label for="export-gap">Gap (mm):</label>
|
|
165
|
+
<input type="number" id="export-gap" value="0" step="0.1">
|
|
166
|
+
</div>
|
|
167
|
+
<div class="form-group">
|
|
168
|
+
<label>Margins (mm):</label>
|
|
169
|
+
<div class="input-group">
|
|
170
|
+
<input type="number" id="export-margin-x" value="2" step="0.1" placeholder="X">
|
|
171
|
+
<input type="number" id="export-margin-y" value="2" step="0.1" placeholder="Y">
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="modal-actions">
|
|
176
|
+
<button id="export-cancel-btn" class="btn secondary">Cancel</button>
|
|
177
|
+
<button id="export-confirm-btn" class="btn primary">Export</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
decksmith/image_ops.py
CHANGED
|
@@ -119,3 +119,14 @@ class ImageOps:
|
|
|
119
119
|
if direction == "vertical":
|
|
120
120
|
return img.transpose(Image.FLIP_TOP_BOTTOM)
|
|
121
121
|
return img
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _filter_opacity(img: Image.Image, opacity: int) -> Image.Image:
|
|
125
|
+
opacity = max(opacity, 0)
|
|
126
|
+
opacity = min(opacity, 100)
|
|
127
|
+
|
|
128
|
+
img = img.convert("RGBA")
|
|
129
|
+
alpha = img.split()[3]
|
|
130
|
+
alpha = alpha.point(lambda p: int(p * opacity / 100))
|
|
131
|
+
img.putalpha(alpha)
|
|
132
|
+
return img
|
decksmith/main.py
CHANGED
|
@@ -80,8 +80,7 @@ def build(ctx, output, spec, data):
|
|
|
80
80
|
# pylint: disable=W0718
|
|
81
81
|
except Exception as exc:
|
|
82
82
|
logger.error("(x) Error building deck '%s' from spec '%s':", data, spec)
|
|
83
|
-
logger.error(" %s", exc)
|
|
84
|
-
logger.debug(traceback.format_exc())
|
|
83
|
+
logger.error(" %s\n%s", exc, traceback.format_exc())
|
|
85
84
|
ctx.exit(1)
|
|
86
85
|
|
|
87
86
|
logger.info("(✔) Deck built successfully.")
|
|
@@ -114,7 +113,8 @@ def build(ctx, output, spec, data):
|
|
|
114
113
|
default=[2, 2],
|
|
115
114
|
help="The horizontal and vertical page margins in millimeters.",
|
|
116
115
|
)
|
|
117
|
-
|
|
116
|
+
@click.pass_context
|
|
117
|
+
def export(ctx, image_folder, output, page_size, width, height, gap, margins):
|
|
118
118
|
"""Exports images from a folder to a PDF file."""
|
|
119
119
|
try:
|
|
120
120
|
image_folder_path = Path(image_folder)
|
|
@@ -133,12 +133,13 @@ def export(image_folder, output, page_size, width, height, gap, margins):
|
|
|
133
133
|
exporter.export()
|
|
134
134
|
logger.info("(✔) Successfully exported PDF to %s", output)
|
|
135
135
|
except FileNotFoundError as exc:
|
|
136
|
-
|
|
136
|
+
click.echo(f"(x) {exc}")
|
|
137
|
+
ctx.exit(1)
|
|
137
138
|
# pylint: disable=W0718
|
|
138
139
|
except Exception as exc:
|
|
139
140
|
logger.error("(x) Error exporting images to '%s':", output)
|
|
140
|
-
logger.error(" %s", exc)
|
|
141
|
-
|
|
141
|
+
logger.error(" %s\n%s", exc, traceback.format_exc())
|
|
142
|
+
ctx.exit(1)
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
if __name__ == "__main__":
|
decksmith/project.py
CHANGED
|
@@ -30,16 +30,12 @@ class ProjectManager:
|
|
|
30
30
|
def create_project(self, path: Path):
|
|
31
31
|
"""
|
|
32
32
|
Creates a new project at the specified path.
|
|
33
|
+
|
|
33
34
|
Args:
|
|
34
|
-
path (Path): The path
|
|
35
|
+
path (Path): The path where the project directory will be created.
|
|
35
36
|
"""
|
|
36
37
|
path.mkdir(parents=True, exist_ok=True)
|
|
37
38
|
|
|
38
|
-
# Copy templates
|
|
39
|
-
# Assuming templates are in decksmith/templates relative to this file
|
|
40
|
-
# But actually they are in decksmith/templates relative to the package root
|
|
41
|
-
# Let's use importlib.resources or relative path from __file__
|
|
42
|
-
# Since we are in decksmith/project.py, templates are in ../templates
|
|
43
39
|
template_dir = Path(__file__).parent / "templates"
|
|
44
40
|
|
|
45
41
|
if not (path / "deck.yaml").exists():
|
|
@@ -50,52 +46,52 @@ class ProjectManager:
|
|
|
50
46
|
|
|
51
47
|
self.working_dir = path
|
|
52
48
|
|
|
53
|
-
def
|
|
49
|
+
def _load_file_or_template(self, filename: str) -> str:
|
|
54
50
|
"""
|
|
55
|
-
|
|
51
|
+
Helper to load a file from the working directory, or fall back to a template.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
filename (str): The name of the file (e.g., "deck.yaml").
|
|
55
|
+
|
|
56
56
|
Returns:
|
|
57
|
-
|
|
57
|
+
str: The content of the file or template.
|
|
58
58
|
"""
|
|
59
59
|
if self.working_dir is None:
|
|
60
|
-
return
|
|
60
|
+
return ""
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
file_path = self.working_dir / filename
|
|
63
|
+
template_path = Path(__file__).parent / "templates" / filename
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
data["csv"] = csv_file.read()
|
|
85
|
-
elif csv_template.exists():
|
|
86
|
-
with open(csv_template, "r", encoding="utf-8") as csv_template_file:
|
|
87
|
-
data["csv"] = csv_template_file.read()
|
|
88
|
-
else:
|
|
89
|
-
data["csv"] = ""
|
|
90
|
-
|
|
91
|
-
return data
|
|
65
|
+
if file_path.exists() and file_path.stat().st_size > 0:
|
|
66
|
+
with open(file_path, "r", encoding="utf-8") as file_object:
|
|
67
|
+
return file_object.read()
|
|
68
|
+
elif template_path.exists():
|
|
69
|
+
with open(template_path, "r", encoding="utf-8") as file_object:
|
|
70
|
+
return file_object.read()
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
def load_files(self) -> Dict[str, str]:
|
|
74
|
+
"""
|
|
75
|
+
Loads the deck.yaml and deck.csv files from the current project.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dict[str, str]: A dictionary with keys "yaml" and "csv" containing file contents.
|
|
79
|
+
"""
|
|
80
|
+
return {
|
|
81
|
+
"yaml": self._load_file_or_template("deck.yaml"),
|
|
82
|
+
"csv": self._load_file_or_template("deck.csv"),
|
|
83
|
+
}
|
|
92
84
|
|
|
93
85
|
def save_files(self, yaml_content: Optional[str], csv_content: Optional[str]):
|
|
94
86
|
"""
|
|
95
87
|
Saves the deck.yaml and deck.csv files to the current project.
|
|
88
|
+
|
|
96
89
|
Args:
|
|
97
90
|
yaml_content (Optional[str]): The content of the deck.yaml file.
|
|
98
91
|
csv_content (Optional[str]): The content of the deck.csv file.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: If no project is currently selected (working_dir is None).
|
|
99
95
|
"""
|
|
100
96
|
if self.working_dir is None:
|
|
101
97
|
raise ValueError("No project selected")
|
decksmith/renderers/image.py
CHANGED
|
@@ -9,7 +9,6 @@ from typing import Any, Dict, Optional
|
|
|
9
9
|
from PIL import Image
|
|
10
10
|
|
|
11
11
|
from decksmith.image_ops import ImageOps
|
|
12
|
-
from decksmith.logger import logger
|
|
13
12
|
from decksmith.utils import apply_anchor
|
|
14
13
|
|
|
15
14
|
|
|
@@ -46,11 +45,10 @@ class ImageRenderer:
|
|
|
46
45
|
if potential_path.exists():
|
|
47
46
|
path = potential_path
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
48
|
+
if not path.exists():
|
|
49
|
+
raise FileNotFoundError(f"Image not found: {path}")
|
|
50
|
+
|
|
51
|
+
img = Image.open(path)
|
|
54
52
|
|
|
55
53
|
img = ImageOps.apply_filters(img, element.get("filters", {}))
|
|
56
54
|
|