fix: file upload

enhancement: widget styling
This commit is contained in:
paul
2024-09-24 14:33:02 +05:30
parent be17b93b02
commit 22e0a0543d
13 changed files with 205 additions and 91 deletions

View File

@@ -16,6 +16,17 @@
-->
<link rel="manifest" href="%PUBLIC_URL%/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" />
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id="></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-');
</script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

View File

@@ -841,6 +841,11 @@ class Canvas extends React.Component {
e.preventDefault()
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
const container = draggedElement.getAttribute("data-container")
const canvasRect = this.canvasRef.current.getBoundingClientRect()

View File

@@ -645,6 +645,11 @@ class Widget extends React.Component {
handleDragEnter = (e, draggedElement, setOverElement) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
const dragEleType = draggedElement.getAttribute("data-draggable-type")
// console.log("Drag entering...", dragEleType, draggedElement, this.droppableTags)
@@ -686,6 +691,12 @@ class Widget extends React.Component {
}
handleDragOver = (e, draggedElement) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
if (draggedElement === this.elementRef.current) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
@@ -706,8 +717,15 @@ class Widget extends React.Component {
}
handleDropEvent = (e, draggedElement, widgetClass = null) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
e.preventDefault()
e.stopPropagation()
// FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget
this.setState({
showDroppableStyle: {
@@ -850,6 +868,8 @@ class Widget extends React.Component {
data-draggable-type={this.getWidgetType()} // helps with droppable
data-container={this.state.widgetContainer} // indicates how the canvas should handle dragging, one is sidebar other is canvas
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
draggable={this.state.dragEnabled}
onDragOver={(e) => this.handleDragOver(e, draggedElement)}

View File

@@ -1,14 +1,12 @@
import { useEffect, useMemo, useRef } from "react"
import Draggable from "./utils/draggableDnd"
import { FileImageOutlined, GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, VideoCameraOutlined,
FileTextOutlined} from "@ant-design/icons"
import { GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, FileTextOutlined} from "@ant-design/icons"
import DraggableWrapper from "./draggable/draggable"
import { useDragContext } from "./draggable/draggableContext"
export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){
export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerRef}){
const urlIcon = useMemo(() => {
if (url){
@@ -36,16 +34,37 @@ export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] 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-[#888] ">
tw-border-blue-500 tw-shadow-md">
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-overflow-hidden">
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
</div>
<span className="tw-text-xl tw-text-center">{name}</span>
<span className="tw-text-center tw-text-black tw-text-lg">{name}</span>
<div className="tw-flex tw-text-lg tw-place tw-px-4">
<a href={url} className="tw-text-black" target="_blank" rel="noopener noreferrer">
<a href={url} className="tw-text-gray-600" target="_blank" rel="noopener noreferrer">
{urlIcon}
</a>
{license?.name &&
<div className="tw-ml-auto tw-text-sm">
{
license.url ?
<a href={license.url} target="_blank" rel="noreferrer noopener"
className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
tw-border-solid tw-rounded-sm tw-border-blue-500
tw-shadow-md tw-text-center tw-no-underline">
{license.name}
</a>
:
<div className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
tw-border-solid tw-rounded-sm tw-border-blue-500
tw-shadow-md tw-text-center">
{license.name}
</div>
}
</div>
}
</div>
</div>
@@ -87,9 +106,10 @@ export function DraggableAssetCard({file}){
return (
<Draggable className="tw-cursor-pointer">
<>
<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-[#888] ">
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" />
@@ -113,9 +133,9 @@ export function DraggableAssetCard({file}){
}
</div>
<span className="tw-text-base">{file.name}</span>
<span className="tw-text-sm tw-text-back">{file.name}</span>
</div>
</Draggable>
</>
)
}

View File

@@ -33,6 +33,7 @@ const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, className
return (
<div className={`${className}`}
draggable
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
data-draggable-type={dragElementType}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}

View File

@@ -18,6 +18,11 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
const handleDragEnter = (e) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
const dragElementType = draggedElement.getAttribute("data-draggable-type")
@@ -42,6 +47,12 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
}
const handleDragOver = (e) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragElementType = draggedElement.getAttribute("data-draggable-type")
@@ -57,6 +68,12 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
}
const handleDropEvent = (e) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
e.stopPropagation()
setShowDroppable({

View File

@@ -1,6 +1,50 @@
import React, { useEffect, useState } from "react"
import React, { useEffect, useState, useRef } from "react"
import { Input, Button, Space, Radio } from "antd"
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons'
export const SearchComponent = ({ onSearch, searchValue, onClear, ...props }) => {
const inputRef = useRef(null)
const handleOuterDivClick = () => {
inputRef.current.focus()
}
return (
<div className="tw-flex tw-gap-2 input tw-place-items-center" onClick={handleOuterDivClick}>
<SearchOutlined />
<input
type="text"
placeholder="Search"
className="tw-outline-none tw-w-full tw-border-none"
id=""
onInput={onSearch}
value={searchValue}
ref={inputRef} // Attach the ref to the input
{...props}
/>
<div className="">
{
searchValue.length > 0 &&
<div className="tw-cursor-pointer tw-text-gray-500"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
if (onClear)
onClear()
}}>
<CloseCircleFilled />
</div>
}
</div>
</div>
)
}
export const DynamicInputList = () => {

View File

@@ -9,33 +9,50 @@ import VideoPlayer from "./plugins/videoPlayer"
import MapView from "./plugins/mapview"
import PandasTable from "./plugins/pandasTable"
// TODO: add license for 3rd party plugins
const TkinterPluginWidgets = [
{
name: "Analog TimePicker",
img: ClockImage,
link: "https://github.com",
widgetClass: AnalogTimePicker
link: "https://github.com/PaulleDemon/tkTimePicker",
widgetClass: AnalogTimePicker,
license: {
name: "MIT",
url: ""
}
},
{
name: "Video Player",
img: VideoImage,
link: "https://github.com/PaulleDemon/tkVideoPlayer",
widgetClass: VideoPlayer
link: "https://github.com/PaulleDemon/tkVideoPlayer",
widgetClass: VideoPlayer,
license: {
name: "MIT",
url: ""
}
},
{
name: "Map viewer",
img: MapImage,
link: "https://github.com/TomSchimansky/TkinterMapView",
widgetClass: MapView
link: "https://github.com/TomSchimansky/TkinterMapView",
widgetClass: MapView,
license: {
name: "MIT",
url: ""
}
},
{
name: "Pandas Table",
img: DataTableImage,
link: "https://github.com/dmnfarrell/pandastable",
widgetClass: PandasTable
link: "https://github.com/dmnfarrell/pandastable",
widgetClass: PandasTable,
license: {
name: "GPL v3",
url: "https://github.com/dmnfarrell/pandastable/blob/master/LICENSE"
}
},
]

View File

@@ -8,6 +8,7 @@ import ButtonWidget from "../assets/widgets/button.png"
import { filterObjectListStartingWith } from "../utils/filter"
import Widget from "../canvas/widgets/base"
import { SearchComponent } from "../components/inputs"
/**
@@ -52,20 +53,9 @@ function PluginsContainer({sidebarContent, onWidgetsUpdate}){
return (
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
<div className="tw-flex tw-gap-2 input tw-place-items-center">
<SearchOutlined />
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
id="" onInput={onSearch} value={searchValue}/>
<div className="">
{
searchValue.length > 0 &&
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
<CloseCircleFilled />
</div>
}
</div>
</div>
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
<SearchComponent onSearch={onSearch} searchValue={searchValue}
onClear={() => setSearchValue("")} />
<div className="tw-flex tw-flex-col tw-place-items-center tw-gap-2 tw-h-full tw-p-1">
{
widgetData.map((widget, index) => {
@@ -74,6 +64,7 @@ function PluginsContainer({sidebarContent, onWidgetsUpdate}){
name={widget.name}
img={widget.img}
url={widget.link}
license={widget.license}
widgetClass={widget.widgetClass}
/>

View File

@@ -62,8 +62,8 @@ function Sidebar({tabs}){
{
sidebarTabs.map((tab, index) => {
return (
<div className={`${activeTab === index ? "tw-text-blue-400 " : "tw-text-gray-600"} tw-cursor-pointer
hover:tw-text-blue-400 tw-flex tw-flex-col tw-gap-2 tw-place-items-center`}
<div className={`${activeTab === index ? "tw-text-blue-500" : "tw-text-gray-600"}
tw-cursor-pointer hover:tw-text-blue-500 tw-flex tw-flex-col tw-gap-2 tw-place-items-center`}
key={tab.name}
onMouseEnter={() => {
openSidebar()
@@ -73,7 +73,7 @@ function Sidebar({tabs}){
setActiveTab(index)
}}
>
<div className="tw-bg-white tw-shadow-lg tw-p-2 tw-rounded-md">
<div className={`${activeTab === index && "tw-border-solid tw-border-[1px] tw-border-blue-500"} tw-bg-white tw-shadow-lg tw-p-2 tw-rounded-md`}>
{tab.icon}
</div>
<span className="tw-text-[12px] ">{tab.name}</span>

View File

@@ -8,6 +8,7 @@ import { CloseCircleFilled, InboxOutlined, SearchOutlined } from "@ant-design/ic
import { DraggableAssetCard } from "../components/cards.js"
import { filterObjectListStartingWith } from "../utils/filter"
import { getFileType } from "../utils/file.js"
import { SearchComponent } from "../components/inputs.js"
// import { update } from "../redux/assetSlice.js"
@@ -67,29 +68,18 @@ function UploadsContainer({assets, onAssetUploadChange}) {
return (
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col"
onDragEnter={() => { setDragEnter(true) }}
onDragLeave={(e) => {
// Ensure the drag leave is happening on the container and not on a child element
if (e.currentTarget.contains(e.relatedTarget)) {
return
}
setDragEnter(false)
}}
>
onDragEnter={() => { setDragEnter(true) }}
onDragLeave={(e) => {
// Ensure the drag leave is happening on the container and not on a child element
if (e.currentTarget.contains(e.relatedTarget)) {
return
}
setDragEnter(false)
}}
>
<div className="tw-flex tw-gap-2 input tw-place-items-center">
<SearchOutlined />
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
id="" onInput={onSearch} value={searchValue} />
<div className="">
{
searchValue.length > 0 &&
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
<CloseCircleFilled />
</div>
}
</div>
</div>
<SearchComponent onSearch={onSearch} searchValue={searchValue}
onClear={() => setSearchValue("")} />
<div className="tw-flex tw-relative tw-flex-col tw-gap-2 tw-h-full tw-p-1 tw-pb-4">
<Dragger className={`${dragEnter && "!tw-h-[80vh] !tw-opacity-100 !tw-bg-[#fafafa] tw-absolute tw-top-0 tw-z-10"} tw-w-full !tw-min-w-[250px]`}
{...props}

View File

@@ -8,6 +8,7 @@ import ButtonWidget from "../assets/widgets/button.png"
import { filterObjectListStartingWith } from "../utils/filter"
import Widget from "../canvas/widgets/base"
import { SearchComponent } from "../components/inputs"
/**
@@ -52,20 +53,9 @@ function WidgetsContainer({sidebarContent, onWidgetsUpdate}){
return (
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
<div className="tw-flex tw-gap-2 input tw-place-items-center">
<SearchOutlined />
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
id="" onInput={onSearch} value={searchValue}/>
<div className="">
{
searchValue.length > 0 &&
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
<CloseCircleFilled />
</div>
}
</div>
</div>
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
<SearchComponent onSearch={onSearch} searchValue={searchValue}
onClear={() => setSearchValue("")} />
<div className="tw-flex tw-flex-col tw-place-items-center tw-gap-2 tw-h-full tw-p-1">
{
widgetData.map((widget, index) => {
@@ -81,6 +71,7 @@ function WidgetsContainer({sidebarContent, onWidgetsUpdate}){
})
}
</div>
</div>
)

View File

@@ -11,30 +11,37 @@ body {
-moz-osx-font-smoothing: grayscale;
}
.dots-bg{
background-image: url("../assets/background/dots.svg");
background-repeat: no-repeat;
background-size: cover;
/* width */
::-webkit-scrollbar {
width: 8px;
}
.stripes-bg {
width: 100%;
height: 100%;
--color: #E1E1E1;
background-color: #F3F3F3;
background-image: linear-gradient(0deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent),
linear-gradient(90deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent);
background-size: 55px 55px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f151;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #b8b8b8ce;
border-radius: 10px;
}
/* Handle on hover */
/* ::-webkit-scrollbar-thumb:hover {
background: #555;
} */
.input{
border: 2px solid #e3e5e8;
border: 1px solid #e3e5e8;
padding: 2px 8px;
min-height: 40px;
border-radius: 10px;
border-radius: 5px;
outline: none;
transition: border 0.2s, box-shadow 0.2s;
}
.input:active, .input:focus, .input:focus-within{
border-color: #60a5fa;
border-color: #3285ea;
box-shadow: 0 0 5px #085db780;
}