added file upload tool and fileUploadProvider
This commit is contained in:
32
src/App.js
32
src/App.js
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
}
|
||||
17
src/contexts/fileUploadContext.js
Normal file
17
src/contexts/fileUploadContext.js
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")}}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user