tkweb 0.0.1__tar.gz

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.
@@ -0,0 +1,34 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-and-publish:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout repo
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+
20
+ - name: Install build tools
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install build twine
24
+
25
+ - name: Build package
26
+ run: |
27
+ python -m build
28
+
29
+ - name: Publish to PyPI
30
+ env:
31
+ TWINE_USERNAME: __token__
32
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
33
+ run: |
34
+ twine upload dist/*
@@ -0,0 +1,39 @@
1
+ # This workflow will install Python dependencies, run tests and lint with a single version of Python
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3
+
4
+ name: Python application
5
+
6
+ on:
7
+ push:
8
+ branches: [ "main" ]
9
+ pull_request:
10
+ branches: [ "main" ]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - name: Set up Python 3.10
23
+ uses: actions/setup-python@v3
24
+ with:
25
+ python-version: "3.10"
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install flake8 pytest
30
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31
+ - name: Lint with flake8
32
+ run: |
33
+ # stop the build if there are Python syntax errors or undefined names
34
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
35
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
36
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
37
+ - name: Test with pytest
38
+ run: |
39
+ pytest
tkweb-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben-bit-code208
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tkweb-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: tkweb
3
+ Version: 0.0.1
4
+ Summary: Tkinter For the Web - A package that brings Tkinter to web browsers using JS and python-wasm.
5
+ Project-URL: Homepage, https://github.com/ben-bit-code208/tkweb
6
+ Project-URL: Issues, https://github.com/ben-bit-code208/tkweb/issues
7
+ Author-email: Ben Klumski <b64964357@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.9
tkweb-0.0.1/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # How to use
2
+ Download the latest release put it in ...\whl\tkweb and load it using micropip (aka. pyodide)
3
+ ```
4
+ await pyodide.loadPackage("micropip");
5
+ const micropip = pyodide.pyimport("micropip");
6
+ await micropip.install(YOUR_WEBSITE_NAME\...\whl\tkweb\WHL_NAME.whl)
7
+ ```
8
+ **DO __NOT__ USE FORM GITHUB DIREKTLY YOU __CAN AND WILL__ ENCOUNTER __A CORS 304__ ERROR**
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling >= 1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ package-data = { "tkweb" = ["py.typed"] }
7
+ packages = ["src"]
8
+
9
+ [project]
10
+ name = "tkweb"
11
+ version = "0.0.1"
12
+ authors = [
13
+ { name="Ben Klumski", email="b64964357@gmail.com" },
14
+ ]
15
+ description = "Tkinter For the Web - A package that brings Tkinter to web browsers using JS and python-wasm."
16
+ requires-python = ">=3.9"
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ license = "MIT"
22
+ license-files = ["LICEN[CS]E*"]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/ben-bit-code208/tkweb"
26
+ Issues = "https://github.com/ben-bit-code208/tkweb/issues"
File without changes
@@ -0,0 +1,389 @@
1
+ # tkinter_browser.py - Minimaler tkinter Mock für Pyodide
2
+ """
3
+ Drop-in Replacement für tkinter im Browser.
4
+ Nutzt JavaScript DOM direkt über js.document
5
+
6
+ Verwendung:
7
+ import tkinter_browser as tk
8
+
9
+ root = tk.Tk()
10
+ label = tk.Label(root, text="Hello!")
11
+ label.pack()
12
+ root.mainloop()
13
+ """
14
+
15
+ import js
16
+ from pyodide.ffi import create_proxy
17
+
18
+ # Globale Container für Widgets
19
+ _root_element = None
20
+ _current_parent = None
21
+
22
+ def _create_element(tag, **attrs):
23
+ """Erstelle HTML Element"""
24
+ elem = js.document.createElement(tag)
25
+ for key, value in attrs.items():
26
+ if key == 'className':
27
+ elem.className = value
28
+ elif key == 'innerHTML':
29
+ elem.innerHTML = value
30
+ else:
31
+ elem.setAttribute(key, str(value))
32
+ return elem
33
+
34
+ class Tk:
35
+ """Root Window"""
36
+ def __init__(self):
37
+ global _root_element, _current_parent
38
+
39
+ # Erstelle Container
40
+ _root_element = js.document.getElementById('py-app-container')
41
+ if not _root_element:
42
+ _root_element = _create_element('div', id='py-app-container')
43
+ _root_element.style.padding = '20px'
44
+ js.document.body.appendChild(_root_element)
45
+
46
+ _current_parent = _root_element
47
+ self.elem = _root_element
48
+
49
+ def title(self, text):
50
+ js.document.title = text
51
+
52
+ def geometry(self, size):
53
+ # Ignoriere - Browser handled das
54
+ pass
55
+
56
+ def configure(self, **kwargs):
57
+ if 'bg' in kwargs:
58
+ self.elem.style.backgroundColor = kwargs['bg']
59
+
60
+ def withdraw(self):
61
+ self.elem.style.display = 'none'
62
+
63
+ def deiconify(self):
64
+ self.elem.style.display = 'block'
65
+
66
+ def mainloop(self):
67
+ # Browser handled das automatisch
68
+ pass
69
+
70
+ def after(self, ms, func):
71
+ js.setTimeout(create_proxy(func), ms)
72
+
73
+ def update(self):
74
+ pass # Browser macht das automatisch
75
+
76
+ def winfo_exists(self):
77
+ return True
78
+
79
+ class Toplevel:
80
+ """Toplevel Window (als Modal)"""
81
+ def __init__(self, parent):
82
+ global _current_parent
83
+
84
+ # Erstelle Modal Overlay
85
+ self.overlay = _create_element('div')
86
+ self.overlay.style.position = 'fixed'
87
+ self.overlay.style.top = '0'
88
+ self.overlay.style.left = '0'
89
+ self.overlay.style.width = '100%'
90
+ self.overlay.style.height = '100%'
91
+ self.overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'
92
+ self.overlay.style.display = 'flex'
93
+ self.overlay.style.justifyContent = 'center'
94
+ self.overlay.style.alignItems = 'center'
95
+ self.overlay.style.zIndex = '1000'
96
+
97
+ # Erstelle Window Container
98
+ self.elem = _create_element('div')
99
+ self.elem.style.backgroundColor = 'white'
100
+ self.elem.style.padding = '30px'
101
+ self.elem.style.borderRadius = '10px'
102
+ self.elem.style.minWidth = '400px'
103
+ self.elem.style.maxWidth = '80%'
104
+ self.elem.style.maxHeight = '80%'
105
+ self.elem.style.overflow = 'auto'
106
+
107
+ self.overlay.appendChild(self.elem)
108
+ js.document.body.appendChild(self.overlay)
109
+
110
+ _current_parent = self.elem
111
+
112
+ def title(self, text):
113
+ title_elem = _create_element('h2', innerHTML=text)
114
+ title_elem.style.marginTop = '0'
115
+ title_elem.style.marginBottom = '20px'
116
+ title_elem.style.color = '#667eea'
117
+ self.elem.insertBefore(title_elem, self.elem.firstChild)
118
+
119
+ def configure(self, **kwargs):
120
+ if 'bg' in kwargs:
121
+ self.elem.style.backgroundColor = kwargs['bg']
122
+
123
+ def geometry(self, size):
124
+ pass
125
+
126
+ def destroy(self):
127
+ if self.overlay.parentNode:
128
+ self.overlay.parentNode.removeChild(self.overlay)
129
+
130
+ def protocol(self, name, func):
131
+ if name == 'WM_DELETE_WINDOW':
132
+ # Füge X-Button hinzu
133
+ close_btn = _create_element('button', innerHTML='✕')
134
+ close_btn.style.position = 'absolute'
135
+ close_btn.style.right = '10px'
136
+ close_btn.style.top = '10px'
137
+ close_btn.style.border = 'none'
138
+ close_btn.style.background = 'none'
139
+ close_btn.style.fontSize = '24px'
140
+ close_btn.style.cursor = 'pointer'
141
+ close_btn.onclick = create_proxy(func)
142
+ self.elem.style.position = 'relative'
143
+ self.elem.appendChild(close_btn)
144
+
145
+ def focus_force(self):
146
+ pass
147
+
148
+ def winfo_exists(self):
149
+ return self.overlay.parentNode is not None
150
+
151
+ class Label:
152
+ """Label Widget"""
153
+ def __init__(self, parent, text="", **kwargs):
154
+ self.elem = _create_element('div', innerHTML=text)
155
+ self.elem.style.margin = '5px 0'
156
+
157
+ if 'font' in kwargs:
158
+ font = kwargs['font']
159
+ if isinstance(font, tuple):
160
+ self.elem.style.fontSize = f"{font[1]}px"
161
+ if 'bg' in kwargs:
162
+ self.elem.style.backgroundColor = kwargs['bg']
163
+ if 'fg' in kwargs:
164
+ self.elem.style.color = kwargs['fg']
165
+
166
+ def pack(self, **kwargs):
167
+ global _current_parent
168
+ _current_parent.appendChild(self.elem)
169
+
170
+ if 'pady' in kwargs:
171
+ self.elem.style.marginTop = f"{kwargs['pady']}px"
172
+ self.elem.style.marginBottom = f"{kwargs['pady']}px"
173
+
174
+ def config(self, **kwargs):
175
+ if 'text' in kwargs:
176
+ self.elem.innerHTML = kwargs['text']
177
+
178
+ class Button:
179
+ """Button Widget"""
180
+ def __init__(self, parent, text="", command=None, **kwargs):
181
+ self.elem = _create_element('button', innerHTML=text)
182
+ self.elem.style.padding = '12px 24px'
183
+ self.elem.style.margin = '5px'
184
+ self.elem.style.border = 'none'
185
+ self.elem.style.borderRadius = '8px'
186
+ self.elem.style.backgroundColor = '#667eea'
187
+ self.elem.style.color = 'white'
188
+ self.elem.style.fontSize = '14px'
189
+ self.elem.style.cursor = 'pointer'
190
+ self.elem.style.fontWeight = 'bold'
191
+
192
+ if command:
193
+ self.elem.onclick = create_proxy(command)
194
+
195
+ if 'bg' in kwargs:
196
+ self.elem.style.backgroundColor = kwargs['bg']
197
+ if 'fg' in kwargs:
198
+ self.elem.style.color = kwargs['fg']
199
+
200
+ def pack(self, **kwargs):
201
+ global _current_parent
202
+ _current_parent.appendChild(self.elem)
203
+
204
+ if 'pady' in kwargs:
205
+ self.elem.style.marginTop = f"{kwargs['pady']}px"
206
+ self.elem.style.marginBottom = f"{kwargs['pady']}px"
207
+ if 'fill' in kwargs and kwargs['fill'] == 'x':
208
+ self.elem.style.width = '100%'
209
+ if 'padx' in kwargs:
210
+ self.elem.style.marginLeft = f"{kwargs['padx']}px"
211
+ self.elem.style.marginRight = f"{kwargs['padx']}px"
212
+
213
+ def config(self, **kwargs):
214
+ if 'state' in kwargs:
215
+ if kwargs['state'] == 'disabled':
216
+ self.elem.disabled = True
217
+ self.elem.style.opacity = '0.5'
218
+ else:
219
+ self.elem.disabled = False
220
+ self.elem.style.opacity = '1'
221
+
222
+ class Entry:
223
+ """Entry Widget"""
224
+ def __init__(self, parent, **kwargs):
225
+ self.elem = _create_element('input')
226
+ self.elem.type = 'text'
227
+ self.elem.style.padding = '10px'
228
+ self.elem.style.margin = '5px'
229
+ self.elem.style.border = '2px solid #667eea'
230
+ self.elem.style.borderRadius = '5px'
231
+ self.elem.style.fontSize = '14px'
232
+
233
+ if 'width' in kwargs:
234
+ self.elem.style.width = f"{kwargs['width'] * 8}px"
235
+
236
+ def pack(self, **kwargs):
237
+ global _current_parent
238
+ _current_parent.appendChild(self.elem)
239
+
240
+ if 'pady' in kwargs:
241
+ self.elem.style.marginTop = f"{kwargs['pady']}px"
242
+ self.elem.style.marginBottom = f"{kwargs['pady']}px"
243
+
244
+ def get(self):
245
+ return self.elem.value
246
+
247
+ def delete(self, start, end):
248
+ self.elem.value = ""
249
+
250
+ def bind(self, event, func):
251
+ if event == '<Return>':
252
+ self.elem.onkeypress = create_proxy(lambda e: func(e) if e.key == 'Enter' else None)
253
+
254
+ def focus(self):
255
+ self.elem.focus()
256
+
257
+ class Text:
258
+ """Text Widget"""
259
+ def __init__(self, parent, **kwargs):
260
+ self.elem = _create_element('div')
261
+ self.elem.style.padding = '10px'
262
+ self.elem.style.margin = '10px 0'
263
+ self.elem.style.border = '2px solid #e9ecef'
264
+ self.elem.style.borderRadius = '8px'
265
+ self.elem.style.backgroundColor = '#f8f9fa'
266
+ self.elem.style.fontFamily = "'Courier New', monospace"
267
+ self.elem.style.fontSize = '14px'
268
+ self.elem.style.whiteSpace = 'pre-wrap'
269
+ self.elem.style.overflowY = 'auto'
270
+
271
+ if 'height' in kwargs:
272
+ self.elem.style.minHeight = f"{kwargs['height'] * 20}px"
273
+ if 'width' in kwargs:
274
+ self.elem.style.width = f"{kwargs['width'] * 8}px"
275
+
276
+ def pack(self, **kwargs):
277
+ global _current_parent
278
+ _current_parent.appendChild(self.elem)
279
+
280
+ if 'pady' in kwargs:
281
+ self.elem.style.marginTop = f"{kwargs['pady']}px"
282
+ self.elem.style.marginBottom = f"{kwargs['pady']}px"
283
+ if 'fill' in kwargs and kwargs['fill'] == 'both':
284
+ self.elem.style.flex = '1'
285
+ if 'expand' in kwargs and kwargs['expand']:
286
+ self.elem.style.flexGrow = '1'
287
+
288
+ def insert(self, pos, text):
289
+ self.elem.innerHTML += text.replace('\n', '<br>')
290
+
291
+ def delete(self, start, end):
292
+ self.elem.innerHTML = ""
293
+
294
+ def see(self, pos):
295
+ self.elem.scrollTop = self.elem.scrollHeight
296
+
297
+ def winfo_exists(self):
298
+ return self.elem.parentNode is not None
299
+
300
+ class Frame:
301
+ """Frame Widget"""
302
+ def __init__(self, parent, **kwargs):
303
+ global _current_parent
304
+ self.elem = _create_element('div')
305
+ self.elem.style.padding = '10px'
306
+ self.prev_parent = _current_parent
307
+
308
+ if 'bg' in kwargs:
309
+ self.elem.style.backgroundColor = kwargs['bg']
310
+
311
+ def pack(self, **kwargs):
312
+ global _current_parent
313
+ self.prev_parent.appendChild(self.elem)
314
+ _current_parent = self.elem # Kinder gehen in diesen Frame
315
+
316
+ if 'pady' in kwargs:
317
+ self.elem.style.marginTop = f"{kwargs['pady']}px"
318
+ self.elem.style.marginBottom = f"{kwargs['pady']}px"
319
+ if 'fill' in kwargs and kwargs['fill'] == 'x':
320
+ self.elem.style.width = '100%'
321
+
322
+ def pack_forget(self):
323
+ if self.elem.parentNode:
324
+ self.elem.parentNode.removeChild(self.elem)
325
+
326
+ class Scale:
327
+ """Scale Widget (Slider)"""
328
+ def __init__(self, parent, **kwargs):
329
+ self.elem = _create_element('input')
330
+ self.elem.type = 'range'
331
+ self.elem.min = kwargs.get('from_', 0)
332
+ self.elem.max = kwargs.get('to', 100)
333
+ self.elem.value = self.elem.min
334
+ self.elem.style.width = '100%'
335
+
336
+ if 'command' in kwargs:
337
+ self.elem.oninput = create_proxy(lambda e: kwargs['command'](self.elem.value))
338
+
339
+ def pack(self, **kwargs):
340
+ global _current_parent
341
+ _current_parent.appendChild(self.elem)
342
+
343
+ def get(self):
344
+ return float(self.elem.value)
345
+
346
+ def set(self, value):
347
+ self.elem.value = str(value)
348
+
349
+ def config(self, **kwargs):
350
+ if 'command' in kwargs:
351
+ self.elem.oninput = create_proxy(lambda e: kwargs['command'](self.elem.value))
352
+
353
+ # Messagebox
354
+ class messagebox:
355
+ @staticmethod
356
+ def showinfo(title, message, **kwargs):
357
+ js.alert(f"{title}\n\n{message}")
358
+
359
+ @staticmethod
360
+ def showwarning(title, message, **kwargs):
361
+ js.alert(f"⚠️ {title}\n\n{message}")
362
+
363
+ @staticmethod
364
+ def showerror(title, message, **kwargs):
365
+ js.alert(f"❌ {title}\n\n{message}")
366
+
367
+ @staticmethod
368
+ def askyesno(title, message, **kwargs):
369
+ return js.confirm(f"{title}\n\n{message}")
370
+
371
+ class simpledialog:
372
+ @staticmethod
373
+ def askstring(title, prompt, **kwargs):
374
+ result = js.prompt(f"{title}\n\n{prompt}")
375
+ return result if result else None
376
+
377
+ # Konstanten
378
+ NORMAL = "normal"
379
+ DISABLED = "disabled"
380
+ END = "end"
381
+ RIDGE = "ridge"
382
+ HORIZONTAL = "horizontal"
383
+
384
+ # Export
385
+ __all__ = [
386
+ 'Tk', 'Toplevel', 'Label', 'Button', 'Entry', 'Text', 'Frame', 'Scale',
387
+ 'messagebox', 'simpledialog',
388
+ 'NORMAL', 'DISABLED', 'END', 'RIDGE', 'HORIZONTAL'
389
+ ]
@@ -0,0 +1 @@
1
+ This File is unimportent this is only to force a push on github..