Feat(Menu): Add tool menu via drag and drop
Drag and drop a tool card to add tool menu
This commit is contained in:
@@ -134,12 +134,7 @@ const Store = () => {
|
||||
return (
|
||||
<StoreCard
|
||||
key={firstTool!.id}
|
||||
icon={
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
||||
alt={'Icon'}
|
||||
/>
|
||||
}
|
||||
icon={firstTool!.icon}
|
||||
toolName={firstTool!.name}
|
||||
toolId={firstTool!.toolId}
|
||||
toolDesc={firstTool!.description}
|
||||
|
||||
@@ -197,12 +197,7 @@ const User = () => {
|
||||
return (
|
||||
<StoreCard
|
||||
key={firstTool!.id}
|
||||
icon={
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
||||
alt={'Icon'}
|
||||
/>
|
||||
}
|
||||
icon={firstTool!.icon}
|
||||
toolName={firstTool!.name}
|
||||
toolId={firstTool!.toolId}
|
||||
toolDesc={firstTool!.description}
|
||||
|
||||
@@ -32,7 +32,10 @@ const View = () => {
|
||||
.compile(files, importMap, toolVo.entryPoint)
|
||||
.then((result) => {
|
||||
const output = result.outputFiles[0].text
|
||||
setCompiledCode(`${output}\n${baseDist}`)
|
||||
setCompiledCode('')
|
||||
setTimeout(() => {
|
||||
setCompiledCode(`${output}\n${baseDist}`)
|
||||
})
|
||||
})
|
||||
.catch((reason) => {
|
||||
void message.error(`编译失败:${reason}`)
|
||||
@@ -72,9 +75,7 @@ const View = () => {
|
||||
break
|
||||
case DATABASE_NO_RECORD_FOUND:
|
||||
void message.error('未找到指定工具')
|
||||
setTimeout(() => {
|
||||
navigateToRepository(navigate)
|
||||
}, 3000)
|
||||
navigateToRepository(navigate)
|
||||
break
|
||||
default:
|
||||
void message.error('获取工具信息失败,请稍后重试')
|
||||
@@ -103,11 +104,11 @@ const View = () => {
|
||||
return
|
||||
}
|
||||
if (username === '!' && !ver) {
|
||||
navigateToView(navigate, '!', toolId!, platform as Platform)
|
||||
navigateToView(navigate, '!', toolId!, platform as Platform, 'latest')
|
||||
return
|
||||
}
|
||||
getTool()
|
||||
}, [])
|
||||
}, [username, toolId, ver, searchParams])
|
||||
|
||||
return (
|
||||
<FitFullscreen data-component={'tools-view'}>
|
||||
|
||||
@@ -166,9 +166,10 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
||||
|
||||
return (
|
||||
<RepositoryCard
|
||||
icon={<img src={`data:image/svg+xml;base64,${selectedTool.icon}`} alt={'Icon'} />}
|
||||
icon={selectedTool.icon}
|
||||
toolName={selectedTool.name}
|
||||
toolId={selectedTool.toolId}
|
||||
ver={selectedTool.ver}
|
||||
onOpen={handleOnOpenTool}
|
||||
onEdit={handleOnEditTool()}
|
||||
onSource={handleOnSourceTool()}
|
||||
@@ -563,65 +564,71 @@ const Tools = () => {
|
||||
))}
|
||||
{hasNextPage && <LoadMoreCard onClick={handleOnLoadMore} />}
|
||||
</FlexBox>
|
||||
<FlexBox className={'favorite-divider'}>
|
||||
<div />
|
||||
<div className={'divider-text'}>收藏</div>
|
||||
<div />
|
||||
</FlexBox>
|
||||
<FlexBox direction={'horizontal'} className={'star-content'}>
|
||||
{starToolData
|
||||
?.reduce((previousValue: ToolVo[], currentValue) => {
|
||||
if (
|
||||
!previousValue.some(
|
||||
(value) =>
|
||||
value.author.id === currentValue.author.id &&
|
||||
value.toolId === currentValue.toolId
|
||||
)
|
||||
) {
|
||||
previousValue.push(currentValue)
|
||||
}
|
||||
return previousValue
|
||||
}, [])
|
||||
.map((item) => {
|
||||
const tools = starToolData.filter(
|
||||
(value) =>
|
||||
value.author.id === item.author.id &&
|
||||
value.toolId === item.toolId
|
||||
)
|
||||
const webTool = tools.find((value) => value.platform === 'WEB')
|
||||
const desktopTool = tools.find(
|
||||
(value) => value.platform === 'DESKTOP'
|
||||
)
|
||||
const androidTool = tools.find(
|
||||
(value) => value.platform === 'ANDROID'
|
||||
)
|
||||
const firstTool =
|
||||
(checkDesktop()
|
||||
? desktopTool || webTool
|
||||
: webTool || desktopTool) || androidTool
|
||||
|
||||
return (
|
||||
<StoreCard
|
||||
key={firstTool!.id}
|
||||
icon={
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
||||
alt={'Icon'}
|
||||
/>
|
||||
{starToolData.length ? (
|
||||
<>
|
||||
<FlexBox className={'favorite-divider'}>
|
||||
<div />
|
||||
<div className={'divider-text'}>收藏</div>
|
||||
<div />
|
||||
</FlexBox>
|
||||
<FlexBox direction={'horizontal'} className={'star-content'}>
|
||||
{starToolData
|
||||
?.reduce((previousValue: ToolVo[], currentValue) => {
|
||||
if (
|
||||
!previousValue.some(
|
||||
(value) =>
|
||||
value.author.id ===
|
||||
currentValue.author.id &&
|
||||
value.toolId === currentValue.toolId
|
||||
)
|
||||
) {
|
||||
previousValue.push(currentValue)
|
||||
}
|
||||
toolName={firstTool!.name}
|
||||
toolId={firstTool!.toolId}
|
||||
toolDesc={firstTool!.description}
|
||||
author={firstTool!.author}
|
||||
ver={firstTool!.ver}
|
||||
platform={firstTool!.platform}
|
||||
supportPlatform={tools.map((value) => value.platform)}
|
||||
favorite={firstTool!.favorite}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{hasNextStarPage && <LoadMoreCard onClick={handleOnLoadMoreStar} />}
|
||||
</FlexBox>
|
||||
return previousValue
|
||||
}, [])
|
||||
.map((item) => {
|
||||
const tools = starToolData.filter(
|
||||
(value) =>
|
||||
value.author.id === item.author.id &&
|
||||
value.toolId === item.toolId
|
||||
)
|
||||
const webTool = tools.find(
|
||||
(value) => value.platform === 'WEB'
|
||||
)
|
||||
const desktopTool = tools.find(
|
||||
(value) => value.platform === 'DESKTOP'
|
||||
)
|
||||
const androidTool = tools.find(
|
||||
(value) => value.platform === 'ANDROID'
|
||||
)
|
||||
const firstTool =
|
||||
(checkDesktop()
|
||||
? desktopTool || webTool
|
||||
: webTool || desktopTool) || androidTool
|
||||
|
||||
return (
|
||||
<StoreCard
|
||||
key={firstTool!.id}
|
||||
icon={firstTool!.icon}
|
||||
toolName={firstTool!.name}
|
||||
toolId={firstTool!.toolId}
|
||||
toolDesc={firstTool!.description}
|
||||
author={firstTool!.author}
|
||||
ver={firstTool!.ver}
|
||||
platform={firstTool!.platform}
|
||||
supportPlatform={tools.map(
|
||||
(value) => value.platform
|
||||
)}
|
||||
favorite={firstTool!.favorite}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{hasNextStarPage && (
|
||||
<LoadMoreCard onClick={handleOnLoadMoreStar} />
|
||||
)}
|
||||
</FlexBox>
|
||||
</>
|
||||
) : undefined}
|
||||
</FlexBox>
|
||||
</HideScrollbar>
|
||||
</FitFullscreen>
|
||||
|
||||
@@ -1,70 +1,184 @@
|
||||
import { DndContext, DragOverEvent, DragStartEvent } from '@dnd-kit/core'
|
||||
import Droppable from '@/components/dnd/Droppable'
|
||||
import type { DragEndEvent } from '@dnd-kit/core/dist/types'
|
||||
import '@/assets/css/pages/tools-framework.scss'
|
||||
import { tools } from '@/router/tools'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import Sidebar from '@/components/common/Sidebar'
|
||||
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
||||
import { arrayMove, SortableContext } from '@dnd-kit/sortable'
|
||||
import { getViewPath } from '@/util/navigation.tsx'
|
||||
import Sortable from '@/components/dnd/Sortable.tsx'
|
||||
import DragHandle from '@/components/dnd/DragHandle.tsx'
|
||||
import DraggableOverlay from '@/components/dnd/DraggableOverlay.tsx'
|
||||
import { getToolMenuItem, saveToolMenuItem } from '@/util/common.tsx'
|
||||
|
||||
const ToolsFramework = () => {
|
||||
const [isDelete, setIsDelete] = useState(false)
|
||||
const [toolMenuItem, setToolMenuItem] = useState<ToolMenuItem[]>(getToolMenuItem)
|
||||
const [activeItem, setActiveItem] = useState<ToolMenuItem | null>(null)
|
||||
|
||||
const handleOnDragStart = ({ active }: DragStartEvent) => {
|
||||
setActiveItem(active.data.current as ToolMenuItem)
|
||||
}
|
||||
|
||||
const handleOnDragOver = ({ over }: DragOverEvent) => {
|
||||
setIsDelete(over === null)
|
||||
}
|
||||
|
||||
const handleOnDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
if (over && active.id !== over?.id) {
|
||||
const activeIndex = toolMenuItem.findIndex(
|
||||
({ authorUsername, toolId, ver }) =>
|
||||
`${authorUsername}:${toolId}:${ver}` === active.id
|
||||
)
|
||||
const overIndex = toolMenuItem.findIndex(
|
||||
({ authorUsername, toolId, ver }) =>
|
||||
`${authorUsername}:${toolId}:${ver}` === over.id
|
||||
)
|
||||
setToolMenuItem(arrayMove(toolMenuItem, activeIndex, overIndex))
|
||||
}
|
||||
|
||||
if (!over) {
|
||||
setToolMenuItem(
|
||||
toolMenuItem.filter(
|
||||
({ authorUsername, toolId, ver }) =>
|
||||
`${authorUsername}:${toolId}:${ver}` !== active?.id
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (!active.data.current?.sortable && over) {
|
||||
const newItem = active.data.current as ToolMenuItem
|
||||
if (
|
||||
toolMenuItem.findIndex(
|
||||
({ authorUsername, toolId, ver }) =>
|
||||
authorUsername === newItem.authorUsername &&
|
||||
toolId === newItem.toolId &&
|
||||
ver === newItem.ver
|
||||
) === -1
|
||||
) {
|
||||
setToolMenuItem([...toolMenuItem, newItem])
|
||||
}
|
||||
}
|
||||
|
||||
console.log('active', active)
|
||||
console.log('over', over)
|
||||
|
||||
setActiveItem(null)
|
||||
}
|
||||
|
||||
const handleOnDragCancel = () => {
|
||||
setActiveItem(null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
saveToolMenuItem(toolMenuItem)
|
||||
}, [toolMenuItem])
|
||||
|
||||
return (
|
||||
<>
|
||||
<FitFullscreen data-component={'tools-framework'} className={'flex-horizontal'}>
|
||||
<div className={'left-panel'}>
|
||||
<Sidebar title={'氧工具'}>
|
||||
<Sidebar.ItemList>
|
||||
<Sidebar.Item
|
||||
end
|
||||
path={'/store'}
|
||||
icon={tools[0].icon}
|
||||
text={tools[0].name}
|
||||
/>
|
||||
<Sidebar.Item
|
||||
end
|
||||
path={'/repository'}
|
||||
icon={tools[1].icon}
|
||||
text={tools[1].name}
|
||||
/>
|
||||
</Sidebar.ItemList>
|
||||
<Sidebar.Separate style={{ marginBottom: 0 }} />
|
||||
<Sidebar.Scroll>
|
||||
<DndContext
|
||||
onDragStart={handleOnDragStart}
|
||||
onDragOver={handleOnDragOver}
|
||||
onDragEnd={handleOnDragEnd}
|
||||
onDragCancel={handleOnDragCancel}
|
||||
>
|
||||
<div className={'left-panel'}>
|
||||
<Sidebar title={'氧工具'}>
|
||||
<Sidebar.ItemList>
|
||||
{tools.map((tool) => {
|
||||
return tool.menu &&
|
||||
tool.id !== 'tools-store' &&
|
||||
tool.id !== 'tools-repository' ? (
|
||||
<Sidebar.Item
|
||||
path={tool.absolutePath}
|
||||
icon={tool.icon}
|
||||
text={tool.name}
|
||||
key={tool.id}
|
||||
>
|
||||
{tool.children &&
|
||||
tool.children.map((subTool) => {
|
||||
return (
|
||||
<Sidebar.Item
|
||||
path={subTool.absolutePath}
|
||||
text={subTool.name}
|
||||
key={subTool.id}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Sidebar.Item>
|
||||
) : undefined
|
||||
})}
|
||||
<Sidebar.Item
|
||||
end
|
||||
path={'/store'}
|
||||
icon={tools[0].icon}
|
||||
text={tools[0].name}
|
||||
/>
|
||||
<Sidebar.Item
|
||||
end
|
||||
path={'/repository'}
|
||||
icon={tools[1].icon}
|
||||
text={tools[1].name}
|
||||
/>
|
||||
</Sidebar.ItemList>
|
||||
</Sidebar.Scroll>
|
||||
</Sidebar>
|
||||
</div>
|
||||
<div className={'right-panel'}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<>
|
||||
<FullscreenLoadingMask />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Sidebar.Separate />
|
||||
<Droppable id={'menu'} className={'menu-droppable'}>
|
||||
<Sidebar.Scroll>
|
||||
<Sidebar.ItemList>
|
||||
<SortableContext
|
||||
items={toolMenuItem.map(
|
||||
({ authorUsername, toolId, ver }) =>
|
||||
`${authorUsername}:${toolId}:${ver}`
|
||||
)}
|
||||
>
|
||||
{toolMenuItem.map(
|
||||
({
|
||||
icon,
|
||||
toolName,
|
||||
toolId,
|
||||
authorUsername,
|
||||
ver
|
||||
}) => (
|
||||
<Sortable
|
||||
id={`${authorUsername}:${toolId}:${ver}`}
|
||||
data={{
|
||||
icon,
|
||||
toolName,
|
||||
toolId,
|
||||
authorUsername,
|
||||
ver
|
||||
}}
|
||||
isDelete={isDelete}
|
||||
>
|
||||
<Sidebar.Item
|
||||
path={getViewPath(
|
||||
authorUsername,
|
||||
toolId,
|
||||
import.meta.env.VITE_PLATFORM,
|
||||
ver
|
||||
)}
|
||||
icon={icon}
|
||||
text={toolName}
|
||||
key={`${authorUsername}:${toolId}`}
|
||||
extend={<DragHandle padding={10} />}
|
||||
/>
|
||||
</Sortable>
|
||||
)
|
||||
)}
|
||||
</SortableContext>
|
||||
<DraggableOverlay>
|
||||
{activeItem && (
|
||||
<Sidebar.Item
|
||||
path={getViewPath(
|
||||
activeItem.authorUsername,
|
||||
activeItem.toolId,
|
||||
import.meta.env.VITE_PLATFORM,
|
||||
activeItem.ver
|
||||
)}
|
||||
icon={activeItem.icon}
|
||||
text={activeItem.toolName}
|
||||
key={`${activeItem.authorUsername}:${activeItem.toolId}:${activeItem.ver}`}
|
||||
extend={<DragHandle padding={10} />}
|
||||
/>
|
||||
)}
|
||||
</DraggableOverlay>
|
||||
</Sidebar.ItemList>
|
||||
</Sidebar.Scroll>
|
||||
</Droppable>
|
||||
</Sidebar>
|
||||
</div>
|
||||
<div className={'right-panel'}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<>
|
||||
<FullscreenLoadingMask />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</div>
|
||||
</DndContext>
|
||||
</FitFullscreen>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user