Compare commits
5 Commits
code-edito
...
label-fixe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fff10097fd | ||
|
|
d10928bb5a | ||
|
|
0243b3b9e8 | ||
|
|
66d7dfc45f | ||
|
|
a38cd90c16 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
.env-cmdrc.json
|
.env-cmdrc.json
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
python-tests/
|
python-tests/
|
||||||
|
|
||||||
|
|||||||
6526
package-lock.json
generated
6526
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -35,8 +35,8 @@
|
|||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "env-cmd -e development cross-env NODE_ENV=development webpack serve",
|
||||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
"build": "env-cmd -e production cross-env NODE_ENV=production webpack",
|
||||||
"build:local": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
|
"build:local": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
@@ -63,8 +63,28 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@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",
|
"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",
|
"docsify-cli": "^4.4.4",
|
||||||
"typescript": "^4.9.5"
|
"file-loader": "^6.2.0",
|
||||||
|
"html-webpack-plugin": "^5.6.3",
|
||||||
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
|
"react-app-rewired": "^2.2.1",
|
||||||
|
"sass-loader": "^16.0.5",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"terser-webpack-plugin": "^5.3.14",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"webpack": "^5.98.0",
|
||||||
|
"webpack-cli": "^6.0.1",
|
||||||
|
"webpack-dev-server": "^5.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,31 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<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="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="A python GUI builder. Create tkinter, Customtk, Kivy and PySide using GUI builder"
|
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
|
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/
|
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" />
|
<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>
|
<script async src="https://tally.so/widgets/embed.js"></script>
|
||||||
|
|
||||||
<!-- Google tag (gtag.js) -->
|
<!-- 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>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', '%REACT_APP_ANALYTICS_SCRIPT_ID%');
|
gtag('config', '<%= REACT_APP_ANALYTICS_SCRIPT_ID %>');
|
||||||
</script>
|
</script>
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
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 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"
|
import DroppableWrapper from "../components/draggable/droppable"
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class Widget extends React.Component {
|
|||||||
static requirements = [] // requirements for the widgets (libraries) eg: tkvideoplayer, tktimepicker
|
static requirements = [] // requirements for the widgets (libraries) eg: tkvideoplayer, tktimepicker
|
||||||
static requiredImports = [] // import statements
|
static requiredImports = [] // import statements
|
||||||
|
|
||||||
|
static requiredCustomPyFiles = [] // custom widgets inside of pythonWidgets don't add .py at the end
|
||||||
|
|
||||||
// static contextType = ActiveWidgetContext
|
// static contextType = ActiveWidgetContext
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -185,6 +187,7 @@ class Widget extends React.Component {
|
|||||||
|
|
||||||
this.getImports = this.getImports.bind(this)
|
this.getImports = this.getImports.bind(this)
|
||||||
this.getRequirements = this.getRequirements.bind(this)
|
this.getRequirements = this.getRequirements.bind(this)
|
||||||
|
this.getRequiredCustomPyFiles = this.getRequiredCustomPyFiles.bind(this)
|
||||||
|
|
||||||
// this.openRenaming = this.openRenaming.bind(this)
|
// this.openRenaming = this.openRenaming.bind(this)
|
||||||
|
|
||||||
@@ -215,8 +218,6 @@ class Widget extends React.Component {
|
|||||||
this.stateUpdateCallback = null // allowing other components such as toolbar to subscribe to changes in this widget
|
this.stateUpdateCallback = null // allowing other components such as toolbar to subscribe to changes in this widget
|
||||||
this.resizeObserver = null
|
this.resizeObserver = null
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -428,6 +429,10 @@ class Widget extends React.Component {
|
|||||||
return this.constructor.requiredImports
|
return this.constructor.requiredImports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRequiredCustomPyFiles(){
|
||||||
|
return this.constructor.requiredCustomPyFiles
|
||||||
|
}
|
||||||
|
|
||||||
generateCode(){
|
generateCode(){
|
||||||
throw new NotImplementedError("generateCode() must be implemented by the subclass")
|
throw new NotImplementedError("generateCode() must be implemented by the subclass")
|
||||||
}
|
}
|
||||||
@@ -918,6 +923,8 @@ class Widget extends React.Component {
|
|||||||
pos
|
pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {layout} = attrs
|
||||||
|
|
||||||
this.setState(newData, () => {
|
this.setState(newData, () => {
|
||||||
// Updates attrs
|
// Updates attrs
|
||||||
let newAttrs = { ...this.state.attrs }
|
let newAttrs = { ...this.state.attrs }
|
||||||
@@ -950,6 +957,10 @@ class Widget extends React.Component {
|
|||||||
if (selected){
|
if (selected){
|
||||||
this.select()
|
this.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (layout){
|
||||||
|
this.setLayout(layout)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,13 +58,35 @@ export class CustomTkBase extends Widget {
|
|||||||
const absolutePositioning = this.getAttrValue("positioning")
|
const absolutePositioning = this.getAttrValue("positioning")
|
||||||
|
|
||||||
let layoutManager = `pack()`
|
let layoutManager = `pack()`
|
||||||
|
const marginX = this.getAttrValue("margin.marginX")
|
||||||
|
const marginY = this.getAttrValue("margin.marginY")
|
||||||
|
|
||||||
|
const paddingX = this.getAttrValue("padding.padX")
|
||||||
|
const paddingY = this.getAttrValue("padding.padY")
|
||||||
|
|
||||||
|
let config = {}
|
||||||
|
|
||||||
|
if (marginX){
|
||||||
|
config["padx"] = marginX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marginY){
|
||||||
|
config["pady"] = marginY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddingX){
|
||||||
|
config["ipadx"] = paddingX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddingY){
|
||||||
|
config["ipady"] = paddingY
|
||||||
|
}
|
||||||
|
|
||||||
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||||
|
|
||||||
const config = {
|
|
||||||
x: Math.trunc(this.state.pos.x),
|
config['x'] = Math.trunc(this.state.pos.x)
|
||||||
y: Math.trunc(this.state.pos.y),
|
config['y'] = Math.trunc(this.state.pos.y)
|
||||||
}
|
|
||||||
|
|
||||||
config["width"] = Math.trunc(this.state.size.width)
|
config["width"] = Math.trunc(this.state.size.width)
|
||||||
config["height"] = Math.trunc(this.state.size.height)
|
config["height"] = Math.trunc(this.state.size.height)
|
||||||
@@ -84,12 +106,6 @@ export class CustomTkBase extends Widget {
|
|||||||
|
|
||||||
const packSide = this.getAttrValue("flexManager.side")
|
const packSide = this.getAttrValue("flexManager.side")
|
||||||
|
|
||||||
const marginX = this.getAttrValue("margin.marginX")
|
|
||||||
const marginY = this.getAttrValue("margin.marginY")
|
|
||||||
|
|
||||||
const paddingX = this.getAttrValue("padding.padX")
|
|
||||||
const paddingY = this.getAttrValue("padding.padY")
|
|
||||||
|
|
||||||
const config = {}
|
const config = {}
|
||||||
|
|
||||||
if (packSide === "" || packSide === "top"){
|
if (packSide === "" || packSide === "top"){
|
||||||
@@ -113,21 +129,6 @@ export class CustomTkBase extends Widget {
|
|||||||
// config["pady"] = gap
|
// config["pady"] = gap
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (marginX){
|
|
||||||
config["padx"] = marginX
|
|
||||||
}
|
|
||||||
|
|
||||||
if (marginY){
|
|
||||||
config["pady"] = marginY
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paddingX){
|
|
||||||
config["ipadx"] = paddingX
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paddingY){
|
|
||||||
config["ipady"] = paddingY
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (align === "start"){
|
// if (align === "start"){
|
||||||
// config["anchor"] = "'nw'"
|
// config["anchor"] = "'nw'"
|
||||||
@@ -167,10 +168,8 @@ export class CustomTkBase extends Widget {
|
|||||||
|
|
||||||
const sticky = this.getAttrValue("gridManager.sticky")
|
const sticky = this.getAttrValue("gridManager.sticky")
|
||||||
|
|
||||||
const config = {
|
config['row'] = row - 1
|
||||||
row: row-1, // unlike css grid tkinter grid starts from 0
|
config['column'] = column - 1
|
||||||
column: column-1, // unlike css grid tkinter grid starts from 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowSpan > 1){
|
if (rowSpan > 1){
|
||||||
config['rowspan'] = rowSpan
|
config['rowspan'] = rowSpan
|
||||||
@@ -574,11 +573,13 @@ export class CustomTkBase extends Widget {
|
|||||||
|
|
||||||
let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1;
|
let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1;
|
||||||
|
|
||||||
if ((expand && previousExpandValue === 0) || (expand && expandValue === 0)){
|
if (expand && expandValue === 0){
|
||||||
expandValue = 1 // if there is expand it should expand
|
expandValue = 1 // if there is expand it should expand
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expand && !isSameSide) previousExpandValue = expandValue;
|
if ((expand && !isSameSide) || (expand && previousExpandValue === 0)){
|
||||||
|
previousExpandValue = expandValue;
|
||||||
|
}
|
||||||
|
|
||||||
lastSide = side; // Update last side for recursion
|
lastSide = side; // Update last side for recursion
|
||||||
|
|
||||||
@@ -1219,23 +1220,23 @@ export class CustomTkWidgetBase extends CustomTkBase{
|
|||||||
if (this.getAttrValue("cursor"))
|
if (this.getAttrValue("cursor"))
|
||||||
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
||||||
|
|
||||||
if (this.getAttrValue("padding.padX")){
|
// if (this.getAttrValue("padding.padX")){
|
||||||
// inner padding
|
// // inner padding
|
||||||
config["ipadx"] = this.getAttrValue("padding.padX")
|
// config["ipadx"] = this.getAttrValue("padding.padX")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.getAttrValue("padding.padY")){
|
// if (this.getAttrValue("padding.padY")){
|
||||||
config["ipady"] = this.getAttrValue("padding.padY")
|
// config["ipady"] = this.getAttrValue("padding.padY")
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
if (this.getAttrValue("margin.marginX")){
|
// if (this.getAttrValue("margin.marginX")){
|
||||||
config["padx"] = this.getAttrValue("margin.marginX")
|
// config["padx"] = this.getAttrValue("margin.marginX")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.getAttrValue("margin.marginY")){
|
// if (this.getAttrValue("margin.marginY")){
|
||||||
config["pady"] = this.getAttrValue("margin.marginY")
|
// config["pady"] = this.getAttrValue("margin.marginY")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in pack and grid not pixels
|
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in pack and grid not pixels
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import MainWindow from "../widgets/mainWindow"
|
|||||||
import { message } from "antd"
|
import { message } from "antd"
|
||||||
import TopLevel from "../widgets/toplevel"
|
import TopLevel from "../widgets/toplevel"
|
||||||
|
|
||||||
|
const pythonFiles = require.context("../pythonWidgets", false, /\.py$/)
|
||||||
|
|
||||||
|
|
||||||
// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null
|
// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null
|
||||||
// Recursive function to generate the code list, imports, requirements, and track mainVariable
|
// Recursive function to generate the code list, imports, requirements, and track mainVariable
|
||||||
@@ -13,6 +15,8 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
let requirements = new Set([])
|
let requirements = new Set([])
|
||||||
let code = []
|
let code = []
|
||||||
|
|
||||||
|
let customPythonWidgets = new Set([])
|
||||||
|
|
||||||
for (let widget of widgetList) {
|
for (let widget of widgetList) {
|
||||||
const widgetRef = widgetRefs[widget.id].current
|
const widgetRef = widgetRefs[widget.id].current
|
||||||
let varName = widgetRef.getVariableName()
|
let varName = widgetRef.getVariableName()
|
||||||
@@ -20,6 +24,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
// Add imports and requirements to sets
|
// Add imports and requirements to sets
|
||||||
widgetRef.getImports().forEach(importItem => imports.add(importItem))
|
widgetRef.getImports().forEach(importItem => imports.add(importItem))
|
||||||
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
|
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
|
||||||
|
widgetRef.getRequiredCustomPyFiles().forEach(customFile => customPythonWidgets.add(customFile))
|
||||||
|
|
||||||
// Set main variable if the widget is MainWindow
|
// Set main variable if the widget is MainWindow
|
||||||
if (widget.widgetType === MainWindow) {
|
if (widget.widgetType === MainWindow) {
|
||||||
@@ -68,6 +73,8 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
// Merge child imports, requirements, and code
|
// Merge child imports, requirements, and code
|
||||||
imports = new Set([...imports, ...childResult.imports])
|
imports = new Set([...imports, ...childResult.imports])
|
||||||
requirements = new Set([...requirements, ...childResult.requirements])
|
requirements = new Set([...requirements, ...childResult.requirements])
|
||||||
|
customPythonWidgets = new Set([...customPythonWidgets, ...childResult.customPythonWidgets])
|
||||||
|
|
||||||
code.push(...childResult.code)
|
code.push(...childResult.code)
|
||||||
|
|
||||||
mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable
|
mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable
|
||||||
@@ -78,6 +85,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
imports: Array.from(imports),
|
imports: Array.from(imports),
|
||||||
code: code,
|
code: code,
|
||||||
requirements: Array.from(requirements),
|
requirements: Array.from(requirements),
|
||||||
|
customPythonWidgets: Array.from(customPythonWidgets),
|
||||||
mainVariable
|
mainVariable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,9 +121,13 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
|||||||
|
|
||||||
const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs.current, "", "")
|
const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs.current, "", "")
|
||||||
|
|
||||||
const {code: codeLines, imports, requirements, mainVariable} = generatedObject
|
const {code: codeLines, imports, requirements, mainVariable, customPythonWidgets} = generatedObject
|
||||||
|
|
||||||
|
console.log("custom python widgets: ", customPythonWidgets)
|
||||||
|
|
||||||
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
||||||
|
|
||||||
|
// TODO: import customWidgets
|
||||||
const code = [
|
const code = [
|
||||||
"# This code is generated by PyUIbuilder: https://pyuibuilder.com",
|
"# This code is generated by PyUIbuilder: https://pyuibuilder.com",
|
||||||
"\n\n",
|
"\n\n",
|
||||||
@@ -147,6 +159,32 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let customWidget of customPythonWidgets){
|
||||||
|
|
||||||
|
let [fileName, extension] = customWidget.split(".")
|
||||||
|
|
||||||
|
if (!extension){
|
||||||
|
fileName = `${fileName}.py`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fileContent = pythonFiles(`./${fileName}`).default
|
||||||
|
|
||||||
|
createFileList.push({
|
||||||
|
fileData: new Blob([fileContent], { type: "text/plain" }),
|
||||||
|
fileName: fileName,
|
||||||
|
folder: "customWidgets"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customPythonWidgets.length > 0){
|
||||||
|
createFileList.push({
|
||||||
|
fileData: new Blob([''], { type: "text/plain" }),
|
||||||
|
fileName: '__init__.py',
|
||||||
|
folder: "customWidgets"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for (let asset of assetFiles){
|
for (let asset of assetFiles){
|
||||||
|
|
||||||
if (asset.fileType === "image"){
|
if (asset.fileType === "image"){
|
||||||
|
|||||||
92
src/frameworks/tkinter/pythonWidgets/imageLabel.py
Normal file
92
src/frameworks/tkinter/pythonWidgets/imageLabel.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Author: Paul: https://github.com/PaulleDemon
|
||||||
|
# Made using PyUibuilder: https://pyuibuilder.com
|
||||||
|
# MIT License - keep the copy of this license
|
||||||
|
|
||||||
|
# By default Label grows to fit the image, which isn't ideal for many use cases (image should grow to fit/cover the image instead)
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
|
|
||||||
|
class ImageLabel(tk.Label):
|
||||||
|
def __init__(self, master, image_path=None, mode="cover", width=100, height=100, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
mode:
|
||||||
|
- "fit" -> Keeps aspect ratio, fits inside label
|
||||||
|
- "cover" -> Covers label fully, cropping excess
|
||||||
|
"""
|
||||||
|
super().__init__(master, width=width, height=height, *args, **kwargs)
|
||||||
|
self.parent = master
|
||||||
|
self.image_path = image_path
|
||||||
|
self.mode = mode
|
||||||
|
self.original_image = None
|
||||||
|
self.photo = None
|
||||||
|
self.resize_job = None # Debounce job reference
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def on_resize(self, event=None):
|
||||||
|
"""Debounce resizing to prevent rapid execution."""
|
||||||
|
if self.resize_job:
|
||||||
|
self.after_cancel(self.resize_job)
|
||||||
|
self.resize_job = self.after(1, self.force_resize) # Debounce
|
||||||
|
|
||||||
|
def force_resize(self):
|
||||||
|
"""Resize image using actual widget size."""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
aspect_ratio = self.original_image.width / self.original_image.height
|
||||||
|
|
||||||
|
if self.mode == "fit":
|
||||||
|
if width / height > aspect_ratio:
|
||||||
|
new_width = int(height * aspect_ratio)
|
||||||
|
new_height = height
|
||||||
|
else:
|
||||||
|
new_width = width
|
||||||
|
new_height = int(width / aspect_ratio)
|
||||||
|
resized = self.original_image.resize((new_width, new_height), Image.LANCZOS)
|
||||||
|
|
||||||
|
elif self.mode == "cover":
|
||||||
|
if width / height > aspect_ratio:
|
||||||
|
new_width = width
|
||||||
|
new_height = int(width / aspect_ratio)
|
||||||
|
else:
|
||||||
|
new_width = int(height * aspect_ratio)
|
||||||
|
new_height = height
|
||||||
|
|
||||||
|
resized = self.original_image.resize((new_width, new_height), Image.LANCZOS)
|
||||||
|
|
||||||
|
# Crop excess
|
||||||
|
left = (new_width - width) // 2
|
||||||
|
top = (new_height - height) // 2
|
||||||
|
right = left + width
|
||||||
|
bottom = top + height
|
||||||
|
resized = resized.crop((left, top, right, bottom))
|
||||||
|
|
||||||
|
# Update image
|
||||||
|
self.photo = ImageTk.PhotoImage(resized)
|
||||||
|
self.config(image=self.photo)
|
||||||
1
src/frameworks/tkinter/pythonWidgets/readme.md
Normal file
1
src/frameworks/tkinter/pythonWidgets/readme.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# contains python coded custom widgets
|
||||||
@@ -59,13 +59,34 @@ export class TkinterBase extends Widget {
|
|||||||
const absolutePositioning = this.getAttrValue("positioning")
|
const absolutePositioning = this.getAttrValue("positioning")
|
||||||
|
|
||||||
let layoutManager = `pack()`
|
let layoutManager = `pack()`
|
||||||
|
const marginX = this.getAttrValue("margin.marginX")
|
||||||
|
const marginY = this.getAttrValue("margin.marginY")
|
||||||
|
|
||||||
|
const paddingX = this.getAttrValue("padding.padX")
|
||||||
|
const paddingY = this.getAttrValue("padding.padY")
|
||||||
|
|
||||||
|
let config = {}
|
||||||
|
|
||||||
|
if (marginX){
|
||||||
|
config["padx"] = marginX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marginY){
|
||||||
|
config["pady"] = marginY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddingX){
|
||||||
|
config["ipadx"] = paddingX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddingY){
|
||||||
|
config["ipady"] = paddingY
|
||||||
|
}
|
||||||
|
|
||||||
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||||
|
|
||||||
const config = {
|
config['x'] = Math.trunc(this.state.pos.x)
|
||||||
x: Math.trunc(this.state.pos.x),
|
config['y'] = Math.trunc(this.state.pos.y)
|
||||||
y: Math.trunc(this.state.pos.y),
|
|
||||||
}
|
|
||||||
|
|
||||||
config["width"] = Math.trunc(this.state.size.width)
|
config["width"] = Math.trunc(this.state.size.width)
|
||||||
config["height"] = Math.trunc(this.state.size.height)
|
config["height"] = Math.trunc(this.state.size.height)
|
||||||
@@ -85,14 +106,6 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
const packSide = this.getAttrValue("flexManager.side")
|
const packSide = this.getAttrValue("flexManager.side")
|
||||||
|
|
||||||
const marginX = this.getAttrValue("margin.marginX")
|
|
||||||
const marginY = this.getAttrValue("margin.marginY")
|
|
||||||
|
|
||||||
const paddingX = this.getAttrValue("padding.padX")
|
|
||||||
const paddingY = this.getAttrValue("padding.padY")
|
|
||||||
|
|
||||||
const config = {}
|
|
||||||
|
|
||||||
if (packSide === "" || packSide === "top"){
|
if (packSide === "" || packSide === "top"){
|
||||||
|
|
||||||
config['side'] = `tk.TOP`
|
config['side'] = `tk.TOP`
|
||||||
@@ -114,21 +127,6 @@ export class TkinterBase extends Widget {
|
|||||||
// config["pady"] = gap
|
// config["pady"] = gap
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (marginX){
|
|
||||||
config["padx"] = marginX
|
|
||||||
}
|
|
||||||
|
|
||||||
if (marginY){
|
|
||||||
config["pady"] = marginY
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paddingX){
|
|
||||||
config["ipadx"] = paddingX
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paddingY){
|
|
||||||
config["ipady"] = paddingY
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (align === "start"){
|
// if (align === "start"){
|
||||||
// config["anchor"] = "'nw'"
|
// config["anchor"] = "'nw'"
|
||||||
@@ -168,10 +166,8 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
const sticky = this.getAttrValue("gridManager.sticky")
|
const sticky = this.getAttrValue("gridManager.sticky")
|
||||||
|
|
||||||
const config = {
|
config['row'] = row - 1 // unlike css grid tkinter grid starts from 0
|
||||||
row: row-1, // unlike css grid tkinter grid starts from 0
|
config['column'] = column - 1 // unlike css grid tkinter grid starts from 0
|
||||||
column: column-1, // unlike css grid tkinter grid starts from 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowSpan > 1){
|
if (rowSpan > 1){
|
||||||
config['rowspan'] = rowSpan
|
config['rowspan'] = rowSpan
|
||||||
@@ -562,6 +558,7 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
// console.log("rerendering:", side, expand);
|
// console.log("rerendering:", side, expand);
|
||||||
|
|
||||||
|
|
||||||
const directionMap = {
|
const directionMap = {
|
||||||
top: "column",
|
top: "column",
|
||||||
bottom: "column-reverse",
|
bottom: "column-reverse",
|
||||||
@@ -570,16 +567,28 @@ export class TkinterBase extends Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentWidgetDirection = directionMap[side] || "column"; // Default to "column"
|
const currentWidgetDirection = directionMap[side] || "column"; // Default to "column"
|
||||||
const isSameSide = lastSide === side;
|
|
||||||
const isVertical = ["top", "bottom"].includes(side);
|
const isVertical = ["top", "bottom"].includes(side);
|
||||||
|
|
||||||
let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1;
|
const isSameSide = lastSide === side
|
||||||
|
const isOppositeSide = ((lastSide === "top" && side === "bottom") || (lastSide === "left" && side === "right"))
|
||||||
|
|
||||||
if ((expand && previousExpandValue === 0) || (expand && expandValue === 0)){
|
const isDiagonal = (!isSameSide && !isOppositeSide && lastSide !== "") // bottom and right, top and left
|
||||||
|
|
||||||
|
let expandValue = expand ? ((isSameSide || isOppositeSide) ? previousExpandValue : (widgets.length - index) + 1) : (previousExpandValue > 0) ? 0 : 1
|
||||||
|
|
||||||
|
if (expand && expandValue === 0){
|
||||||
expandValue = 1 // if there is expand it should expand
|
expandValue = 1 // if there is expand it should expand
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expand && !isSameSide) previousExpandValue = expandValue;
|
if (expand && isDiagonal){
|
||||||
|
expandValue = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: if the child widget as fillx or y use flex grow
|
||||||
|
|
||||||
|
if ((expand && !isSameSide) || (expand && previousExpandValue === 0)){
|
||||||
|
previousExpandValue = expandValue;
|
||||||
|
}
|
||||||
|
|
||||||
lastSide = side; // Update last side for recursion
|
lastSide = side; // Update last side for recursion
|
||||||
|
|
||||||
@@ -598,6 +607,9 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
|
|
||||||
const stretchClass = isVertical ? "tw-flex-grow" : "tw-h-full"; // Allow only horizontal growth for top/bottom
|
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) {
|
if (isSameSide) {
|
||||||
return (
|
return (
|
||||||
@@ -627,11 +639,12 @@ export class TkinterBase extends Widget {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-pack-container={side}
|
data-pack-container={side}
|
||||||
className={`tw-flex ${isVertical ? "!tw-h-full" : "!tw-w-full"}`}
|
className={`tw-flex tw-flex-auto`}
|
||||||
|
// className={`tw-flex ${isVertical ? "!tw-h-full" : "!tw-w-full"}`}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: currentWidgetDirection,
|
flexDirection: currentWidgetDirection,
|
||||||
flexGrow: expand ? expandValue : 1, //((widgets.length - 1) === index) ? 1 : 0, // last index will always have flex-grow 1
|
flexGrow: expandValue, //((widgets.length - 1) === index) ? 1 : 0, // last index will always have flex-grow 1
|
||||||
flexShrink: expand ? 0 : 1,
|
flexShrink: expand ? 0 : 1,
|
||||||
flexBasis: "auto",
|
flexBasis: "auto",
|
||||||
minWidth: isVertical ? "0" : "auto",
|
minWidth: isVertical ? "0" : "auto",
|
||||||
@@ -1212,23 +1225,23 @@ export class TkinterWidgetBase extends TkinterBase{
|
|||||||
if (this.getAttrValue("cursor"))
|
if (this.getAttrValue("cursor"))
|
||||||
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
||||||
|
|
||||||
if (this.getAttrValue("padding.padX")){
|
// if (this.getAttrValue("padding.padX")){
|
||||||
// inner padding
|
// // inner padding
|
||||||
config["ipadx"] = this.getAttrValue("padding.padX")
|
// config["ipadx"] = this.getAttrValue("padding.padX")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.getAttrValue("padding.padY")){
|
// if (this.getAttrValue("padding.padY")){
|
||||||
config["ipady"] = this.getAttrValue("padding.padY")
|
// config["ipady"] = this.getAttrValue("padding.padY")
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
if (this.getAttrValue("margin.marginX")){
|
// if (this.getAttrValue("margin.marginX")){
|
||||||
config["padx"] = this.getAttrValue("margin.marginX")
|
// config["padx"] = this.getAttrValue("margin.marginX")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.getAttrValue("margin.marginY")){
|
// if (this.getAttrValue("margin.marginY")){
|
||||||
config["pady"] = this.getAttrValue("margin.marginY")
|
// config["pady"] = this.getAttrValue("margin.marginY")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in label and grid not pixels
|
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in label and grid not pixels
|
||||||
// if (!this.state.fitContent.width){
|
// if (!this.state.fitContent.width){
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import { convertObjectToKeyValueString } from "../../../utils/common"
|
import { convertObjectToKeyValueString } from "../../../utils/common"
|
||||||
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
@@ -10,6 +12,7 @@ class Label extends TkinterWidgetBase{
|
|||||||
static widgetType = "label"
|
static widgetType = "label"
|
||||||
static displayName = "Label"
|
static displayName = "Label"
|
||||||
|
|
||||||
|
// static requiredCustomPyFiles = ["imageLabel"]
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -55,6 +58,34 @@ class Label extends TkinterWidgetBase{
|
|||||||
value: "",
|
value: "",
|
||||||
onChange: (value) => this.setAttrValue("imageUpload", value)
|
onChange: (value) => this.setAttrValue("imageUpload", value)
|
||||||
},
|
},
|
||||||
|
imageSize: {
|
||||||
|
label: "Image size",
|
||||||
|
display: "horizontal",
|
||||||
|
// width: {
|
||||||
|
// label: "Width",
|
||||||
|
// tool: Tools.NUMBER_INPUT, // the tool to display, can be either HTML ELement or a constant string
|
||||||
|
// toolProps: { placeholder: "width", max: 3000, min: 1 },
|
||||||
|
// value: 150,
|
||||||
|
// onChange: (value) => this.setWidgetSize(value, null)
|
||||||
|
// },
|
||||||
|
// height: {
|
||||||
|
// label: "Height",
|
||||||
|
// tool: Tools.NUMBER_INPUT,
|
||||||
|
// toolProps: { placeholder: "height", max: 3000, min: 1 },
|
||||||
|
// value: 150,
|
||||||
|
// onChange: (value) => this.setWidgetSize(null, value)
|
||||||
|
// },
|
||||||
|
mode: {
|
||||||
|
label: "Image mode",
|
||||||
|
tool: Tools.SELECT_DROPDOWN,
|
||||||
|
options: ["fit", "cover"].map((val) => ({value: val, label: val})),
|
||||||
|
value: "cover",
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setAttrValue("imageSize.mode", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +104,20 @@ class Label extends TkinterWidgetBase{
|
|||||||
const imports = super.getImports()
|
const imports = super.getImports()
|
||||||
|
|
||||||
if (this.getAttrValue("imageUpload"))
|
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
|
return imports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRequiredCustomPyFiles(){
|
||||||
|
const requiredCustomFiles = super.getRequiredCustomPyFiles()
|
||||||
|
|
||||||
|
if (this.getAttrValue("imageUpload"))
|
||||||
|
requiredCustomFiles.push("imageLabel")
|
||||||
|
|
||||||
|
return requiredCustomFiles
|
||||||
|
}
|
||||||
|
|
||||||
getRequirements(){
|
getRequirements(){
|
||||||
const requirements = super.getRequirements()
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
@@ -92,10 +132,28 @@ class Label extends TkinterWidgetBase{
|
|||||||
const config = super.getConfigCode()
|
const config = super.getConfigCode()
|
||||||
|
|
||||||
const anchor = this.getAttrValue("styling.anchor")
|
const anchor = this.getAttrValue("styling.anchor")
|
||||||
|
const fitWidth = this.state.fitContent.width
|
||||||
|
const fitHeight = this.state.fitContent.height
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const {layout} = this.getParentLayout()
|
||||||
|
|
||||||
if (anchor)
|
if (anchor)
|
||||||
config['anchor'] = `"${anchor}"`
|
config['anchor'] = `"${anchor}"`
|
||||||
|
|
||||||
|
// LABEL width and height are not pixel based instead its character based
|
||||||
|
// if (layout !== Layouts.PLACE){
|
||||||
|
// if (!fitWidth){
|
||||||
|
// config['width'] = width
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!fitHeight){
|
||||||
|
// config['height'] = height
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +170,10 @@ class Label extends TkinterWidgetBase{
|
|||||||
const code = []
|
const code = []
|
||||||
|
|
||||||
if (image?.name){
|
if (image?.name){
|
||||||
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
// code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||||
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
// code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
// code.push("\n")
|
// 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")
|
// code.push("\n")
|
||||||
@@ -155,29 +213,46 @@ class Label extends TkinterWidgetBase{
|
|||||||
center: { justifyContent: 'center', alignItems: 'center' }
|
center: { justifyContent: 'center', alignItems: 'center' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return anchorStyles[anchor] || anchorStyles["w"];
|
return anchorStyles[anchor] || anchorStyles["center"];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
|
//FIXME: label image causing issues
|
||||||
const image = this.getAttrValue("imageUpload")
|
const image = this.getAttrValue("imageUpload")
|
||||||
|
const imageMode = this.getAttrValue("imageSize.mode") || "cover"
|
||||||
|
|
||||||
|
const imgClassName = imageMode === "fit" ? "tw-object-contain" : (imageMode === "cover" ? "tw-object-cover" : "")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-flex-col tw-w-full tw-relative tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
style={{
|
// style={{
|
||||||
flexGrow: 1, // Ensure the content grows to fill the parent
|
// flexGrow: 1, // Ensure the content grows to fill the parent
|
||||||
minWidth: '100%', // Force the width to 100% of the parent
|
// minWidth: '100%', // Force the width to 100% of the parent
|
||||||
minHeight: '100%', // Force the height to 100% of the parent
|
// minHeight: '100%', // Force the height to 100% of the parent
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center "
|
<div className="tw-p-2 tw-w-full tw-h-full tw-overflow-hidden tw-flex tw-place-content-center tw-place-items-center"
|
||||||
ref={this.styleAreaRef}
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{/* {this.props.children} */}
|
{/* {this.props.children} */}
|
||||||
{
|
{
|
||||||
image && (
|
image ? (
|
||||||
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
<div className="tw-relative tw-w-full tw-h-full tw-overflow-hidden">
|
||||||
)
|
<img src={image.previewUrl}
|
||||||
|
className={`${imgClassName}`}
|
||||||
|
alt={this.getAttrValue("widgetName")}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
|
||||||
|
}}
|
||||||
|
// className="tw-object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
<div className={`tw-flex ${!image ? "tw-w-full tw-h-full" : ""}`} style={{
|
<div className={`tw-flex ${!image ? "tw-w-full tw-h-full" : ""}`} style={{
|
||||||
color: this.getAttrValue("styling.foregroundColor"),
|
color: this.getAttrValue("styling.foregroundColor"),
|
||||||
@@ -193,4 +268,44 @@ class Label extends TkinterWidgetBase{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function LabelImage({imageSrc, alt, styleAreaRef}){
|
||||||
|
|
||||||
|
const [size, setSize] = useState({
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!styleAreaRef.current) return;
|
||||||
|
|
||||||
|
// Function to update size
|
||||||
|
const updateSize = () => {
|
||||||
|
const boundingBox = styleAreaRef.current.getBoundingClientRect();
|
||||||
|
setSize({ width: boundingBox.width, height: boundingBox.height });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial size update
|
||||||
|
updateSize();
|
||||||
|
|
||||||
|
// Observe size changes
|
||||||
|
const resizeObserver = new ResizeObserver(updateSize);
|
||||||
|
resizeObserver.observe(styleAreaRef.current);
|
||||||
|
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
|
}, [styleAreaRef])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img src={imageSrc} alt={alt} className="tw-object-cover"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
width: "100%", // Prevent overflow
|
||||||
|
height: "100%", // Prevent overflow
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Label
|
export default Label
|
||||||
@@ -14,6 +14,9 @@ import "./styles/index.css";
|
|||||||
|
|
||||||
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
||||||
|
|
||||||
|
window.React = React
|
||||||
|
|
||||||
|
|
||||||
const originalSetItem = localStorage.setItem;
|
const originalSetItem = localStorage.setItem;
|
||||||
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
||||||
localStorage.setItem = function (key, value) {
|
localStorage.setItem = function (key, value) {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ function Premium({ children, className = "" }) {
|
|||||||
>
|
>
|
||||||
more.
|
more.
|
||||||
</a>
|
</a>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Premium features will start rolling out phase wise from mid of April, after which there would be a price increase.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
110
webpack.config.js
Normal file
110
webpack.config.js
Normal file
@@ -0,0 +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 = {
|
||||||
|
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