🔨 chore: add react layout

This commit is contained in:
倏昱 2023-04-19 20:36:53 +08:00
parent 8e84bbd187
commit f8e98b9f80
15 changed files with 1111 additions and 23564 deletions

File diff suppressed because one or more lines are too long

View File

@ -42,10 +42,13 @@
},
"dependencies": {},
"devDependencies": {
"@ant-design/icons": "^5.0.1",
"@commitlint/cli": "^17",
"@types/node": "^18",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-rnd": "^8.0.0",
"@types/styled-components": "^5.1.26",
"@umijs/lint": "^4.0.64",
"antd": "^5.4.2",
"antd-style": "^3.0.0",
@ -58,12 +61,16 @@
"husky": "^8.0.3",
"lint-staged": "^13.2.1",
"object-to-css-variables": "^0.2.1",
"polished": "^4.2.2",
"prettier": "^2",
"prettier-plugin-organize-imports": "^3",
"prettier-plugin-packagejson": "^2",
"query-string": "^8.1.0",
"re-resizable": "^6.9.9",
"react": "^18",
"react-dom": "^18",
"react-layout-kit": "^1.6.1",
"react-rnd": "^10.4.1",
"semantic-release": "^21",
"semantic-release-config-gitmoji": "^1",
"styled-components": "^5.3.9",
@ -71,6 +78,7 @@
"stylelint-less": "^1.0.6",
"typescript": "^5.0.0",
"umi": "^4.0.64",
"use-merge-value": "^1.2.0",
"webpack-shell-plugin-next": "^2.3.1"
}
}

View File

@ -0,0 +1,171 @@
import type { NumberSize, Size } from 're-resizable'
import type { CSSProperties, FC, ReactNode } from 'react'
import { memo } from 'react'
import type { Props as RndProps } from 'react-rnd'
import { FixMode, placementType } from './FixMode'
import { FloatMode } from './FloatMode'
export interface DraggablePanelProps {
/**
*
* 使
*/
mode?: 'fixed' | 'float'
/**
*
* @default right
*/
placement?: placementType
/**
*
*/
minWidth?: number
/**
*
*/
minHeight?: number
/**
*
*/
resize?: RndProps['enableResizing']
/**
*
*
*/
size?: Partial<Size>
onSizeChange?: (delta: NumberSize, size?: Size) => void
/**
*
* @param delta
* @param size
*/
onSizeDragging?: (delta: NumberSize, size?: Size) => void
/**
*
* @default true
*/
expandable?: boolean
/**
*
*/
isExpand?: boolean
/**
*
* @param expand
*/
onExpandChange?: (expand: boolean) => void
/**
*
*
*/
position?: RndProps['position']
/**
*
* width 320px height 100%
* width 320px height 400px
*/
defaultSize?: Partial<Size>
/**
*
* @default [100,100]
*/
defaultPosition?: RndProps['position']
/**
*
*/
onPositionChange?: (position: RndProps['position']) => void
/**
*
*/
style?: CSSProperties
/**
*
*/
className?: string
/**
*
*/
children: ReactNode
/**
*
*/
prefixCls?: string
}
export const Draggable: FC<DraggablePanelProps> = memo(
({
children,
className,
mode,
placement = 'right',
resize,
style,
position,
onPositionChange,
size,
defaultSize,
defaultPosition,
minWidth,
minHeight,
onSizeChange,
onSizeDragging,
expandable = true,
isExpand,
onExpandChange,
}) => {
const prefixCls = 'draggable-panel'
switch (mode) {
case 'fixed':
default:
return (
<FixMode
prefixCls={prefixCls}
// 尺寸
size={size}
defaultSize={defaultSize}
onSizeDragging={onSizeDragging}
onSizeChange={onSizeChange}
minHeight={minHeight}
minWidth={minWidth}
// 缩放
resize={resize}
onExpandChange={onExpandChange}
expandable={expandable}
isExpand={isExpand}
className={className}
placement={placement}
style={style}
>
{children}
</FixMode>
)
case 'float':
return (
<FloatMode
prefixCls={prefixCls}
// 坐标
defaultPosition={defaultPosition}
position={position}
onPositionChange={onPositionChange}
// 尺寸
minHeight={minHeight}
minWidth={minWidth}
defaultSize={defaultSize}
size={size}
onSizeDragging={onSizeDragging}
onSizeChange={onSizeChange}
// 缩放
resize={resize}
canResizing={resize !== false}
className={className}
style={style}
>
{children}
</FloatMode>
)
}
}
)

View File

@ -0,0 +1,248 @@
import { DownOutlined, LeftOutlined, RightOutlined, UpOutlined } from '@ant-design/icons'
import type { Enable, NumberSize, Size } from 're-resizable'
import { HandleClassName, Resizable } from 're-resizable'
import type { CSSProperties, FC, ReactNode } from 'react'
import { memo, useMemo } from 'react'
import { Center } from 'react-layout-kit'
import type { Props as RndProps } from 'react-rnd'
import useControlledState from 'use-merge-value'
import { useStyle } from './style'
export type placementType = 'right' | 'left' | 'top' | 'bottom'
export interface FixModePanelProps {
/**
*
* 使
*/
mode?: 'fixed' | 'float'
/**
*
* @default right
*/
placement: placementType
/**
*
*/
minWidth?: number
/**
*
*/
minHeight?: number
/**
*
*/
resize?: RndProps['enableResizing']
/**
*
*
*/
size?: Partial<Size>
onSizeChange?: (delta: NumberSize, size?: Size) => void
/**
*
* @param delta
* @param size
*/
onSizeDragging?: (delta: NumberSize, size?: Size) => void
/**
*
* @default true
*/
expandable?: boolean
/**
*
*/
isExpand?: boolean
/**
*
* @param expand
*/
onExpandChange?: (expand: boolean) => void
/**
*
*
*/
position?: RndProps['position']
/**
*
* width 320px height 100%
* width 320px height 400px
*/
defaultSize?: Partial<Size>
/**
*
* @default [100,100]
*/
defaultPosition?: RndProps['position']
/**
*
*/
onPositionChange?: (position: RndProps['position']) => void
/**
*
*/
style?: CSSProperties
className?: string
/**
*
*/
children: ReactNode
/**
*
*/
prefixCls?: string
}
const DEFAULT_HEIGHT = 150
const DEFAULT_WIDTH = 400
const revesePlacement = (placement: placementType) => {
switch (placement) {
case 'bottom':
return 'top'
case 'top':
return 'bottom'
case 'right':
return 'left'
case 'left':
return 'right'
}
}
export const FixMode: FC<FixModePanelProps> = memo<FixModePanelProps>(
({
children,
placement = 'right',
resize,
style,
size,
defaultSize: customizeDefaultSize,
minWidth,
minHeight,
onSizeChange,
onSizeDragging,
expandable = true,
isExpand: expand,
onExpandChange,
className,
}) => {
const prefixCls = 'draggable-panel'
const isVertical = placement === 'top' || placement === 'bottom'
const { styles, cx } = useStyle(prefixCls)
const [isExpand, setIsExpand] = useControlledState(true, {
value: expand,
onChange: onExpandChange,
})
// 只有配置了 resize 和 isExpand 属性后才可拖拽
const canResizing = resize !== false && isExpand
const resizeHandleClassNames: HandleClassName = useMemo(() => {
if (!canResizing) return {}
return {
[revesePlacement(placement)]: styles[`${revesePlacement(placement)}Handle`],
}
}, [canResizing, placement])
const resizing = {
top: false,
bottom: false,
right: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
[revesePlacement(placement)]: true,
...(resize as Enable),
}
const defaultSize: Size = useMemo(() => {
if (isVertical)
return {
width: '100%',
height: DEFAULT_HEIGHT,
...customizeDefaultSize,
}
return {
width: DEFAULT_WIDTH,
height: '100%',
...customizeDefaultSize,
}
}, [isVertical])
const sizeProps = isExpand
? {
minWidth: typeof minWidth === 'number' ? Math.max(minWidth, 0) : 280,
minHeight: typeof minHeight === 'number' ? Math.max(minHeight, 0) : undefined,
defaultSize,
size: size as Size,
style,
}
: {
minWidth: 0,
minHeight: 0,
size: { width: 0, height: 0 },
}
const { Arrow, className: arrowPlacement } = useMemo(() => {
switch (placement) {
case 'top':
return { className: 'Bottom', Arrow: DownOutlined }
case 'bottom':
return { className: 'Top', Arrow: UpOutlined }
case 'right':
return { className: 'Left', Arrow: LeftOutlined }
case 'left':
return { className: 'Right', Arrow: RightOutlined }
}
}, [styles, placement])
return (
<div className={cx(styles.container, className)} style={{ [`border${arrowPlacement}Width`]: 1 }}>
{expandable && (
<Center
// @ts-ignore
className={cx(styles[`toggle${arrowPlacement}`])}
onClick={() => {
setIsExpand(!isExpand)
}}
style={{ opacity: isExpand ? undefined : 1 }}
>
<Arrow rotate={isExpand ? 180 : 0} />
</Center>
)}
{
<Resizable
{...sizeProps}
className={styles.fixed}
enable={canResizing ? (resizing as Enable) : undefined}
handleClasses={resizeHandleClassNames}
onResizeStop={(e, direction, ref, delta) => {
onSizeChange?.(delta, {
width: ref.style.width,
height: ref.style.height,
})
}}
onResize={(_, direction, ref, delta) => {
onSizeDragging?.(delta, {
width: ref.style.width,
height: ref.style.height,
})
}}
>
{children}
</Resizable>
}
</div>
)
}
)

View File

@ -0,0 +1,174 @@
import type { Enable, NumberSize, Size } from 're-resizable'
import { HandleClassName } from 're-resizable'
import type { CSSProperties, FC, ReactNode } from 'react'
import { memo, useMemo } from 'react'
import type { Position, Props as RndProps } from 'react-rnd'
import { Rnd } from 'react-rnd'
import { useStyle } from './style'
export interface FloatProps {
/**
*
* 使
*/
mode?: 'fixed' | 'float'
/**
*
* @default horizontal
*/
direction?: 'vertical' | 'horizontal'
/**
*
*/
minWidth?: number
/**
*
*/
minHeight?: number
/**
*
*/
resize?: RndProps['enableResizing']
/**
*
*
*/
size?: Partial<Size>
onSizeChange?: (delta: NumberSize, size?: Size) => void
/**
*
* @param delta
* @param size
*/
onSizeDragging?: (delta: NumberSize, size?: Size) => void
canResizing?: boolean
/**
*
*
*/
position?: RndProps['position']
/**
*
* width 320px height 100%
* width 320px height 400px
*/
defaultSize?: Partial<Size>
/**
*
* @default [100,100]
*/
defaultPosition?: RndProps['position']
/**
*
*/
onPositionChange?: (position: RndProps['position']) => void
/**
*
*/
style?: CSSProperties
/**
*
*/
className?: string
/**
*
*/
children: ReactNode
/**
*
*/
prefixCls?: string
}
const DEFAULT_HEIGHT = 300
const DEFAULT_WIDTH = 400
export const FloatMode: FC<FloatProps> = memo(
({
children,
direction,
resize,
style,
position,
onPositionChange,
size,
defaultSize: customizeDefaultSize,
defaultPosition: customizeDefaultPosition,
minWidth = 280,
minHeight = 200,
prefixCls,
canResizing,
}) => {
const { styles } = useStyle(prefixCls)
const resizeHandleClassNames: HandleClassName = useMemo(() => {
if (!canResizing) return {}
return {
right: styles.rightHandle,
left: styles.leftHandle,
top: styles.topHandle,
bottom: styles.bottomHandle,
}
}, [canResizing, direction])
const resizing = useMemo(() => {
if (canResizing) return resize
return {
top: true,
bottom: true,
right: true,
left: true,
topRight: true,
bottomRight: true,
bottomLeft: true,
topLeft: true,
...(resize as Enable),
}
}, [canResizing, resize])
const defaultSize: Size = {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
...customizeDefaultSize,
}
const defaultPosition: Position = {
x: 100,
y: 100,
...customizeDefaultPosition,
}
const sizeProps = {
minWidth: Math.max(minWidth, 0),
minHeight: Math.max(minHeight, 0),
defaultSize,
size: size as Size,
style,
}
return (
<Rnd
position={position}
resizeHandleClasses={resizeHandleClassNames}
default={{
...defaultPosition,
...defaultSize,
}}
onDragStop={(e, data) => {
onPositionChange?.({ x: data.x, y: data.y })
}}
bound={'parent'}
enableResizing={resizing}
{...sizeProps}
className={styles.float}
>
{children}
</Rnd>
)
}
)

View File

@ -0,0 +1,3 @@
export type { Position } from 'react-rnd'
export { Draggable as DraggablePanel } from './DraggablePanel'
export type { DraggablePanelProps } from './DraggablePanel'

View File

@ -0,0 +1,174 @@
import { createStyles, css, cx } from 'antd-style'
export const useStyle = createStyles(({ token }, prefix: string) => {
const commonHandle = css`
position: relative;
&::before {
position: absolute;
z-index: 50;
transition: all 0.3s ease-in-out;
content: '';
}
&:hover,
&:active {
&::before {
background: ${token.colorPrimary};
}
}
`
const commonToggle = css`
position: absolute;
opacity: 0;
z-index: 1001;
transition: opacity 0.1s;
border-radius: 4px;
cursor: pointer;
background: ${token.colorBgElevated};
border-width: 1px;
border-style: solid;
color: ${token.colorTextTertiary};
border-color: ${token.colorBorder};
&:hover {
color: ${token.colorTextSecondary};
background: ${token.colorFillQuaternary};
}
`
const offset = 17
const toggleLength = 40
const toggleShort = 16
return {
container: cx(
prefix,
css`
flex-shrink: 0;
position: relative;
border: 0 solid ${token.colorSplit};
&:hover {
.${prefix}-toggle {
opacity: 1;
}
}
`
),
toggleLeft: cx(
`${prefix}-toggle`,
`${prefix}-toggle-left`,
commonToggle,
css`
width: ${toggleShort}px;
height: ${toggleLength}px;
left: -${offset}px;
top: 50%;
margin-top: -20px;
border-radius: 4px 0 0 4px;
border-right-width: 0;
`
),
toggleRight: cx(
`${prefix}-toggle`,
`${prefix}-toggle-right`,
commonToggle,
css`
width: ${toggleShort}px;
height: ${toggleLength}px;
right: -${offset}px;
top: 50%;
margin-top: -20px;
border-radius: 0 4px 4px 0;
border-left-width: 0;
`
),
toggleTop: cx(
`${prefix}-toggle`,
`${prefix}-toggle-top`,
commonToggle,
css`
height: ${toggleShort}px;
width: ${toggleLength}px;
top: -${offset}px;
left: 50%;
margin-left: -20px;
border-radius: 4px 4px 0 0;
border-bottom-width: 0;
`
),
toggleBottom: cx(
`${prefix}-toggle`,
`${prefix}-toggle-bottom`,
commonToggle,
css`
height: 16px;
width: ${toggleLength}px;
bottom: -${offset}px;
left: 50%;
margin-left: -20px;
border-radius: 0 0 4px 4px;
border-top-width: 0;
`
),
fixed: cx(
`${prefix}-fixed`,
css`
background: ${token.colorBgContainer};
overflow: hidden;
`
),
float: cx(
`${prefix}-float`,
css`
overflow: hidden;
border-radius: 8px;
background: ${token.colorBgElevated};
box-shadow: ${token.boxShadowSecondary};
z-index: 2000;
`
),
leftHandle: cx(
css`
${commonHandle};
&::before {
left: 50%;
width: 2px;
height: 100%;
}
`,
`${prefix}-left-handle`
),
rightHandle: cx(
css`
${commonHandle};
&::before {
right: 50%;
width: 2px;
height: 100%;
}
`,
`${prefix}-right-handle`
),
topHandle: cx(
`${prefix}-top-handle`,
css`
${commonHandle};
&::before {
top: 50%;
height: 2px;
width: 100%;
}
`
),
bottomHandle: cx(
`${prefix}-bottom-handle`,
css`
${commonHandle};
&::before {
bottom: 50%;
height: 2px;
width: 100%;
}
`
),
}
})

1
src/components/index.tsx Normal file
View File

@ -0,0 +1 @@
export * from './DraggablePanel'

View File

@ -4,14 +4,16 @@ import React, { useEffect, useState } from 'react'
import Layout from './Layout'
const App: React.FC = () => {
const [appearance, setAppearance] = useState<string>('auto')
const [appearance, setAppearance] = useState<string>('dark')
useEffect(() => {
setAppearance(String(qs.parseUrl(window.location.href).query.__theme) || 'auto')
const themeMode = String(qs.parseUrl(window.location.href).query.__theme) || 'dark'
setAppearance(themeMode)
document.body.classList.add(themeMode)
}, [])
return (
<ThemeProvider appearance={appearance}>
<Layout />
<Layout themeMode={appearance} />
</ThemeProvider>
)
}

View File

@ -1,16 +1,58 @@
import { Layout } from 'antd'
import React from 'react'
import { DraggablePanel } from '@/components/DraggablePanel'
import React, { useEffect, useRef } from 'react'
import styled from 'styled-components'
const { Header } = Layout
const View = styled.div`
width: 100vw;
height: 100vh;
display: flex;
flex-direction: row !important;
`
const LayoutView: React.FC = () => {
// const header = gradioApp().getElementById('quicksettings')
const header = document.getElementById('quicksettings')
const MainView = styled.div`
flex: 1;
height: 100%;
`
const Header = styled.div`
height: 72px;
`
const Content = styled.div``
interface LayoutViewProps {
themeMode: string
}
const LayoutView: React.FC<LayoutViewProps> = ({ themeMode }) => {
const sidebarRef: any = useRef<HTMLElement>()
const mainRef: any = useRef<HTMLElement>()
const headerRef: any = useRef<HTMLElement>()
useEffect(() => {
onUiLoaded(() => {
const sidebar = gradioApp().querySelector('#quicksettings')
const header = gradioApp().querySelector('#tabs > .tab-nav:first-child')
const main = gradioApp().querySelector('.app')
if (sidebar) sidebarRef.current?.appendChild(sidebar)
if (header) headerRef.current?.appendChild(header)
if (main) mainRef.current?.appendChild(main)
})
}, [])
return (
<Layout>
<Header>{header}</Header>
</Layout>
<View>
<MainView>
<Header className="gradio-container-3-23-0">
<div id="header" ref={headerRef} />
</Header>
<Content>
<div ref={mainRef} />
</Content>
</MainView>
<DraggablePanel placement="right">
<div ref={sidebarRef} />
</DraggablePanel>
</View>
)
}

View File

@ -1,21 +1,20 @@
import favicon from '@/script/favicon'
import formatPrompt from '@/script/format-prompt'
import promptBracketChecker from '@/script/prompt-bracket-checker'
import '@/theme/style.less'
import { createRoot } from 'react-dom/client'
import favicon from '../../script/favicon'
import formatPrompt from '../../script/format-prompt'
import promptBracketChecker from '../../script/prompt-bracket-checker'
import '../../theme/style.less'
import App from './App'
document.addEventListener('DOMContentLoaded', () => {
const root = document.createElement('div')
root.setAttribute('id', 'root')
document.body.append(root)
gradioApp().append(root)
const client = createRoot(root)
client.render(<App />)
})
onUiLoaded(() => {
favicon()
window.init = true
})
onUiUpdate(() => {
@ -23,4 +22,4 @@ onUiUpdate(() => {
promptBracketChecker()
})
export default () => null
export default () => <App />

View File

@ -1,16 +1,4 @@
#quicksettings {
position: fixed;
z-index: 1000;
flex-wrap: nowrap;
top: 12px;
display: flex;
align-items: center;
> * {
@media screen and (max-width: 640px) {
display: none;
}
}
span {
white-space: nowrap;
@ -19,17 +7,17 @@
width: 100%;
}
&::before {
content: '';
display: block;
background: var(--logo) no-repeat;
width: 129px;
height: 26px;
z-index: 1000;
margin-right: 36px;
margin-left: 16px;
margin-top: 12px;
}
//&::before {
// content: '';
// display: block;
// background: var(--logo) no-repeat;
// width: 129px;
// height: 26px;
// z-index: 1000;
// margin-right: 36px;
// margin-left: 16px;
// margin-top: 12px;
//}
.dropdown-arrow {
min-width: 16px;
@ -39,9 +27,6 @@
> div,
> fieldset {
min-width: 240px !important;
@media screen and (max-width: 640px) {
max-width: 160px;
}
}
button.svelte-1ipelgc {
@ -57,45 +42,46 @@
margin-top: 118px;
}
> .tab-nav:first-child {
position: fixed;
top: 90px;
z-index: 999;
flex-wrap: nowrap;
overflow-y: auto;
width: 100%;
}
#header {
//position: fixed;
//top: 90px;
//z-index: 999;
//flex-wrap: nowrap;
//width: 100%;
//border: none;
//&::before {
// content: '';
// display: block;
// position: fixed;
// width: 100vw;
// height: 121px;
// top: 0;
// left: 0;
// border-block-end: 1px solid var(--color-border-secondary);
// background: var(--color-header);
// backdrop-filter: blur(24px);
// z-index: -1;
//}
button {
border: none;
&::before {
content: '';
display: block;
position: fixed;
width: 100vw;
height: 121px;
top: 0;
left: 0;
border-block-end: 1px solid var(--color-border-secondary);
background: var(--color-header);
backdrop-filter: blur(24px);
z-index: -1;
border-bottom: 3px solid transparent !important;
flex: none;
transition: all 0.2s ease-in-out;
&:hover {
border: none;
border-bottom: 3px solid var(--color-primary) !important;
flex: none;
}
> button {
&.selected {
background: transparent;
border: none;
border-bottom: 3px solid transparent !important;
flex: none;
transition: all 0.2s ease-in-out;
&:hover {
border: none;
border-bottom: 3px solid var(--color-primary) !important;
flex: none;
}
&.selected {
background: transparent;
border: none;
border-bottom: 3px solid var(--color-primary) !important;
}
border-bottom: 3px solid var(--color-primary) !important;
}
}
}

View File

@ -11,7 +11,7 @@
@import 'components/container';
@import 'components/scrollbar';
@import 'components/options';
//@import 'components/header';
@import 'components/header';
@import 'components/modal';
@import 'components/sliders';
@import 'components/button';
@ -35,8 +35,11 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-size-adjust: 100%;
background-image: var(--color-body-background);
background-repeat: no-repeat;
}
code {
font-family: var(--font-family-code);
}
@ -67,13 +70,18 @@ h5 {
color: var(--color-text);
}
.dark, .light {
div {
color: var(--color-text);
}
}
/* Theme Fix */
.gradio-container {
font-size: var(--font-size);
color: var(--color-text);
margin: 0;
background-image: var(--color-body-background);
background-repeat: no-repeat;
background: transparent !important;
}
#txtimg_hr_finalres {

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,10 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["javascript"]