Refactor:11; Fix:6; Chore:1; Feat:7; Build:1 #49
55
package-lock.json
generated
55
package-lock.json
generated
@@ -9,6 +9,9 @@
|
|||||||
"version": "1.0.0-SNAPSHOT",
|
"version": "1.0.0-SNAPSHOT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
|
"@dnd-kit/core": "^6.1.0",
|
||||||
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@marsidev/react-turnstile": "^0.5.3",
|
"@marsidev/react-turnstile": "^0.5.3",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@typescript/ata": "^0.9.4",
|
"@typescript/ata": "^0.9.4",
|
||||||
@@ -584,6 +587,55 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dnd-kit/accessibility": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/core": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/accessibility": "^3.1.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/sortable": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@dnd-kit/core": "^6.1.0",
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/utilities": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emotion/hash": {
|
"node_modules/@emotion/hash": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz",
|
||||||
@@ -7428,8 +7480,7 @@
|
|||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
|
"@dnd-kit/core": "^6.1.0",
|
||||||
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@marsidev/react-turnstile": "^0.5.3",
|
"@marsidev/react-turnstile": "^0.5.3",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@typescript/ata": "^0.9.4",
|
"@typescript/ata": "^0.9.4",
|
||||||
|
|||||||
@@ -55,13 +55,14 @@
|
|||||||
.scroll {
|
.scroll {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
> li {
|
> li, > div > li {
|
||||||
|
padding: 2px 14px;
|
||||||
&.item {
|
&.item {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 4px 14px;
|
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
|
|
||||||
>.menu-bt {
|
>.menu-bt {
|
||||||
@@ -78,6 +79,10 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: constants.$SIZE_ICON_SM;
|
font-size: constants.$SIZE_ICON_SM;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -86,6 +91,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -94,7 +100,12 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: constants.$origin-color;
|
color: constants.$origin-color;
|
||||||
background-color: constants.$main-color !important;
|
background-color: constants.$main-color;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: drop-shadow(1000px 0 0 constants.$origin-color);
|
||||||
|
transform: translate(-1000px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,11 +147,11 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: constants.$origin-color;
|
color: constants.$origin-color;
|
||||||
background-color: constants.$main-color !important;
|
background-color: constants.$main-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover a {
|
&:hover a:not(.active) {
|
||||||
background-color: constants.$background-color;
|
background-color: constants.$background-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +160,7 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
>.menu-bt {
|
>.menu-bt {
|
||||||
a {
|
a:not(.active) {
|
||||||
background-color: constants.$background-color;
|
background-color: constants.$background-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,6 +182,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
.menu-bt {
|
||||||
|
border: {
|
||||||
|
width: 1px;
|
||||||
|
color: constants.$error-secondary-color;
|
||||||
|
style: dashed;
|
||||||
|
};
|
||||||
|
filter: drop-shadow(1000px 0 0 constants.$error-secondary-color);
|
||||||
|
transform: translate(-1000px);
|
||||||
|
|
||||||
|
> a {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +284,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-bt {
|
.menu-bt {
|
||||||
.text {
|
.text, .extend {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/assets/css/components/dnd/drag-handle.scss
Normal file
5
src/assets/css/components/dnd/drag-handle.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[data-component=component-drag-handle] {
|
||||||
|
background-color: transparent;
|
||||||
|
color: unset;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
@use '@/assets/css/constants' as constants;
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
[data-component=component-repository-card] {
|
[data-component=component-repository-card] {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.repository-card {
|
.repository-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -12,23 +14,25 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.version-select {
|
.header {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 10px;
|
width: 100%;
|
||||||
left: 10px;
|
align-items: center;
|
||||||
width: 9em;
|
padding: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.upgrade-bt {
|
.version-select {
|
||||||
position: absolute;
|
width: 9em;
|
||||||
top: 10px;
|
margin-right: auto;
|
||||||
right: 10px;
|
}
|
||||||
font-size: 1.8em;
|
|
||||||
|
>:not(.version-select) {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: 50px;
|
padding-top: 10px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
color: constants.$production-color;
|
color: constants.$production-color;
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
font-size: constants.$SIZE_ICON_XL;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@use '@/assets/css/constants' as constants;
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
[data-component=component-store-card] {
|
[data-component=component-store-card] {
|
||||||
|
height: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.store-card {
|
.store-card {
|
||||||
@@ -14,10 +15,31 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.version {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.6em;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
> *:hover {
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: 40px;
|
padding-top: 10px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
color: constants.$production-color;
|
color: constants.$production-color;
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
font-size: constants.$SIZE_ICON_XL;
|
||||||
@@ -28,12 +50,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.version {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
|
||||||
@@ -43,8 +59,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tool-desc {
|
.tool-desc {
|
||||||
margin-top: 10px;
|
margin: {
|
||||||
|
top: 10px;
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
};
|
||||||
color: constants.$font-secondary-color;
|
color: constants.$font-secondary-color;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-height: 40px;
|
||||||
|
width: 80%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,17 +92,16 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.operation {
|
:hover {
|
||||||
display: flex;
|
.header {
|
||||||
position: absolute;
|
.version {
|
||||||
top: 10px;
|
opacity: 0;
|
||||||
right: 12px;
|
}
|
||||||
font-size: 1.6em;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
> *:hover {
|
.operation {
|
||||||
color: constants.$font-secondary-color;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,13 @@
|
|||||||
[data-component=tools-framework] {
|
[data-component=tools-framework] {
|
||||||
.left-panel {
|
.left-panel {
|
||||||
background-color: constants.$origin-color;
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
|
.menu-droppable {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
> .card-box {
|
> .card-box, > div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
:first-child, :last-child {
|
:first-child, :last-child {
|
||||||
height: 0;
|
height: 0;
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
> .card-box {
|
> .card-box, > div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
> .card-box {
|
> div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
> .card-box {
|
> div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
1
src/assets/svg/handle.svg
Normal file
1
src/assets/svg/handle.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" /></svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
@@ -3,10 +3,11 @@ import Icon from '@ant-design/icons'
|
|||||||
import Submenu from '@/components/common/Sidebar/Submenu'
|
import Submenu from '@/components/common/Sidebar/Submenu'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
icon?: IconComponent
|
icon?: IconComponent | string
|
||||||
text?: string
|
text?: string
|
||||||
path: string
|
path: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
extend?: ReactNode
|
||||||
end?: boolean
|
end?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +43,19 @@ const Item = (props: ItemProps) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={'icon-box'}>
|
<div className={'icon-box'}>
|
||||||
{props.icon && <Icon className={'icon'} component={props.icon} />}
|
{props.icon &&
|
||||||
|
(typeof props.icon === 'string' ? (
|
||||||
|
<img
|
||||||
|
className={'icon'}
|
||||||
|
src={`data:image/svg+xml;base64,${props.icon}`}
|
||||||
|
alt={'icon'}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon className={'icon'} component={props.icon} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className={'text'}>{props.text}</span>
|
<span className={'text'}>{props.text}</span>
|
||||||
|
<div className={'extend'}>{props.extend}</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
{props.children && (
|
{props.children && (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const Separate = ({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
|
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
|
||||||
return <div className={`separate ${className ? ` ${className}` : ''}`} {...props} />
|
return <div className={`separate${className ? ` ${className}` : ''}`} {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Separate
|
export default Separate
|
||||||
|
|||||||
27
src/components/dnd/DragHandle.tsx
Normal file
27
src/components/dnd/DragHandle.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/dnd/drag-handle.scss'
|
||||||
|
|
||||||
|
interface DragHandleProps {
|
||||||
|
padding?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragHandle = ({ padding }: DragHandleProps) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
const { attributes, listeners, ref } = useContext(HandleContextInst)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-component={'component-drag-handle'}
|
||||||
|
style={{ padding }}
|
||||||
|
ref={ref}
|
||||||
|
className={'drag-handle'}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
<Icon component={IconOxygenHandle} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DragHandle
|
||||||
47
src/components/dnd/Draggable.tsx
Normal file
47
src/components/dnd/Draggable.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { CSSProperties, PropsWithChildren } from 'react'
|
||||||
|
import { useDraggable } from '@dnd-kit/core'
|
||||||
|
import { HandleContext, HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
|
||||||
|
interface DraggableProps extends PropsWithChildren {
|
||||||
|
id: string
|
||||||
|
data: ToolMenuItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const Draggable = ({ id, data, children }: DraggableProps) => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
isDragging,
|
||||||
|
listeners,
|
||||||
|
setNodeRef: draggableRef,
|
||||||
|
setActivatorNodeRef,
|
||||||
|
transform
|
||||||
|
} = useDraggable({
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
const context = useMemo<HandleContext>(
|
||||||
|
() => ({
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
ref: setActivatorNodeRef
|
||||||
|
}),
|
||||||
|
[attributes, listeners, setActivatorNodeRef]
|
||||||
|
)
|
||||||
|
const style: CSSProperties | undefined = transform
|
||||||
|
? {
|
||||||
|
opacity: isDragging ? 0 : undefined,
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
zIndex: 10000
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HandleContextInst.Provider value={context}>
|
||||||
|
<div ref={draggableRef} style={style}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</HandleContextInst.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Draggable
|
||||||
22
src/components/dnd/DraggableOverlay.tsx
Normal file
22
src/components/dnd/DraggableOverlay.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { PropsWithChildren } from 'react'
|
||||||
|
import { defaultDropAnimationSideEffects, DragOverlay, DropAnimation } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
interface DraggableOverlayProps extends PropsWithChildren {
|
||||||
|
isDelete?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropAnimationConfig: DropAnimation = {
|
||||||
|
sideEffects: defaultDropAnimationSideEffects({
|
||||||
|
styles: {
|
||||||
|
active: {
|
||||||
|
opacity: '0.4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const DraggableOverlay = ({ children }: DraggableOverlayProps) => {
|
||||||
|
return <DragOverlay dropAnimation={dropAnimationConfig}>{children}</DragOverlay>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraggableOverlay
|
||||||
16
src/components/dnd/Droppable.tsx
Normal file
16
src/components/dnd/Droppable.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { DetailedHTMLProps, HTMLAttributes } from 'react'
|
||||||
|
import { useDroppable } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
interface DroppableProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Droppable = ({ id, ...props }: DroppableProps) => {
|
||||||
|
const { setNodeRef: droppableRef } = useDroppable({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div {...props} ref={droppableRef} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Droppable
|
||||||
13
src/components/dnd/HandleContext.ts
Normal file
13
src/components/dnd/HandleContext.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { DraggableSyntheticListeners } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
export interface HandleContext {
|
||||||
|
attributes: Record<string, any>
|
||||||
|
listeners: DraggableSyntheticListeners
|
||||||
|
ref(node: HTMLElement | null): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HandleContextInst = createContext<HandleContext>({
|
||||||
|
attributes: {},
|
||||||
|
listeners: undefined,
|
||||||
|
ref() {}
|
||||||
|
})
|
||||||
54
src/components/dnd/Sortable.tsx
Normal file
54
src/components/dnd/Sortable.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { CSSProperties, PropsWithChildren } from 'react'
|
||||||
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
|
import { HandleContext, HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
|
||||||
|
interface SortableProps extends PropsWithChildren {
|
||||||
|
id: string
|
||||||
|
data: ToolMenuItem
|
||||||
|
isDelete?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sortable = ({ id, data, isDelete, children }: SortableProps) => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
isDragging,
|
||||||
|
listeners,
|
||||||
|
setNodeRef: draggableRef,
|
||||||
|
setActivatorNodeRef,
|
||||||
|
transform,
|
||||||
|
transition
|
||||||
|
} = useSortable({
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
const context = useMemo<HandleContext>(
|
||||||
|
() => ({
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
ref: setActivatorNodeRef
|
||||||
|
}),
|
||||||
|
[attributes, listeners, setActivatorNodeRef]
|
||||||
|
)
|
||||||
|
const style: CSSProperties | undefined = transform
|
||||||
|
? {
|
||||||
|
opacity: isDragging ? 0.4 : undefined,
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
zIndex: 10000,
|
||||||
|
transition
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HandleContextInst.Provider value={context}>
|
||||||
|
<div
|
||||||
|
ref={draggableRef}
|
||||||
|
style={style}
|
||||||
|
className={isDragging && isDelete ? 'delete' : undefined}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</HandleContextInst.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sortable
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'
|
import { DetailedHTMLProps, HTMLAttributes } from 'react'
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||||
import '@/assets/css/components/tools/repository-card.scss'
|
import '@/assets/css/components/tools/repository-card.scss'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import Draggable from '@/components/dnd/Draggable'
|
||||||
|
import DragHandle from '@/components/dnd/DragHandle.tsx'
|
||||||
|
|
||||||
interface RepositoryCardProps
|
interface RepositoryCardProps
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
icon: ReactNode
|
icon: string
|
||||||
toolName: string
|
toolName: string
|
||||||
toolId: string
|
toolId: string
|
||||||
|
ver: string
|
||||||
options?: TiltOptions
|
options?: TiltOptions
|
||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
onEdit?: () => void
|
onEdit?: () => void
|
||||||
@@ -25,6 +28,7 @@ const RepositoryCard = ({
|
|||||||
icon,
|
icon,
|
||||||
toolName,
|
toolName,
|
||||||
toolId,
|
toolId,
|
||||||
|
ver,
|
||||||
options = {
|
options = {
|
||||||
reverse: true,
|
reverse: true,
|
||||||
max: 8,
|
max: 8,
|
||||||
@@ -48,51 +52,58 @@ const RepositoryCard = ({
|
|||||||
}, [options])
|
}, [options])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Draggable id={toolId} data={{ icon, toolName, toolId, authorUsername: '!', ver }}>
|
||||||
data-component={'component-repository-card'}
|
<Card
|
||||||
style={{ overflow: 'visible', ...style }}
|
data-component={'component-repository-card'}
|
||||||
ref={cardRef}
|
style={{ overflow: 'visible', ...style }}
|
||||||
{...props}
|
ref={cardRef}
|
||||||
>
|
{...props}
|
||||||
<FlexBox className={'repository-card'}>
|
>
|
||||||
<div className={'icon'}>{icon}</div>
|
<FlexBox className={'repository-card'}>
|
||||||
<div className={'info'}>
|
<div className={'header'}>
|
||||||
{toolName && <div className={'tool-name'}>{toolName}</div>}
|
{children}
|
||||||
{toolId && <div className={'tool-id'}>{`ID: ${toolId}`}</div>}
|
<DragHandle />
|
||||||
</div>
|
</div>
|
||||||
<div className={'operation'}>
|
<div className={'icon'}>
|
||||||
{onOpen && (
|
<img src={`data:image/svg+xml;base64,${icon}`} alt={'Icon'} />
|
||||||
<AntdButton onClick={onOpen} size={'small'} type={'primary'}>
|
</div>
|
||||||
打开
|
<div className={'info'}>
|
||||||
</AntdButton>
|
<div className={'tool-name'}>{toolName}</div>
|
||||||
)}
|
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||||
{onEdit && onPublish && (
|
</div>
|
||||||
<div className={'edit'}>
|
<div className={'operation'}>
|
||||||
<AntdButton.Group size={'small'}>
|
{onOpen && (
|
||||||
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
<AntdButton onClick={onOpen} size={'small'} type={'primary'}>
|
||||||
<AntdButton onClick={onPublish}>发布</AntdButton>
|
打开
|
||||||
</AntdButton.Group>
|
</AntdButton>
|
||||||
</div>
|
)}
|
||||||
)}
|
{onEdit && onPublish && (
|
||||||
{onSource && (
|
<div className={'edit'}>
|
||||||
<AntdButton size={'small'} onClick={onSource}>
|
<AntdButton.Group size={'small'}>
|
||||||
源码
|
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
||||||
</AntdButton>
|
<AntdButton onClick={onPublish}>发布</AntdButton>
|
||||||
)}
|
</AntdButton.Group>
|
||||||
{onCancelReview && (
|
</div>
|
||||||
<AntdButton size={'small'} onClick={onCancelReview}>
|
)}
|
||||||
取消审核
|
{onSource && (
|
||||||
</AntdButton>
|
<AntdButton size={'small'} onClick={onSource}>
|
||||||
)}
|
源码
|
||||||
{onDelete && (
|
</AntdButton>
|
||||||
<AntdButton size={'small'} danger onClick={onDelete}>
|
)}
|
||||||
删除
|
{onCancelReview && (
|
||||||
</AntdButton>
|
<AntdButton size={'small'} onClick={onCancelReview}>
|
||||||
)}
|
取消审核
|
||||||
</div>
|
</AntdButton>
|
||||||
{children}
|
)}
|
||||||
</FlexBox>
|
{onDelete && (
|
||||||
</Card>
|
<AntdButton size={'small'} danger onClick={onDelete}>
|
||||||
|
删除
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
</Draggable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode } from 'react'
|
import { DetailedHTMLProps, HTMLAttributes, MouseEvent } from 'react'
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||||
import protocolCheck from 'custom-protocol-check'
|
import protocolCheck from 'custom-protocol-check'
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
@@ -10,9 +10,11 @@ import { r_tool_add_favorite, r_tool_remove_favorite } from '@/services/tool'
|
|||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { getUserId } from '@/util/auth.tsx'
|
import { getUserId } from '@/util/auth.tsx'
|
||||||
|
import DragHandle from '@/components/dnd/DragHandle.tsx'
|
||||||
|
import Draggable from '@/components/dnd/Draggable.tsx'
|
||||||
|
|
||||||
interface StoreCardProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
interface StoreCardProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
icon: ReactNode
|
icon: string
|
||||||
toolName: string
|
toolName: string
|
||||||
toolId: string
|
toolId: string
|
||||||
toolDesc: string
|
toolDesc: string
|
||||||
@@ -176,81 +178,101 @@ const StoreCard = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card
|
<Draggable
|
||||||
data-component={'component-store-card'}
|
id={`${author.username}:${toolId}:${ver}`}
|
||||||
style={{ overflow: 'visible', ...style }}
|
data={{ icon, toolName, toolId, authorUsername: author.username, ver: 'latest' }}
|
||||||
ref={cardRef}
|
|
||||||
{...props}
|
|
||||||
onClick={handleCardOnClick}
|
|
||||||
>
|
>
|
||||||
<FlexBox className={'store-card'}>
|
<Card
|
||||||
<div className={'icon'}>{icon}</div>
|
data-component={'component-store-card'}
|
||||||
<div className={'version'}>
|
style={{ overflow: 'visible', ...style }}
|
||||||
<AntdTag>
|
ref={cardRef}
|
||||||
{platform.slice(0, 1)}-{ver}
|
{...props}
|
||||||
</AntdTag>
|
onClick={handleCardOnClick}
|
||||||
</div>
|
>
|
||||||
<div className={'info'}>
|
<FlexBox className={'store-card'}>
|
||||||
<div className={'tool-name'}>{toolName}</div>
|
<div className={'header'}>
|
||||||
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
<div className={'version'}>
|
||||||
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
<AntdTag>
|
||||||
</div>
|
{platform.slice(0, 1)}-{ver}
|
||||||
{showAuthor && (
|
</AntdTag>
|
||||||
<div className={'author'} onClick={handleOnClickAuthor}>
|
</div>
|
||||||
<div className={'avatar'}>
|
<div className={'operation'}>
|
||||||
<AntdAvatar
|
{platform !== 'ANDROID' && supportPlatform.includes('ANDROID') && (
|
||||||
src={
|
<AntdTooltip title={'Android 端'}>
|
||||||
<AntdImage
|
<Icon
|
||||||
preview={false}
|
component={IconOxygenMobile}
|
||||||
src={`data:image/png;base64,${author.userInfo.avatar}`}
|
onClick={handleOnAndroidBtnClick}
|
||||||
alt={'Avatar'}
|
/>
|
||||||
/>
|
</AntdTooltip>
|
||||||
}
|
)}
|
||||||
style={{ background: COLOR_BACKGROUND }}
|
{platform === 'DESKTOP' && supportPlatform.includes('WEB') && (
|
||||||
/>
|
<AntdTooltip title={'Web 端'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenBrowser}
|
||||||
|
onClick={handleOnWebBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
{platform === 'WEB' && supportPlatform.includes('DESKTOP') && (
|
||||||
|
<AntdTooltip title={'桌面端'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenDesktop}
|
||||||
|
onClick={handleOnDesktopBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
<AntdTooltip title={'源码'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenCode}
|
||||||
|
onClick={handleOnSourceBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
{author.id !== userId && (
|
||||||
|
<AntdTooltip title={favorite_ ? '取消收藏' : '收藏'}>
|
||||||
|
<Icon
|
||||||
|
component={
|
||||||
|
favorite_ ? IconOxygenStarFilled : IconOxygenStar
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
color: favorite_ ? COLOR_PRODUCTION : undefined
|
||||||
|
}}
|
||||||
|
onClick={handleOnStarBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
<DragHandle />
|
||||||
</div>
|
</div>
|
||||||
<AntdTooltip title={author.username}>
|
|
||||||
<div className={'author-name'}>{author.userInfo.nickname}</div>
|
|
||||||
</AntdTooltip>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className={'icon'}>
|
||||||
<div className={'operation'}>
|
<img src={`data:image/svg+xml;base64,${icon}`} alt={'Icon'} />
|
||||||
{platform !== 'ANDROID' && supportPlatform.includes('ANDROID') && (
|
</div>
|
||||||
<AntdTooltip title={'Android 端'}>
|
<div className={'info'}>
|
||||||
<Icon
|
<div className={'tool-name'}>{toolName}</div>
|
||||||
component={IconOxygenMobile}
|
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||||
onClick={handleOnAndroidBtnClick}
|
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
||||||
/>
|
</div>
|
||||||
</AntdTooltip>
|
{showAuthor && (
|
||||||
|
<div className={'author'} onClick={handleOnClickAuthor}>
|
||||||
|
<div className={'avatar'}>
|
||||||
|
<AntdAvatar
|
||||||
|
src={
|
||||||
|
<AntdImage
|
||||||
|
preview={false}
|
||||||
|
src={`data:image/png;base64,${author.userInfo.avatar}`}
|
||||||
|
alt={'Avatar'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{ background: COLOR_BACKGROUND }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AntdTooltip title={author.username}>
|
||||||
|
<div className={'author-name'}>{author.userInfo.nickname}</div>
|
||||||
|
</AntdTooltip>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{platform === 'DESKTOP' && supportPlatform.includes('WEB') && (
|
</FlexBox>
|
||||||
<AntdTooltip title={'Web 端'}>
|
</Card>
|
||||||
<Icon component={IconOxygenBrowser} onClick={handleOnWebBtnClick} />
|
</Draggable>
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
{platform === 'WEB' && supportPlatform.includes('DESKTOP') && (
|
|
||||||
<AntdTooltip title={'桌面端'}>
|
|
||||||
<Icon
|
|
||||||
component={IconOxygenDesktop}
|
|
||||||
onClick={handleOnDesktopBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
<AntdTooltip title={'源码'}>
|
|
||||||
<Icon component={IconOxygenCode} onClick={handleOnSourceBtnClick} />
|
|
||||||
</AntdTooltip>
|
|
||||||
{author.id !== userId && (
|
|
||||||
<AntdTooltip title={favorite_ ? '取消收藏' : '收藏'}>
|
|
||||||
<Icon
|
|
||||||
component={favorite_ ? IconOxygenStarFilled : IconOxygenStar}
|
|
||||||
style={{ color: favorite_ ? COLOR_PRODUCTION : undefined }}
|
|
||||||
onClick={handleOnStarBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const PRODUCTION_NAME = 'Oxygen Toolbox'
|
export const PRODUCTION_NAME = 'Oxygen Toolbox'
|
||||||
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
||||||
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
||||||
export const STORAGE_FAVORITE_KEY = 'FAVORITE'
|
export const STORAGE_TOOL_MENU_ITEM_KEY = 'TOOL_MENU_ITEM'
|
||||||
export const COLOR_ORIGIN = 'white'
|
export const COLOR_ORIGIN = 'white'
|
||||||
export const COLOR_PRODUCTION = '#4E47BB'
|
export const COLOR_PRODUCTION = '#4E47BB'
|
||||||
export const COLOR_MAIN = COLOR_PRODUCTION
|
export const COLOR_MAIN = COLOR_PRODUCTION
|
||||||
|
|||||||
8
src/global.d.ts
vendored
8
src/global.d.ts
vendored
@@ -654,3 +654,11 @@ interface ToolFavoriteAddRemoveParam {
|
|||||||
toolId: string
|
toolId: string
|
||||||
platform: Platform
|
platform: Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolMenuItem {
|
||||||
|
icon: string
|
||||||
|
toolName: string
|
||||||
|
toolId: string
|
||||||
|
authorUsername: string
|
||||||
|
ver: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,12 +134,7 @@ const Store = () => {
|
|||||||
return (
|
return (
|
||||||
<StoreCard
|
<StoreCard
|
||||||
key={firstTool!.id}
|
key={firstTool!.id}
|
||||||
icon={
|
icon={firstTool!.icon}
|
||||||
<img
|
|
||||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
|
||||||
alt={'Icon'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
toolName={firstTool!.name}
|
toolName={firstTool!.name}
|
||||||
toolId={firstTool!.toolId}
|
toolId={firstTool!.toolId}
|
||||||
toolDesc={firstTool!.description}
|
toolDesc={firstTool!.description}
|
||||||
|
|||||||
@@ -197,12 +197,7 @@ const User = () => {
|
|||||||
return (
|
return (
|
||||||
<StoreCard
|
<StoreCard
|
||||||
key={firstTool!.id}
|
key={firstTool!.id}
|
||||||
icon={
|
icon={firstTool!.icon}
|
||||||
<img
|
|
||||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
|
||||||
alt={'Icon'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
toolName={firstTool!.name}
|
toolName={firstTool!.name}
|
||||||
toolId={firstTool!.toolId}
|
toolId={firstTool!.toolId}
|
||||||
toolDesc={firstTool!.description}
|
toolDesc={firstTool!.description}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ const View = () => {
|
|||||||
.compile(files, importMap, toolVo.entryPoint)
|
.compile(files, importMap, toolVo.entryPoint)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const output = result.outputFiles[0].text
|
const output = result.outputFiles[0].text
|
||||||
setCompiledCode(`${output}\n${baseDist}`)
|
setCompiledCode('')
|
||||||
|
setTimeout(() => {
|
||||||
|
setCompiledCode(`${output}\n${baseDist}`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
void message.error(`编译失败:${reason}`)
|
void message.error(`编译失败:${reason}`)
|
||||||
@@ -72,9 +75,7 @@ const View = () => {
|
|||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigateToRepository(navigate)
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -103,11 +104,11 @@ const View = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username === '!' && !ver) {
|
if (username === '!' && !ver) {
|
||||||
navigateToView(navigate, '!', toolId!, platform as Platform)
|
navigateToView(navigate, '!', toolId!, platform as Platform, 'latest')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [username, toolId, ver, searchParams])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FitFullscreen data-component={'tools-view'}>
|
<FitFullscreen data-component={'tools-view'}>
|
||||||
|
|||||||
@@ -166,9 +166,10 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RepositoryCard
|
<RepositoryCard
|
||||||
icon={<img src={`data:image/svg+xml;base64,${selectedTool.icon}`} alt={'Icon'} />}
|
icon={selectedTool.icon}
|
||||||
toolName={selectedTool.name}
|
toolName={selectedTool.name}
|
||||||
toolId={selectedTool.toolId}
|
toolId={selectedTool.toolId}
|
||||||
|
ver={selectedTool.ver}
|
||||||
onOpen={handleOnOpenTool}
|
onOpen={handleOnOpenTool}
|
||||||
onEdit={handleOnEditTool()}
|
onEdit={handleOnEditTool()}
|
||||||
onSource={handleOnSourceTool()}
|
onSource={handleOnSourceTool()}
|
||||||
@@ -563,65 +564,71 @@ const Tools = () => {
|
|||||||
))}
|
))}
|
||||||
{hasNextPage && <LoadMoreCard onClick={handleOnLoadMore} />}
|
{hasNextPage && <LoadMoreCard onClick={handleOnLoadMore} />}
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
<FlexBox className={'favorite-divider'}>
|
{starToolData.length ? (
|
||||||
<div />
|
<>
|
||||||
<div className={'divider-text'}>收藏</div>
|
<FlexBox className={'favorite-divider'}>
|
||||||
<div />
|
<div />
|
||||||
</FlexBox>
|
<div className={'divider-text'}>收藏</div>
|
||||||
<FlexBox direction={'horizontal'} className={'star-content'}>
|
<div />
|
||||||
{starToolData
|
</FlexBox>
|
||||||
?.reduce((previousValue: ToolVo[], currentValue) => {
|
<FlexBox direction={'horizontal'} className={'star-content'}>
|
||||||
if (
|
{starToolData
|
||||||
!previousValue.some(
|
?.reduce((previousValue: ToolVo[], currentValue) => {
|
||||||
(value) =>
|
if (
|
||||||
value.author.id === currentValue.author.id &&
|
!previousValue.some(
|
||||||
value.toolId === currentValue.toolId
|
(value) =>
|
||||||
)
|
value.author.id ===
|
||||||
) {
|
currentValue.author.id &&
|
||||||
previousValue.push(currentValue)
|
value.toolId === currentValue.toolId
|
||||||
}
|
)
|
||||||
return previousValue
|
) {
|
||||||
}, [])
|
previousValue.push(currentValue)
|
||||||
.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'}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
toolName={firstTool!.name}
|
return previousValue
|
||||||
toolId={firstTool!.toolId}
|
}, [])
|
||||||
toolDesc={firstTool!.description}
|
.map((item) => {
|
||||||
author={firstTool!.author}
|
const tools = starToolData.filter(
|
||||||
ver={firstTool!.ver}
|
(value) =>
|
||||||
platform={firstTool!.platform}
|
value.author.id === item.author.id &&
|
||||||
supportPlatform={tools.map((value) => value.platform)}
|
value.toolId === item.toolId
|
||||||
favorite={firstTool!.favorite}
|
)
|
||||||
/>
|
const webTool = tools.find(
|
||||||
)
|
(value) => value.platform === 'WEB'
|
||||||
})}
|
)
|
||||||
{hasNextStarPage && <LoadMoreCard onClick={handleOnLoadMoreStar} />}
|
const desktopTool = tools.find(
|
||||||
</FlexBox>
|
(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>
|
</FlexBox>
|
||||||
</HideScrollbar>
|
</HideScrollbar>
|
||||||
</FitFullscreen>
|
</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 '@/assets/css/pages/tools-framework.scss'
|
||||||
import { tools } from '@/router/tools'
|
import { tools } from '@/router/tools'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import Sidebar from '@/components/common/Sidebar'
|
import Sidebar from '@/components/common/Sidebar'
|
||||||
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
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 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullscreen data-component={'tools-framework'} className={'flex-horizontal'}>
|
<FitFullscreen data-component={'tools-framework'} className={'flex-horizontal'}>
|
||||||
<div className={'left-panel'}>
|
<DndContext
|
||||||
<Sidebar title={'氧工具'}>
|
onDragStart={handleOnDragStart}
|
||||||
<Sidebar.ItemList>
|
onDragOver={handleOnDragOver}
|
||||||
<Sidebar.Item
|
onDragEnd={handleOnDragEnd}
|
||||||
end
|
onDragCancel={handleOnDragCancel}
|
||||||
path={'/store'}
|
>
|
||||||
icon={tools[0].icon}
|
<div className={'left-panel'}>
|
||||||
text={tools[0].name}
|
<Sidebar title={'氧工具'}>
|
||||||
/>
|
|
||||||
<Sidebar.Item
|
|
||||||
end
|
|
||||||
path={'/repository'}
|
|
||||||
icon={tools[1].icon}
|
|
||||||
text={tools[1].name}
|
|
||||||
/>
|
|
||||||
</Sidebar.ItemList>
|
|
||||||
<Sidebar.Separate style={{ marginBottom: 0 }} />
|
|
||||||
<Sidebar.Scroll>
|
|
||||||
<Sidebar.ItemList>
|
<Sidebar.ItemList>
|
||||||
{tools.map((tool) => {
|
<Sidebar.Item
|
||||||
return tool.menu &&
|
end
|
||||||
tool.id !== 'tools-store' &&
|
path={'/store'}
|
||||||
tool.id !== 'tools-repository' ? (
|
icon={tools[0].icon}
|
||||||
<Sidebar.Item
|
text={tools[0].name}
|
||||||
path={tool.absolutePath}
|
/>
|
||||||
icon={tool.icon}
|
<Sidebar.Item
|
||||||
text={tool.name}
|
end
|
||||||
key={tool.id}
|
path={'/repository'}
|
||||||
>
|
icon={tools[1].icon}
|
||||||
{tool.children &&
|
text={tools[1].name}
|
||||||
tool.children.map((subTool) => {
|
/>
|
||||||
return (
|
|
||||||
<Sidebar.Item
|
|
||||||
path={subTool.absolutePath}
|
|
||||||
text={subTool.name}
|
|
||||||
key={subTool.id}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Sidebar.Item>
|
|
||||||
) : undefined
|
|
||||||
})}
|
|
||||||
</Sidebar.ItemList>
|
</Sidebar.ItemList>
|
||||||
</Sidebar.Scroll>
|
<Sidebar.Separate />
|
||||||
</Sidebar>
|
<Droppable id={'menu'} className={'menu-droppable'}>
|
||||||
</div>
|
<Sidebar.Scroll>
|
||||||
<div className={'right-panel'}>
|
<Sidebar.ItemList>
|
||||||
<Suspense
|
<SortableContext
|
||||||
fallback={
|
items={toolMenuItem.map(
|
||||||
<>
|
({ authorUsername, toolId, ver }) =>
|
||||||
<FullscreenLoadingMask />
|
`${authorUsername}:${toolId}:${ver}`
|
||||||
</>
|
)}
|
||||||
}
|
>
|
||||||
>
|
{toolMenuItem.map(
|
||||||
<Outlet />
|
({
|
||||||
</Suspense>
|
icon,
|
||||||
</div>
|
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>
|
</FitFullscreen>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
||||||
import { floor } from 'lodash'
|
import { floor } from 'lodash'
|
||||||
|
import { getLocalStorage, setLocalStorage } from '@/util/browser.tsx'
|
||||||
|
import { STORAGE_TOOL_MENU_ITEM_KEY } from '@/constants/common.constants.ts'
|
||||||
|
|
||||||
export const randomInt = (start: number, end: number) => {
|
export const randomInt = (start: number, end: number) => {
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
@@ -133,3 +135,15 @@ const formatByte = (size: number, unit: ByteUnit): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const checkDesktop = () => import.meta.env.VITE_PLATFORM === 'DESKTOP'
|
export const checkDesktop = () => import.meta.env.VITE_PLATFORM === 'DESKTOP'
|
||||||
|
|
||||||
|
export const saveToolMenuItem = (toolMenuItem: ToolMenuItem[]) => {
|
||||||
|
setLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY, JSON.stringify(toolMenuItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToolMenuItem = (): ToolMenuItem[] => {
|
||||||
|
const s = getLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY)
|
||||||
|
if (!s) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return JSON.parse(s) as ToolMenuItem[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,3 +117,11 @@ export const navigateToUser = (navigate: NavigateFunction, options?: NavigateOpt
|
|||||||
export const navigateToTools = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
export const navigateToTools = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
||||||
navigate('/system/tools', options)
|
navigate('/system/tools', options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getViewPath = (
|
||||||
|
username: string,
|
||||||
|
toolId: string,
|
||||||
|
platform: Platform,
|
||||||
|
version?: string
|
||||||
|
) =>
|
||||||
|
`/view/${username}/${toolId}${version ? `/${version}` : ''}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
||||||
|
|||||||
Reference in New Issue
Block a user