added file upload tool and fileUploadProvider

This commit is contained in:
paul
2024-09-27 11:42:34 +05:30
parent 5be078f846
commit 7b4446d9ee
14 changed files with 205 additions and 70 deletions

View File

@@ -16,6 +16,7 @@ import PluginsContainer from './sidebar/pluginsContainer'
import TkinterPluginWidgets from './frameworks/tkinter/sidebarPlugins'
import FrameWorks from './constants/frameworks'
import generateTkinterCode from './frameworks/tkinter/engine/code'
import { FileUploadProvider, useFileUploadContext } from './contexts/fileUploadContext'
function App() {
@@ -28,18 +29,18 @@ function App() {
const [projectName, setProjectName] = useState('untitled project')
const [UIFramework, setUIFramework] = useState(FrameWorks.TKINTER)
const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
// const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
const [sidebarWidgets, setSidebarWidgets] = useState(TkinterWidgets || [])
// NOTE: the below reference is no longer required
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
const [canvasWidgetRefs, setCanvasWidgetRefs] = useState([])
const sidebarTabs = [
{
name: "Widgets",
icon: <LayoutFilled />,
content: <WidgetsContainer sidebarContent={TkinterWidgets} onWidgetsUpdate={(widgets) => setSidebarWidgets(widgets)}/>
content: <WidgetsContainer sidebarContent={sidebarWidgets} onWidgetsUpdate={(widgets) => setSidebarWidgets(widgets)}/>
},
{
name: "Plugins",
@@ -49,8 +50,7 @@ function App() {
{
name: "Uploads",
icon: <CloudUploadOutlined />,
content: <UploadsContainer assets={uploadedAssets}
onAssetUploadChange={(assets) => setUploadedAssets(assets)}/>
content: <UploadsContainer />
}
]
@@ -160,18 +160,16 @@ function App() {
<p>Are you sure you want to change the framework? This will clear the canvas.</p>
</Modal> */}
<DragProvider>
<div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={sidebarTabs}/>
{/* <ActiveWidgetProvider> */}
<Canvas ref={canvasRef} widgets={canvasWidgets} onWidgetAdded={handleWidgetAddedToCanvas}/>
{/* </ActiveWidgetProvider> */}
</div>
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
</DragProvider>
<DragProvider>
<div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={sidebarTabs}/>
{/* <ActiveWidgetProvider> */}
<Canvas ref={canvasRef} widgets={canvasWidgets} onWidgetAdded={handleWidgetAddedToCanvas}/>
{/* </ActiveWidgetProvider> */}
</div>
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
</DragProvider>
</div>
)
}

View File

@@ -3,7 +3,7 @@ import React, { createContext, Component, useContext, useState, createRef, useMe
// NOTE: using this context provider causes many re-rendering when the canvas is panned the toolbar updates unnecessarily
// use draggable widgetcontext
// Create the Context
export const ActiveWidgetContext = createContext()

View File

@@ -9,7 +9,8 @@ const Tools = {
INPUT_LIST: "input_list",
INPUT_RADIO_LIST: "input_radio_list",
LAYOUT_MANAGER: "layout_manager"
LAYOUT_MANAGER: "layout_manager",
UPLOADED_LIST: "uploaded_list",
}

View File

@@ -2,7 +2,7 @@ import React, { createContext, Component } from 'react'
const WidgetContext = createContext()
// NOTE: Don't use context provider
// NOTE: Don't use this context provider
class WidgetProvider extends Component {
state = {

View File

@@ -2,7 +2,10 @@ import Cursor from "./constants/cursor"
import Widget from "./widgets/base"
import { useEffect, useState } from "react"
// FIXME: when using this if the widhet has invisible swappable area, this won't work
// NOTE: Not in use
// FIXME: when using this if the widget has invisible swappable area, this won't work
/**
*
* @param {Widget} - selectedWidget

View File

@@ -1,4 +1,4 @@
import { memo, useEffect, useState } from "react"
import { memo, useEffect, useMemo, useState } from "react"
import {
Checkbox, ColorPicker, Input,
@@ -10,6 +10,8 @@ import Tools from "./constants/tools.js"
import { useActiveWidget } from "./activeWidgetContext.js"
import { Layouts } from "./constants/layouts.js"
import { DynamicRadioInputList } from "../components/inputs.js"
import { useFileUploadContext } from "../contexts/fileUploadContext.js"
import { AudioOutlined, FileImageOutlined, FileTextOutlined, VideoCameraOutlined } from "@ant-design/icons"
// FIXME: Maximum recursion error
@@ -28,6 +30,56 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
const [toolbarOpen, setToolbarOpen] = useState(isOpen)
const [toolbarAttrs, setToolbarAttrs] = useState(attrs)
const {uploadedAssets} = useFileUploadContext()
const uploadItems = useMemo(() => {
const returnComponentBasedOnFileType = (file) => {
if (file.fileType === "image"){
return (
<div className="tw-w-flex tw-gap-2">
<FileImageOutlined />
<span className="tw-ml-1">{file.name}</span>
</div>
)
}else if (file.fileType === "video"){
return (
<div className="tw-w-flex tw-gap-2">
<VideoCameraOutlined />
<span className="tw-ml-1">{file.name}</span>
</div>
)
}else if (file.fileType === "audio"){
return (
<div className="tw-w-flex tw-gap-2">
<AudioOutlined />
<span className="tw-ml-1">{file.name}</span>
</div>
)
}else{
return (
<div className="tw-w-flex tw-gap-2">
<FileTextOutlined />
<span className="tw-ml-1">{file.name}</span>
</div>
)
}
}
const uploadList = uploadedAssets.map((file, idx) => ({
value: file.name,
label: returnComponentBasedOnFileType(file),
fileType: file.fileType,
type: file.type,
// previewUrl: file.previewUrl,
}))
return uploadList
}, [uploadedAssets])
useEffect(() => {
setToolbarOpen(isOpen)
@@ -45,6 +97,33 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
}
const renderUploadDropDown = (val, filter) => {
let uploadOptions = [...uploadItems]
if (filter){
uploadOptions = uploadOptions.filter((value, idx) => filter.includes(value.type))
}
return (
<div className="tw-flex tw-w-full">
<Select
options={uploadOptions}
size="large"
placeholder="select content"
showSearch
className="tw-w-full"
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
/>
</div>
)
}
const renderLayoutManager = (val) => {
return (
@@ -198,6 +277,11 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
renderLayoutManager(val)
)
}
{
val.tool === Tools.UPLOADED_LIST && (
renderUploadDropDown(val.value, val?.toolProps?.filterOptions || null)
)
}
</>
)

View File

@@ -344,7 +344,7 @@ class Widget extends React.Component {
}
generateCode(){
throw new NotImplementedError("Get Code must be implemented by the subclass")
throw new NotImplementedError("generateCode() must be implemented by the subclass")
}
getAttributes() {

View File

@@ -4,7 +4,7 @@ const DragWidgetContext = createContext()
export const useDragWidgetContext = () => useContext(DragWidgetContext)
// Provider component to wrap around parts of your app that need drag-and-drop functionality
// Provider component to wrap around parts that need drag-and-drop functionality
export const DragWidgetProvider = ({ children }) => {
const [draggedElement, setDraggedElement] = useState(null)

View File

@@ -2,8 +2,11 @@ import { useEffect, useMemo, useRef } from "react"
import Draggable from "./utils/draggableDnd"
import { GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, FileTextOutlined} from "@ant-design/icons"
AudioOutlined, FileTextOutlined,
DeleteFilled,
DeleteOutlined} from "@ant-design/icons"
import DraggableWrapper from "./draggable/draggable"
import { Button } from "antd"
export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerRef}){
@@ -75,7 +78,7 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
}
export function DraggableAssetCard({file}){
export function DraggableAssetCard({file, onDelete}){
const videoRef = useRef()
@@ -106,36 +109,42 @@ export function DraggableAssetCard({file}){
return (
<>
<div className="tw-w-full tw-h-[240px] tw-p-1 tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
tw-border-blue-500 tw-shadow-md ">
<div className="tw-h-[200px] tw-w-full tw-flex tw-place-content-center tw-p-1 tw-text-3xl tw-overflow-hidden">
{ file.fileType === "image" &&
<img src={file.previewUrl} alt={file.name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
}
<div draggable="false" className="tw-w-full tw-h-[240px] tw-p-1 tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
tw-border-blue-500 tw-shadow-md ">
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-flex tw-place-content-center tw-p-1 tw-text-3xl tw-overflow-hidden">
{ file.fileType === "image" &&
<img src={file.previewUrl} alt={file.name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
}
{
file.fileType === "video" &&
<video className="tw-w-full tw-object-contain" ref={videoRef} muted>
<source src={file.previewUrl} type={`${file.type || "video/mp4"}`} />
Your browser does not support the video tag.
</video>
}
{
file.fileType === "video" &&
<video className="tw-w-full tw-object-contain" ref={videoRef} muted>
<source src={file.previewUrl} type={`${file.type || "video/mp4"}`} />
Your browser does not support the video tag.
</video>
}
{
file.fileType === "audio" &&
<AudioOutlined />
}
{
file.fileType === "others" &&
<FileTextOutlined />
}
{
file.fileType === "audio" &&
<AudioOutlined />
}
{
file.fileType === "others" &&
<FileTextOutlined />
}
</div>
<span className="tw-text-sm tw-text-back">{file.name}</span>
</div>
</>
<div className="tw-flex tw-justify-between tw-gap-1 tw-p-1">
<span onDragStart={() => false} draggable="false"
className="tw-text-sm tw-text-back tw-pointer-events-none">{file.name}</span>
<div className="tw-text-red-500 tw-cursor-pointer"
onClick={() => onDelete(file)} >
<DeleteOutlined />
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,17 @@
import React, { createContext, useContext, useState } from 'react';
const FileUploadContext = createContext()
export const useFileUploadContext = () => useContext(FileUploadContext)
// Provider component to wrap around parts that needs upload context
export const FileUploadProvider = ({ children }) => {
const [uploadedAssets, setUploadedAssets] = useState([])
return (
<FileUploadContext.Provider value={{ uploadedAssets, setUploadedAssets }}>
{children}
</FileUploadContext.Provider>
)
}

View File

@@ -44,7 +44,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
let widgetCode = widgetRef.generateCode(varName, currentParentVariable)
if (!(widgetCode instanceof Array)) {
throw new Error("Generate code function should return array, each new line should be a new item")
throw new Error("generateCode() function should return array, each new line should be a new item")
}
// Add \n after every line

View File

@@ -36,12 +36,18 @@ class Label extends TkinterBase{
},
labelWidget: {
label: "Text",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
tool: Tools.INPUT,
toolProps: {placeholder: "text", maxLength: 100},
value: "Label",
onChange: (value) => this.setAttrValue("labelWidget", value)
}
},
imageUpload: {
label: "Image",
tool: Tools.UPLOADED_LIST,
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
value: "",
onChange: (value) => this.setAttrValue("imageUpload", value)
},
}
}
}
@@ -82,7 +88,7 @@ class Label extends TkinterBase{
renderContent(){
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
<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-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center " style={this.state.widgetInnerStyling}>
{/* {this.props.children} */}
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>

View File

@@ -11,6 +11,7 @@ import { QueryClient, QueryClientProvider } from "react-query";
import "./styles/tailwind.css";
import "./styles/index.css";
import { FileUploadProvider } from "./contexts/fileUploadContext";
const originalSetItem = localStorage.setItem;
// triggers itemsChaned event whenever the item in localstorage is chanegd.
@@ -67,7 +68,9 @@ root.render(
<React.StrictMode>
<Provider store={store}>
<QueryClientProvider client={queryClient} >
<App />
<FileUploadProvider>
<App />
</FileUploadProvider>
</QueryClientProvider>
</Provider>
</React.StrictMode>

View File

@@ -9,6 +9,7 @@ import { DraggableAssetCard } from "../components/cards.js"
import { filterObjectListStartingWith } from "../utils/filter"
import { getFileType } from "../utils/file.js"
import { SearchComponent } from "../components/inputs.js"
import { useFileUploadContext } from "../contexts/fileUploadContext.js"
// import { update } from "../redux/assetSlice.js"
@@ -33,7 +34,7 @@ const props = {
* DND for file uploads
*
*/
function UploadsContainer({assets, onAssetUploadChange}) {
function UploadsContainer() {
// const dispatch = useDispatch()
// const selector = useSelector(state => state.assets)
@@ -42,27 +43,38 @@ function UploadsContainer({assets, onAssetUploadChange}) {
const [dragEnter, setDragEnter] = useState(false)
const [searchValue, setSearchValue] = useState("")
const [uploadData, setUploadData] = useState(assets) // contains all the uploaded data
const [uploadResults, setUploadResults] = useState(assets) // sets search results
// const [uploadData, setUploadData] = useState(assets) // contains all the uploaded data
const {uploadedAssets, setUploadedAssets} = useFileUploadContext()
const [uploadResults, setUploadResults] = useState(uploadedAssets) // sets search results
// useEffect(() => {
// setUploadResults(uploadData)
// }, [uploadData])
useEffect(() => {
setUploadResults(uploadData)
}, [uploadData])
setUploadResults(uploadedAssets)
}, [uploadedAssets])
useEffect(() => {
if (searchValue.length > 0) {
const searchData = filterObjectListStartingWith(uploadData, "name", searchValue)
const searchData = filterObjectListStartingWith(uploadedAssets, "name", searchValue)
setUploadResults(searchData)
} else {
setUploadResults(uploadData)
setUploadResults(uploadedAssets)
}
}, [searchValue])
function onSearch(event) {
setSearchValue(event.target.value)
}
function handleDelete(file){
// remove the file from the asset on delete
setUploadedAssets(prev => prev.filter(val => val.uid !== file.uid))
}
@@ -101,11 +113,12 @@ function UploadsContainer({assets, onAssetUploadChange}) {
const newFileData = {
...info.file,
previewUrl,
fileType
fileType,
}
// dispatch(update([newFileData, ...uploadData]))
setUploadData(prev => [newFileData, ...prev])
onAssetUploadChange([newFileData, ...uploadData])
// setUploadData(prev => [newFileData, ...prev])
setUploadedAssets(prev => [newFileData, ...prev])
// onAssetUploadChange([newFileData, ...uploadData])
setDragEnter(false)
message.success(`${info.file.name} file uploaded successfully.`)
@@ -126,6 +139,7 @@ function UploadsContainer({assets, onAssetUploadChange}) {
return (
<DraggableAssetCard key={file.uid}
file={file}
onDelete={handleDelete}
/>
)