paint-brush
Así es como construí un generador de interfaz de usuario similar a Webflow para Pythonpor@paulfreeman
Nueva Historia

Así es como construí un generador de interfaz de usuario similar a Webflow para Python

por Paul10m2024/10/05
Read on Terminal Reader

Demasiado Largo; Para Leer

Comparto mi proceso de pensamiento y experiencia en la creación de un generador de interfaz de usuario de arrastrar y soltar para Python
featured image - Así es como construí un generador de interfaz de usuario similar a Webflow para Python
Paul HackerNoon profile picture
0-item
1-item


He estado trabajando en un generador de arrastrar y soltar para Python durante las últimas semanas.


Puedes comprobarlo en Constructor de PyUI

Código fuente: https://github.com/PaulleDemon/PyUIBuilder


¿Qué puede hacer el constructor?

En resumen, puede ayudarlo a crear rápidamente una interfaz de usuario para Python y generar código de interfaz de usuario en múltiples bibliotecas/marcos, incluidos Tkinter y customtkinter. Puede leer más en Sección de características


Pero no solo quiero lanzar un proyecto, también me gustaría compartir mi experiencia con ustedes. En este blog, repasaré mi proceso de pensamiento y una descripción general de alto nivel de cómo creé la aplicación.

Se me ocurrió la idea.

Contrariamente a la creencia popular, Python se utiliza a menudo para crear aplicaciones rápidas, es especialmente popular entre los desarrolladores que trabajan en ciencia de datos, automatización, tareas de scripting, etc. Muchas herramientas internas y GUI, particularmente en entornos científicos y de investigación, se crean con Python debido a su simplicidad y la disponibilidad de marcos como Tkinter, PyQt y otros.

idea

Ahora bien, había muchos constructores de arrastrar y soltar para la web, pero muy pocos para las GUI de Python, especialmente para tkinter. Vi algunos, pero el problema era que tenían una cantidad muy limitada de widgets o generaban código en formato XML, lo que no es ideal si estás desarrollando una IU en Python.


Entonces, al principio solo quería crear un generador de interfaz de usuario de arrastrar y soltar adecuado solo para Tkinter.


Seguí dándole vueltas a la idea de un generador de GUI ideal (sin juego de palabras). Me inspiré en la interfaz de usuario de Canva y se me ocurrieron algunas características que harían que mi GUI fuera ideal.


  1. Todos los widgets deberían crearse como complementos.
  2. Debería admitir widgets de UI de terceros en forma de complementos.
  3. Debería poder cargar activos como imágenes, vídeos, etc.
  4. Debería generar código en Python.

Entonces, a finales de julio, decidí comenzar a trabajar en el proyecto.

Ampliando la idea

Al principio se llamó tkbuilder, lo que indica que es un generador de GUI para la biblioteca de interfaz de usuario de Tkinter.


Pero, si te diste cuenta, también puedo ampliar la misma idea para soportar múltiples bibliotecas y marcos de GUI de Python, ya que todo está hecho como un complemento y eso es exactamente lo que planeé hacer.

Planificación de la versión inicial.

Para la versión inicial, no quería agregar demasiadas funciones que abrumaran a los usuarios. Quería crearla en función de los comentarios de las personas que la usaban. De esta manera, no pierdo el tiempo creando cosas que la gente no quiere.


Desde el principio decidí no tener un backend ni ningún otro formulario de registro. De esta manera, es mucho más sencillo para mí desarrollarlo y para los usuarios que lo usan. Solo quería un frontend simple con el que la gente pudiera comenzar a trabajar.

Elegir lenguaje JS, TS o Python

elegir un idioma

Sí, esto es algo en lo que estuve pensando durante bastante tiempo. La mayoría de los constructores de GUI para Python que existen se crearon con Python. Mi primera opción para Python fue PySide.


La aplicación basada en GUI más compleja que creé usando PyQt/Pyside fue una editor basado en nodos Hace unos años.


Pero rápidamente me di cuenta de las limitaciones de usar Python para construir la versión inicial.


  • Las bibliotecas de UI de Python no tienen muchos widgets de terceros que me ayuden a crear rápidamente la versión inicial.
  • No es fácil distribuir aplicaciones Python como archivos exe, mientras que al usar JS podemos distribuirlas en forma de una aplicación electrónica.
  • La mayoría de las personas prefieren utilizar la web en lugar de descargar un ejecutable de un sitio web desconocido.


Typescript también era una opción, pero con Typescript siempre sentí que era demasiado verboso.


Éstas fueron las únicas cosas que noté inmediatamente, por lo que mi primera opción fue usar JS.


PD: Más tarde me arrepentí de no haber empezado con TS, pero esa será una historia para otro momento.

Con marco o sin marco.

La biblioteca tipo framework con la que me siento más cómodo es React.js, pero crear una abstracción requeriría usar clases, lo que no se recomienda desde la introducción de los ganchos.


El problema de no usar un marco era que tendría que construir todo yo mismo y no tendría acceso a las vastas bibliotecas de componentes que React tiene para ofrecer.


Ambos tenían sus desventajas, pero las clases de React aún se pueden usar, por lo que para mí se convirtió en la opción obvia.

Un comienzo accidentado

Comencé construyendo la base y la barra lateral a principios de agosto y tuve que parar por falta de fondos, así que me puse a trabajar en el proyecto de un cliente, que lamentablemente no me pagó el monto final. Probé con la financiación colectiva, pero tampoco tuve suerte.


Así que, en el mes de septiembre, con los pocos fondos que me quedaban, decidí lanzarme de lleno a este proyecto. Alrededor del 9 de septiembre, retomé el trabajo.

Planificando con anticipación...

planificando con anticipación

Se dedicó mucho tiempo a pensar en la abstracción base, que se puede ampliar para satisfacer las necesidades.


  1. Quería tener un lienzo que se pudiera ampliar y mover de forma similar a Figma.

  2. Un widget base desde el cual pueden extenderse todos los demás widgets.

  3. Una función de arrastrar y soltar para arrastrar y soltar elementos de la interfaz de usuario en el lienzo.


Para construir con React, es necesario pensarlo y construirlo de una determinada manera. A pesar de la discusión sobre si es una biblioteca o un marco, siempre se siente más como un marco que como una biblioteca.

Diseño de interfaz de usuario

Siempre me gustó cómo Canva construyó su barra lateral, quería tener algo similar para mi generador de arrastrar y soltar.

Dibujé lo que tenía en mente en un papel. No soy el mejor artista del mundo 🙄

Diseño de interfaz de usuario

Mi proceso de pensamiento con respecto a la interacción del lienzo y el widget.

planificando con anticipación...

Entonces, ¿quién debería encargarse de arrastrar, redimensionar y seleccionar? ¿El lienzo o el widget base? ¿Cómo se manejarán los widgets dentro del widget?


¿El widget base conocerá a sus elementos secundarios o se gestionará con una única estructura de datos mediante el propio lienzo? ¿Cómo representaré los elementos secundarios dentro de los elementos secundarios?


¿Cómo funcionará la función de arrastrar y soltar dentro del lienzo y otros widgets?


¿Cómo se gestionarán los diseños?


Éstas fueron algunas de las preguntas que comencé a hacer antes de construir todo.


Aunque ahora la interfaz de usuario parece más sencilla, se pensó mucho en construir la base, para que parezca mucho más sencilla para los usuarios.

Enfoque basado en HTML Canvas o enfoque sin lienzo.

Enfoque basado en lienzo

Ahora HTML tiene un elemento Canvas predeterminado que permite hacer muchas cosas, como dibujar, agregar imágenes y demás. Ahora parecía un elemento ideal para usar en mi programa.


Entonces, comencé a verificar si existía una implementación de arrastrar y soltar, redimensionar, hacer zoom y desplazarse. Encontré FabricJS Esta me pareció una biblioteca fantástica para mi caso de uso.


Intenté experimentar con Fabric.Js e intenté implementar todo en Fabric.js como puedes ver aquí. implementación , pero había algo en el lienzo que no preví.


  1. Comencé a experimentar con un enfoque basado en ganchos al construir el lienzo, pero la función de eliminación de fabric.js era asincrónica, por lo que no funcionaría bien con ganchos.
  2. Canvas no puede tener elementos secundarios como Div u otros elementos que harían que sea un poco más difícil crear administradores de diseño.
  3. Depurar cualquier cosa en el lienzo es bastante difícil ya que los elementos internos del lienzo no aparecen en la inspección de elementos de las herramientas para desarrolladores.


Enfoque no basado en lienzo


Ahora, después de experimentar, el enfoque sin lienzo parecía mejor, ya que tengo acceso al administrador de diseño predeterminado provisto, además de que había muchos componentes de interfaz de usuario prediseñados disponibles que harían de esta una opción ideal al escalar.


Planeé simular un lienzo utilizando dos div diferentes: un div interno y un div contenedor externo.


Ahora, crear zoom y desplazarse era bastante fácil de implementar, ya que CSS ya tenía transformación, escala y traducción.


Primero, para implementar esto, tuve que tener un contenedor que contuviera un lienzo. Ahora, este lienzo es un elemento invisible (sin desbordamiento oculto), aquí es donde se colocan todos los elementos y se aplica el escalado y la traducción.

Recipiente

Para acercar tuve que incrementar la escala y para alejarla disminuírla.

Pruebe este sencillo ejemplo. (Tecla + para hacer zoom y - para alejar)


La panorámica funcionó de manera similar

Arrastrar y soltar

arrastrar y soltar

Al empezar, había investigado sobre un par de bibliotecas como Reacciona-hermosa-Dnd , Kit de Dnd para reaccionar y Reaccionar Swappy .


Después de investigar, vi que React-beautiful-dnd ya no se mantenía y comencé con React dnd-kit. Cuando empecé a desarrollar, descubrí que la documentación de dnd-kit era bastante limitada para lo que estaba desarrollando. Además, pronto saldría una nueva versión con cambios importantes en la biblioteca, así que decidí dejar de usar React-dnd-kit hasta la versión principal.


Reescribí las partes en las que usé DND-kit con la API de arrastrar y soltar de HTML. La única limitación con la API de arrastrar y soltar nativa era que todavía no era compatible con algunos dispositivos táctiles, lo que no me importó porque estaba creando para dispositivos no táctiles.

Una única fuente de verdad

Afrontar la verdad

Al crear una aplicación como esta, es fácil perder el control de todas las variables y los cambios, por lo que no puedo tener múltiples variables que controlen la misma información.


La información/estado de cada widget debe estar almacenada en el lienzo o en el propio widget, que luego pasa la información cuando se le solicita.


O tal vez usar una biblioteca de gestión de estado como Redux


Elegí tener toda la información sobre los widgets administrados por el componente Canvas después de experimentar diferentes enfoques.


La estructura de datos se parece a esto.

 [ { id: "", // id of the widget widgetType: WidgetClass, // base widget children: [], // children will also have the same datastructure as the parent parent: "", // id of the parent of the current widget initialData: {} // information about the widget's data that's about to be rendered eg: backgroundColor, foregroundColor etc. } ]

Gestores de contexto de React

Ahora quería cargar los recursos en la barra lateral a la que se podía acceder mediante la barra de herramientas de los widgets, pero cada vez que cambiaba de pestaña, la nueva renderización hacía que los recursos cargados desaparecieran.


Una de las mayores limitaciones de Redux es que solo se pueden almacenar datos serializables. Los datos no serializables, como imágenes, vídeos y otros recursos, no se pueden almacenar en Redux. Esto dificultaría la transferencia de datos comunes entre distintos componentes.


Una forma de solucionar este problema es usar React Context. En resumen, React Context ofrece una forma de pasar datos a través del árbol de componentes sin tener que pasar propiedades manualmente en cada nivel.


Todo lo que tendría que hacer para tener los datos en diferentes componentes sería envolverlos alrededor de un proveedor de contexto React.


Creé mis propios proveedores de contexto para dos cosas:

  1. Arrastrar y soltar: habilita la función de arrastrar y soltar desde la barra lateral + arrastrar y soltar dentro de elementos secundarios.
  2. Carga de archivos: para que los archivos cargados sean accesibles en la barra de herramientas de cada widget.


Aquí hay un ejemplo simple de cómo utilicé el contexto de React para arrastrar y soltar.

 import React, { createContext, useContext, useState } from 'react' const DragWidgetContext = createContext() export const useDragWidgetContext = () => useContext(DragWidgetContext) // Provider component to wrap around parts that need drag-and-drop functionality export const DragWidgetProvider = ({ children }) => { const [draggedElement, setDraggedElement] = useState(null) const onDragStart = (element) => { setDraggedElement(element) } const onDragEnd = () => { setDraggedElement(null) } return ( <DragWidgetContext.Provider value={{ draggedElement, onDragStart, onDragEnd }}> {children} </DragWidgetContext.Provider> ) }


¡Sí! Eso es todo. Ahora solo me quedaba envolverlo alrededor del componente donde necesitaba el contexto, que en mi caso era sobre Canvas y la barra lateral.

Generando código

Responsabilidad

Dado que cada widget se comporta de manera diferente y tiene sus propios atributos, decidí que los widgets deben ser responsables de generar su propio código y un motor de código solo manejará los conflictos de nombres de variables y de unir el código.


De esta manera, pude expandirme fácilmente para admitir muchos widgets prediseñados, así como algunos complementos de interfaz de usuario de terceros.

Poniéndose en marcha

No tenía un backend ni un registro y había muchas empresas que ofrecían alojamiento gratuito para páginas estáticas. Primero decidí utilizar Vercel, pero muchas veces he visto que el alojamiento gratuito de Vercel deja de funcionar si había demasiadas solicitudes.


Fue entonces cuando me enteré de Páginas de Cloudflares Su oferta de neumáticos gratis tenía casi todo ilimitado. Por eso, usar Cloudflare se convirtió en mi primera opción.


Las únicas desventajas fueron que los tiempos de compilación eran bastante lentos y carecían de bastante documentación.


La parte más molesta del paso de compilación fue la falla de compilación. Funcionó en Vercel, pero no en las páginas de Cloudflare. Los registros tampoco eran tan claros. Y tenemos neumáticos gratuitos, solo tenemos 500 compilaciones por mes, así que no quería desperdiciar demasiadas.


Lo intenté durante horas y luego decidí configurar la integración continua en una cadena vacía.

 CI='' npm install


Y finalmente se puso en marcha.
Vivir

¿Quieres ver cómo ha progresado a lo largo de los meses?

He estado construyendo todo esto en público. Si te interesa ver cómo ha evolucionado desde una simple barra lateral hasta un generador de arrastrar y soltar completamente desarrollado, puedes consultar el línea de tiempo aquí .


#construirenpúblico


¡Oh! No te olvides de seguirnos para recibir actualizaciones.

Repositorio de estrellas ⭐️


Si te gustó este tipo de contenido, escribiré más blogs profundizando en cómo planifico y construyo cosas. Para seguirme, puedes suscribirte a mi boletín de substack :)