added webpack and fixed ImageLabel
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
|
||||
.env-cmdrc.json
|
||||
build
|
||||
dist
|
||||
|
||||
python-tests/
|
||||
|
||||
|
||||
2211
package-lock.json
generated
2211
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,8 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
||||
"start": "env-cmd -e development cross-env NODE_ENV=development webpack serve",
|
||||
"build": "env-cmd -e production cross-env NODE_ENV=production webpack",
|
||||
"build:local": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
@@ -66,9 +66,11 @@
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"ajv": "^7.2.4",
|
||||
"babel-loader": "^10.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||
"docsify-cli": "^4.4.4",
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A python GUI builder. Create tkinter, Customtk, Kivy and PySide using GUI builder"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script async src="https://tally.so/widgets/embed.js"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_ANALYTICS_SCRIPT_ID%"></script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= REACT_APP_ANALYTICS_SCRIPT_ID %>"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '%REACT_APP_ANALYTICS_SCRIPT_ID%');
|
||||
gtag('config', '<%= REACT_APP_ANALYTICS_SCRIPT_ID %>');
|
||||
</script>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
|
||||
@@ -19,7 +19,8 @@ import { removeDuplicateObjects } from "../utils/common"
|
||||
|
||||
|
||||
// import DotsBackground from "../assets/background/dots.svg"
|
||||
import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
// import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
import DotsBackground from "../assets/background/dots.svg";
|
||||
|
||||
import DroppableWrapper from "../components/draggable/droppable"
|
||||
|
||||
|
||||
@@ -159,7 +159,6 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: add empty __init__ file
|
||||
for (let customWidget of customPythonWidgets){
|
||||
|
||||
let [fileName, extension] = customWidget.split(".")
|
||||
@@ -169,9 +168,8 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
||||
}
|
||||
|
||||
|
||||
const fileContent = pythonFiles(`./${fileName}`);
|
||||
const fileContent = pythonFiles(`./${fileName}`).default
|
||||
|
||||
console.log("file name: ", fileContent.default, pythonFiles(`./${fileName}`))
|
||||
createFileList.push({
|
||||
fileData: new Blob([fileContent], { type: "text/plain" }),
|
||||
fileName: fileName,
|
||||
@@ -179,6 +177,14 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
||||
})
|
||||
}
|
||||
|
||||
if (customPythonWidgets.length > 0){
|
||||
createFileList.push({
|
||||
fileData: new Blob([''], { type: "text/plain" }),
|
||||
fileName: '__init__.py',
|
||||
folder: "customWidgets"
|
||||
})
|
||||
}
|
||||
|
||||
for (let asset of assetFiles){
|
||||
|
||||
if (asset.fileType === "image"){
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
# Author: Paul: https://github.com/PaulleDemon
|
||||
# Made using PyUibuilder: https://pyuibuilder.com
|
||||
# MIT License - keep the copy of this license
|
||||
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
|
||||
class ImageLabel(tk.Label):
|
||||
def __init__(self, master, image_path, mode="fit", *args, **kwargs):
|
||||
def __init__(self, master, image_path=None, mode="cover", width=100, height=100, *args, **kwargs):
|
||||
"""
|
||||
mode:
|
||||
mode:
|
||||
- "fit" -> Keeps aspect ratio, fits inside label
|
||||
- "cover" -> Covers label fully, cropping excess
|
||||
"""
|
||||
super().__init__(master, *args, **kwargs)
|
||||
self.parent = master # Store parent reference
|
||||
super().__init__(master, width=width, height=height, *args, **kwargs)
|
||||
self.parent = master
|
||||
self.image_path = image_path
|
||||
self.mode = mode
|
||||
self.original_image = Image.open(image_path)
|
||||
self.original_image = None
|
||||
self.photo = None
|
||||
self.resize_job = None # Debounce job reference
|
||||
|
||||
self.force_resize() # Initial resize
|
||||
self.after(100, self.init_events) # Delay event binding slightly
|
||||
if mode not in ['fit', 'cover']:
|
||||
raise Exception("Mode can only be fit or cover.")
|
||||
|
||||
if image_path:
|
||||
try:
|
||||
self.original_image = Image.open(image_path)
|
||||
self.photo = ImageTk.PhotoImage(self.original_image)
|
||||
self.config(image=self.photo)
|
||||
|
||||
self.force_resize()
|
||||
except Exception as e:
|
||||
print(f"Error loading image: {e}")
|
||||
|
||||
self.after(100, self.init_events)
|
||||
|
||||
def init_events(self):
|
||||
self.parent.bind("<Configure>", self.on_resize) # Bind resize to parent
|
||||
self.parent.bind("<Configure>", self.on_resize)
|
||||
|
||||
def on_resize(self, event=None):
|
||||
"""Debounce resizing to prevent rapid execution."""
|
||||
@@ -30,8 +47,13 @@ class ImageLabel(tk.Label):
|
||||
|
||||
def force_resize(self):
|
||||
"""Resize image using actual widget size."""
|
||||
width = self.winfo_width()
|
||||
height = self.winfo_height()
|
||||
|
||||
if self.original_image is None:
|
||||
return # Do nothing if no image is loaded
|
||||
|
||||
width = self.winfo_width()
|
||||
height = self.winfo_height()
|
||||
|
||||
if width < 5 or height < 5:
|
||||
return
|
||||
|
||||
@@ -53,6 +75,7 @@ class ImageLabel(tk.Label):
|
||||
else:
|
||||
new_width = int(height * aspect_ratio)
|
||||
new_height = height
|
||||
|
||||
resized = self.original_image.resize((new_width, new_height), Image.LANCZOS)
|
||||
|
||||
# Crop excess
|
||||
|
||||
@@ -561,7 +561,8 @@ export class TkinterBase extends Widget {
|
||||
const { side = "top", expand = false, anchor } = widgetRef.getPackAttrs() || {};
|
||||
|
||||
// console.log("rerendering:", side, expand);
|
||||
|
||||
|
||||
|
||||
const directionMap = {
|
||||
top: "column",
|
||||
bottom: "column-reverse",
|
||||
@@ -587,7 +588,7 @@ export class TkinterBase extends Widget {
|
||||
expandValue = 1
|
||||
}
|
||||
|
||||
// TODO: if previous expand value is greater than 0 and current doesn't have expand then it should be 0
|
||||
// TODO: if the child widget as fillx or y use flex grow
|
||||
|
||||
if ((expand && !isSameSide) || (expand && previousExpandValue === 0)){
|
||||
previousExpandValue = expandValue;
|
||||
@@ -610,7 +611,10 @@ export class TkinterBase extends Widget {
|
||||
|
||||
|
||||
const stretchClass = isVertical ? "tw-flex-grow" : "tw-h-full"; // Allow only horizontal growth for top/bottom
|
||||
|
||||
// TODO: if previous expand value is greater than 0 and current doesn't have expand then it should be 0
|
||||
|
||||
// const fill = this.getAttrValue("flexManager.fillX") || this.getAttrValue("flexManager.fillY")
|
||||
|
||||
if (isSameSide) {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -12,7 +12,7 @@ class Label extends TkinterWidgetBase{
|
||||
static widgetType = "label"
|
||||
static displayName = "Label"
|
||||
|
||||
static requiredCustomPyFiles = ["imageLabel"]
|
||||
// static requiredCustomPyFiles = ["imageLabel"]
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@@ -104,11 +104,20 @@ class Label extends TkinterWidgetBase{
|
||||
const imports = super.getImports()
|
||||
|
||||
if (this.getAttrValue("imageUpload"))
|
||||
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||
imports.push("import os", "from PIL import Image, ImageTk", "from customWidgets.imageLabel import ImageLabel")
|
||||
|
||||
return imports
|
||||
}
|
||||
|
||||
getRequiredCustomPyFiles(){
|
||||
const requiredCustomFiles = super.getRequiredCustomPyFiles()
|
||||
|
||||
if (this.getAttrValue("imageUpload"))
|
||||
requiredCustomFiles.push("imageLabel")
|
||||
|
||||
return requiredCustomFiles
|
||||
}
|
||||
|
||||
getRequirements(){
|
||||
const requirements = super.getRequirements()
|
||||
|
||||
@@ -161,10 +170,10 @@ class Label extends TkinterWidgetBase{
|
||||
const code = []
|
||||
|
||||
if (image?.name){
|
||||
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||
// code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||
// code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||
// code.push("\n")
|
||||
labelInitialization = `${variableName} = tk.Label(master=${parent}, image=${variableName}_img, text="${labelText}", compound=tk.TOP)`
|
||||
labelInitialization = `${variableName} = ImageLabel(master=${parent}, image_path=${getPythonAssetPath(image.name, "image")}, text="${labelText}", compound=tk.TOP, mode="${this.getAttrValue("imageSize.mode")}")`
|
||||
}
|
||||
|
||||
// code.push("\n")
|
||||
|
||||
@@ -14,6 +14,9 @@ import "./styles/index.css";
|
||||
|
||||
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
||||
|
||||
window.React = React
|
||||
|
||||
|
||||
const originalSetItem = localStorage.setItem;
|
||||
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
||||
localStorage.setItem = function (key, value) {
|
||||
|
||||
@@ -47,6 +47,10 @@ function Premium({ children, className = "" }) {
|
||||
>
|
||||
more.
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
Premium features will start rolling out phase wise from mid of April, after which there would be a price increase.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,110 @@
|
||||
const webpack = require('webpack');
|
||||
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
const fs = require('fs');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
// Load .env-cmdrc.json based on NODE_ENV
|
||||
const envFile = `.env-cmdrc.json`;
|
||||
const envConfig = JSON.parse(fs.readFileSync(envFile, 'utf8'))[process.env.NODE_ENV] || {};
|
||||
|
||||
// Convert JSON to a format Webpack understands
|
||||
const envKeys = Object.keys(envConfig).reduce((prev, next) => {
|
||||
prev[`process.env.${next}`] = JSON.stringify(envConfig[next]);
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.py$/,
|
||||
use: "raw-loader",
|
||||
mode: isProduction ? "production" : "development",
|
||||
|
||||
watch: !isProduction,
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: isProduction ? "js/[name].[contenthash].js" : "js/[name].js",
|
||||
publicPath: "/",
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader",],
|
||||
},
|
||||
{
|
||||
test: /\.py$/,
|
||||
use: "raw-loader", // Load Python files as raw text
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: ["@svgr/webpack"], // Enables importing SVGs as React components
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|ico)$/,
|
||||
type: "asset/resource", // Handles image files
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(envKeys),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./public/index.html",
|
||||
minify: isProduction,
|
||||
inject: true,
|
||||
templateParameters: envConfig
|
||||
}),
|
||||
(isProduction ? new MiniCssExtractPlugin({ filename: 'styles.[contenthash].css' }) : new MiniCssExtractPlugin()),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "src/assets"),
|
||||
to: "assets", // Copies to `dist/assets`
|
||||
noErrorOnMissing: true, // Prevents errors if the folder is missing
|
||||
},
|
||||
{ from: 'public', to: '', noErrorOnMissing: true, globOptions: { ignore: ['**/index.html'] }}, // Copies everything else from public to dist
|
||||
],
|
||||
}),
|
||||
|
||||
new webpack.EnvironmentPlugin({
|
||||
REACT_APP_ANALYTICS_SCRIPT_ID: process.env.REACT_APP_ANALYTICS_SCRIPT_ID || '', // Default empty value
|
||||
API_URL: 'https://default.api.com'
|
||||
})
|
||||
|
||||
],
|
||||
|
||||
optimization: isProduction
|
||||
? {
|
||||
minimize: true,
|
||||
minimizer: [new CssMinimizerPlugin()],
|
||||
}
|
||||
: {},
|
||||
|
||||
devServer: {
|
||||
static: path.join(__dirname, "public"),
|
||||
port: 3000,
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user