Feat(Menu): Add tool menu via drag and drop

Drag and drop a tool card to add tool menu
This commit is contained in:
2024-04-30 13:42:36 +08:00
parent 843f47346a
commit 7b61a5fdb3
30 changed files with 785 additions and 298 deletions

View File

@@ -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}

View File

@@ -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}

View File

@@ -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'}>

View File

@@ -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>

View File

@@ -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>
</>
)