💄 style: Update ESLint configuration and add Prettier configuration

pull/123/head
canisminor1990 2023-05-30 21:49:57 +08:00
parent ad040e1c18
commit fefad8c569
30 changed files with 894 additions and 937 deletions

View File

@ -11,3 +11,4 @@ jest*
/docs
/dist
/javascript
style.css

View File

@ -1,7 +1,15 @@
module.exports = {
extends: require.resolve('@umijs/lint/dist/config/eslint'),
plugins: ['simple-import-sort', 'import', 'typescript-sort-keys'],
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-param-reassign': 1,
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
'typescript-sort-keys/interface': 'error',
'typescript-sort-keys/string-enum': 'error',
'react/jsx-sort-props': 'error',
'react/jsx-no-useless-fragment': 'error',
},
}
};

3
.gitpod.yml Normal file
View File

@ -0,0 +1,3 @@
tasks:
- init: pnpm install
command: pnpm run start

View File

@ -25,4 +25,4 @@ yarn-error.log
.env
.env.local
style.css
javascript/index.js
javascript/

View File

@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120
}

19
.prettierrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
pluginSearchDirs: false,
plugins: [
require.resolve('prettier-plugin-organize-imports'),
require.resolve('prettier-plugin-packagejson'),
],
printWidth: 100,
proseWrap: 'never',
singleQuote: true,
trailingComma: 'all',
overrides: [
{
files: '*.md',
options: {
proseWrap: 'preserve',
},
},
],
};

1
.stylelintignore Normal file
View File

@ -0,0 +1 @@
style.css

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@
"scripts": {
"build": "umi build",
"dev": "umi build",
"lint": "eslint \"{src,javascript}/**/*.{js,jsx,ts,tsx}\" --fix",
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
"prepare": "husky install",
"prettier": "prettier -c --write \"**/**\" && npm run lint && npm run stylelint",
"release": "semantic-release",
@ -24,25 +24,27 @@
"type-check": "tsc -p tsconfig-check.json"
},
"lint-staged": {
"*.less": [
"*.{css,less}": [
"stylelint --fix",
"prettier --write"
],
"*.{md,json}": [
"prettier --write --no-error-on-unmatched-pattern"
],
"*.json": [
"prettier --write --no-error-on-unmatched-pattern"
],
"*.jsx": [
"*.{js,jsx}": [
"stylelint --fix",
"eslint --fix",
"prettier --write"
],
"*.{ts,tsx}": [
"stylelint --fix",
"eslint --fix",
"prettier --parser=typescript --write"
]
},
"dependencies": {
"lucide-react": "^0.224.0"
},
"devDependencies": {
"@ant-design/icons": "^5",
"@commitlint/cli": "^17",
@ -67,6 +69,9 @@
"eslint": "^8",
"eslint-import-resolver-alias": "^1",
"eslint-import-resolver-typescript": "^3",
"eslint-plugin-import": "^2",
"eslint-plugin-simple-import-sort": "^10",
"eslint-plugin-typescript-sort-keys": "^2",
"husky": "^8",
"lightningcss": "^1",
"lint-staged": "^13",
@ -99,8 +104,5 @@
"use-merge-value": "^1",
"webpack-shell-plugin-next": "^2",
"zustand": "^4"
},
"dependencies": {
"lucide-react": "^0.224.0"
}
}

View File

@ -1,9 +1,10 @@
// import { useAppStore } from '@/store'
import { FloatButton } from 'antd'
import React, { useRef } from 'react'
import styled from 'styled-components'
import { useAppStore } from '@/store'
import { shallow } from 'zustand/shallow'
import { FloatButton } from 'antd';
import React, { useRef } from 'react';
import styled from 'styled-components';
import { shallow } from 'zustand/shallow';
import { useAppStore } from '@/store';
const ContentView = styled.div<{ isPromptResizable: boolean }>`
overflow-x: hidden;
@ -17,23 +18,23 @@ const ContentView = styled.div<{ isPromptResizable: boolean }>`
[id$='2img_neg_prompt'] textarea {
max-height: ${({ isPromptResizable }) => (isPromptResizable ? 'unset' : '84px')};
}
`
`;
interface ContentProps {
children: React.ReactNode
loading?: boolean
children: React.ReactNode;
loading?: boolean;
}
const Content: React.FC<ContentProps> = ({ children }) => {
const ref: any = useRef(null)
const [setting] = useAppStore((st) => [st.setting], shallow)
const ref: any = useRef(null);
const [setting] = useAppStore((st) => [st.setting], shallow);
return (
<ContentView ref={ref} isPromptResizable={setting.promotTextarea === 'resizable'}>
<ContentView isPromptResizable={setting.promotTextarea === 'resizable'} ref={ref}>
{children}
<FloatButton.BackTop target={() => ref.current} />
</ContentView>
)
}
);
};
export default React.memo(Content)
export default React.memo(Content);

View File

@ -1,38 +1,40 @@
import type { DivProps } from '@/types'
import { useHover } from 'ahooks'
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react'
import type { Enable, NumberSize, Size } from 're-resizable'
import { HandleClassName, Resizable } from 're-resizable'
import React, { useEffect, useMemo, useRef, useState } 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'
import { revesePlacement } from './utils'
import { useHover } from 'ahooks';
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react';
import type { Enable, NumberSize, Size } from 're-resizable';
import { HandleClassName, Resizable } from 're-resizable';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Center } from 'react-layout-kit';
import type { Props as RndProps } from 'react-rnd';
import useControlledState from 'use-merge-value';
const DEFAULT_HEIGHT = 180
const DEFAULT_WIDTH = 280
import type { DivProps } from '@/types';
export type placementType = 'right' | 'left' | 'top' | 'bottom'
import { useStyle } from './style';
import { revesePlacement } from './utils';
const DEFAULT_HEIGHT = 180;
const DEFAULT_WIDTH = 280;
export type placementType = 'right' | 'left' | 'top' | 'bottom';
export interface DraggablePanelProps extends DivProps {
pin?: boolean
mode?: 'fixed' | 'float'
placement: placementType
minWidth?: number
minHeight?: number
resize?: RndProps['enableResizing']
size?: Partial<Size>
onSizeChange?: (delta: NumberSize, size?: Size) => void
onSizeDragging?: (delta: NumberSize, size?: Size) => void
expandable?: boolean
expand?: boolean
defaultExpand?: boolean
onExpandChange?: (expand: boolean) => void
defaultSize?: Partial<Size>
destroyOnClose?: boolean
showHandlerWhenUnexpand?: boolean
hanlderStyle?: React.CSSProperties
defaultExpand?: boolean;
defaultSize?: Partial<Size>;
destroyOnClose?: boolean;
expand?: boolean;
expandable?: boolean;
hanlderStyle?: React.CSSProperties;
minHeight?: number;
minWidth?: number;
mode?: 'fixed' | 'float';
onExpandChange?: (expand: boolean) => void;
onSizeChange?: (delta: NumberSize, size?: Size) => void;
onSizeDragging?: (delta: NumberSize, size?: Size) => void;
pin?: boolean;
placement: placementType;
resize?: RndProps['enableResizing'];
showHandlerWhenUnexpand?: boolean;
size?: Partial<Size>;
}
const DraggablePanel: React.FC<DraggablePanelProps> = ({
@ -57,37 +59,37 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
destroyOnClose,
hanlderStyle,
}) => {
const ref = useRef(null)
const isHovering = useHover(ref)
const isVertical = placement === 'top' || placement === 'bottom'
const ref = useRef(null);
const isHovering = useHover(ref);
const isVertical = placement === 'top' || placement === 'bottom';
const { styles, cx } = useStyle('draggable-panel')
const { styles, cx } = useStyle('draggable-panel');
const [isExpand, setIsExpand] = useControlledState(defaultExpand, {
value: expand,
onChange: onExpandChange,
})
});
useEffect(() => {
if (pin) return
if (pin) return;
if (isHovering && !isExpand) {
setIsExpand(true)
setIsExpand(true);
} else if (!isHovering && isExpand) {
setIsExpand(false)
setIsExpand(false);
}
}, [pin, isHovering, isExpand])
}, [pin, isHovering, isExpand]);
const [showExpand, setShowExpand] = useState(true)
const [showExpand, setShowExpand] = useState(true);
const canResizing = resize !== false && isExpand
const canResizing = resize !== false && isExpand;
const resizeHandleClassNames: HandleClassName = useMemo(() => {
if (!canResizing) return {}
if (!canResizing) return {};
return {
[revesePlacement(placement)]: styles[`${revesePlacement(placement)}Handle`],
}
}, [canResizing, placement])
};
}, [canResizing, placement]);
const resizing = {
top: false,
@ -100,7 +102,7 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
topLeft: false,
[revesePlacement(placement)]: true,
...(resize as Enable),
}
};
const defaultSize: Size = useMemo(() => {
if (isVertical)
@ -108,14 +110,14 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
width: '100%',
height: DEFAULT_HEIGHT,
...customizeDefaultSize,
}
};
return {
width: DEFAULT_WIDTH,
height: '100%',
...customizeDefaultSize,
}
}, [isVertical])
};
}, [isVertical]);
const sizeProps = isExpand
? {
@ -132,20 +134,20 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
: {
minWidth: 0,
size: { width: 0 },
}
};
const { Arrow, className: arrowPlacement } = useMemo(() => {
switch (placement) {
case 'top':
return { className: 'Bottom', Arrow: ChevronDown }
return { className: 'Bottom', Arrow: ChevronDown };
case 'bottom':
return { className: 'Top', Arrow: ChevronUp }
return { className: 'Top', Arrow: ChevronUp };
case 'right':
return { className: 'Left', Arrow: ChevronLeft }
return { className: 'Left', Arrow: ChevronLeft };
case 'left':
return { className: 'Right', Arrow: ChevronRight }
return { className: 'Right', Arrow: ChevronRight };
}
}, [styles, placement])
}, [styles, placement]);
const handler = (
// @ts-ignore
@ -155,62 +157,65 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
style={{ opacity: isExpand ? (!pin ? 0 : undefined) : showHandlerWhenUnexpand ? 1 : 0 }}
>
<Center
style={hanlderStyle}
onClick={() => {
setIsExpand(!isExpand)
setIsExpand(!isExpand);
}}
style={hanlderStyle}
>
<div className={styles.handlerIcon} style={{ transform: `rotate(${isExpand ? 180 : 0}deg)` }}>
<div
className={styles.handlerIcon}
style={{ transform: `rotate(${isExpand ? 180 : 0}deg)` }}
>
<Arrow size={16} strokeWidth={1.5} />
</div>
</Center>
</Center>
)
);
const inner = (
// @ts-ignore
<Resizable
{...sizeProps}
style={style}
className={styles.panel}
enable={canResizing ? (resizing as Enable) : undefined}
handleClasses={resizeHandleClassNames}
onResizeStop={(e, direction, ref, delta) => {
setShowExpand(true)
onSizeChange?.(delta, {
width: ref.style.width,
height: ref.style.height,
})
}}
onResizeStart={() => {
setShowExpand(false)
}}
onResize={(_, direction, ref, delta) => {
onSizeDragging?.(delta, {
width: ref.style.width,
height: ref.style.height,
})
});
}}
onResizeStart={() => {
setShowExpand(false);
}}
onResizeStop={(e, direction, ref, delta) => {
setShowExpand(true);
onSizeChange?.(delta, {
width: ref.style.width,
height: ref.style.height,
});
}}
style={style}
>
{children}
</Resizable>
)
);
return (
<div
ref={ref}
className={cx(
styles.container,
// @ts-ignore
styles[mode === 'fixed' ? 'fixed' : `${placement}Float`],
className
className,
)}
ref={ref}
style={{ [`border${arrowPlacement}Width`]: 1 }}
>
{expandable && showExpand && handler}
{destroyOnClose ? isExpand && inner : inner}
</div>
)
}
);
};
export default React.memo(DraggablePanel)
export default React.memo(DraggablePanel);

View File

@ -1,26 +1,28 @@
import { DraggablePanel } from '@/components'
import { useAppStore } from '@/store'
import { ZoomInOutlined } from '@ant-design/icons'
import { Slider } from 'antd'
import { useResponsive } from 'antd-style'
import React, { CSSProperties, useEffect, useState } from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import { shallow } from 'zustand/shallow'
import { ZoomInOutlined } from '@ant-design/icons';
import { Slider } from 'antd';
import { useResponsive } from 'antd-style';
import React, { CSSProperties, useEffect, useState } from 'react';
import styled, { createGlobalStyle } from 'styled-components';
import { shallow } from 'zustand/shallow';
import { DraggablePanel } from '@/components';
import { useAppStore } from '@/store';
const GlobalStyle = createGlobalStyle`
button#txt2img_extra_networks,
button#img2img_extra_networks {
display: none !important;
}
`
`;
const View = styled.div`
overflow: hidden;
display: flex;
flex-direction: column;
height: -webkit-fill-available;
height: -moz-available;
`
`;
const SidebarView = styled.div<{ size: number }>`
overflow-x: hidden;
@ -47,7 +49,7 @@ const SidebarView = styled.div<{ size: number }>`
height: ${({ size }) => size * 1.5}px !important;
}
}
`
`;
const Footer = styled.div`
display: flex;
@ -58,57 +60,57 @@ const Footer = styled.div`
padding: 8px 16px;
border-top: 1px solid var(--color-border);
`
`;
const ZoomSlider = styled(Slider)`
flex: 1;
margin-left: 16px;
`
`;
interface SidebarProps {
children: React.ReactNode
loading?: boolean
style?: CSSProperties
children: React.ReactNode;
loading?: boolean;
style?: CSSProperties;
}
const Sidebar: React.FC<SidebarProps> = ({ children, style }) => {
const { mobile } = useResponsive()
const [setting] = useAppStore((st) => [st.setting], shallow)
const [mode] = useState<'fixed' | 'float'>(setting.extraNetworkFixedMode)
const [expand, setExpand] = useState<boolean>(setting.extraNetworkSidebarExpand)
const [size, setSize] = useState<number>(setting?.extraNetworkCardSize || 86)
const { mobile } = useResponsive();
const [setting] = useAppStore((st) => [st.setting], shallow);
const [mode] = useState<'fixed' | 'float'>(setting.extraNetworkFixedMode);
const [expand, setExpand] = useState<boolean>(setting.extraNetworkSidebarExpand);
const [size, setSize] = useState<number>(setting?.extraNetworkCardSize || 86);
useEffect(() => {
if (mobile) setExpand(false)
}, [])
if (mobile) setExpand(false);
}, []);
return (
<>
<GlobalStyle />
<DraggablePanel
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
expand={expand}
minWidth={setting.extraNetworkSidebarWidth}
mode={mode}
onExpandChange={setExpand}
pin={mode === 'fixed'}
placement="right"
style={{
display: 'flex',
flexDirection: 'column',
...style,
}}
placement="right"
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
minWidth={setting.extraNetworkSidebarWidth}
expand={expand}
onExpandChange={setExpand}
>
<View>
<SidebarView size={size}>{children}</SidebarView>
<Footer>
<ZoomInOutlined />
<ZoomSlider defaultValue={size} step={8} max={256} min={64} onChange={setSize} />
<ZoomSlider defaultValue={size} max={256} min={64} onChange={setSize} step={8} />
</Footer>
</View>
</DraggablePanel>
</>
)
}
);
};
export default React.memo(Sidebar)
export default React.memo(Sidebar);

View File

@ -1,28 +1,28 @@
import React, { useEffect } from 'react'
import React, { useEffect } from 'react';
interface GiscusProps {
themeMode: 'light' | 'dark'
themeMode: 'light' | 'dark';
}
const Giscus: React.FC<GiscusProps> = ({ themeMode }) => {
useEffect(() => {
// giscus
const giscus: HTMLScriptElement = document.createElement('script')
giscus.src = 'https://giscus.app/client.js'
giscus.setAttribute('data-repo', 'canisminor1990/sd-webui-kitchen-theme')
giscus.setAttribute('data-repo-id', 'R_kgDOJCPcNg')
giscus.setAttribute('data-mapping', 'number')
giscus.setAttribute('data-term', '53')
giscus.setAttribute('data-reactions-enabled', '1')
giscus.setAttribute('data-emit-metadata', '0')
giscus.setAttribute('data-input-position', 'bottom')
giscus.setAttribute('data-theme', themeMode)
giscus.setAttribute('data-lang', 'en')
giscus.crossOrigin = 'anonymous'
giscus.async = true
document.getElementsByTagName('head')[0].appendChild(giscus)
}, [])
const giscus: HTMLScriptElement = document.createElement('script');
giscus.src = 'https://giscus.app/client.js';
giscus.setAttribute('data-repo', 'canisminor1990/sd-webui-kitchen-theme');
giscus.setAttribute('data-repo-id', 'R_kgDOJCPcNg');
giscus.setAttribute('data-mapping', 'number');
giscus.setAttribute('data-term', '53');
giscus.setAttribute('data-reactions-enabled', '1');
giscus.setAttribute('data-emit-metadata', '0');
giscus.setAttribute('data-input-position', 'bottom');
giscus.setAttribute('data-theme', themeMode);
giscus.setAttribute('data-lang', 'en');
giscus.crossOrigin = 'anonymous';
giscus.async = true;
document.getElementsByTagName('head')[0].appendChild(giscus);
}, []);
return <div id="giscus" className="giscus" />
}
return <div className="giscus" id="giscus" />;
};
export default React.memo(Giscus)
export default React.memo(Giscus);

View File

@ -1,14 +1,21 @@
import React from 'react'
import { darkLogo, lightLogo } from './style'
import React from 'react';
import { darkLogo, lightLogo } from './style';
interface LogoProps {
size?: number
style?: React.CSSProperties
themeMode: 'dark' | 'light'
size?: number;
style?: React.CSSProperties;
themeMode: 'dark' | 'light';
}
const Logo: React.FC<LogoProps> = ({ size = 20, style, themeMode }) => {
return <img src={themeMode === 'dark' ? darkLogo : lightLogo} alt="logo" style={{ height: size, ...style }} />
}
return (
<img
alt="logo"
src={themeMode === 'dark' ? darkLogo : lightLogo}
style={{ height: size, ...style }}
/>
);
};
export default React.memo(Logo)
export default React.memo(Logo);

View File

@ -1,56 +1,69 @@
import React, { useEffect, useState } from 'react'
import type { MenuProps } from 'antd'
import { Menu } from 'antd'
import styled from 'styled-components'
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
const NavBar = styled(Menu)`
flex: 1;
border: none;
line-height: 1;
overflow: hidden;
flex: 1;
line-height: 1;
border: none;
.ant-menu-overflow-item {
padding: 8px 12px;
border-radius: 4px !important;
color: var(--color-text-secondary);
&:after {
display: none; !important;
}
&:hover {
color: var(--color-text);
background: var(--color-fill-tertiary);
}
&.ant-menu-item-selected {
font-weight: 600;
color: var(--color-text);
}
padding: 8px 12px;
color: var(--color-text-secondary);
border-radius: 4px !important;
&:after {
display: none !important;
}
&:hover {
color: var(--color-text);
background: var(--color-fill-tertiary);
}
&.ant-menu-item-selected {
font-weight: 600;
color: var(--color-text);
}
}
`
`;
const Nav: React.FC = () => {
const [items, setItems] = useState<MenuProps['items']>([])
const [items, setItems] = useState<MenuProps['items']>([]);
useEffect(() => {
onUiLoaded(() => {
const buttons = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button')
const list: MenuProps['items'] | any = []
const buttons = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button');
const list: MenuProps['items'] | any = [];
buttons.forEach((button: HTMLButtonElement | any, index) => {
button.id = `kitchen-nav-${index}`
button.id = `kitchen-nav-${index}`;
list.push({
label: button.textContent,
key: String(index),
})
})
setItems(list)
})
}, [])
});
});
setItems(list);
});
}, []);
const onClick: MenuProps['onClick'] = (e: any) => {
const buttons: HTMLButtonElement[] | any = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button')
buttons[Number(e.key)]?.click()
}
const buttons: HTMLButtonElement[] | any = gradioApp().querySelectorAll(
'#tabs > .tab-nav:first-child button',
);
buttons[Number(e.key)]?.click();
};
return <NavBar defaultActiveFirst defaultSelectedKeys={['0']} onClick={onClick} mode="horizontal" items={items} />
}
return (
<NavBar
defaultActiveFirst
defaultSelectedKeys={['0']}
items={items}
mode="horizontal"
onClick={onClick}
/>
);
};
export default Nav
export default Nav;

View File

@ -1,9 +1,10 @@
import { WebuiSetting, defaultSetting, useAppStore } from '@/store'
import { SettingOutlined } from '@ant-design/icons'
import { Button, Divider, Form, InputNumber, Popover, Space, Switch, Segmented } from 'antd'
import React, { useCallback } from 'react'
import styled from 'styled-components'
import { shallow } from 'zustand/shallow'
import { SettingOutlined } from '@ant-design/icons';
import { Button, Divider, Form, InputNumber, Popover, Segmented, Space, Switch } from 'antd';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import { shallow } from 'zustand/shallow';
import { defaultSetting, useAppStore, WebuiSetting } from '@/store';
/******************************************************
*********************** Style *************************
@ -18,42 +19,46 @@ const FormItem = styled(Form.Item)`
flex-grow: unset !important;
}
}
`
`;
const Title = styled.div`
font-size: 16px;
font-weight: 600;
`
`;
const SubTitle = styled.div`
margin-bottom: 4px;
font-size: 14px;
font-weight: 500;
`
`;
/******************************************************
************************* Dom *************************
******************************************************/
const Setting: React.FC = () => {
const [setting, onSetSetting] = useAppStore((st) => [st.setting, st.onSetSetting], shallow)
const [setting, onSetSetting] = useAppStore((st) => [st.setting, st.onSetSetting], shallow);
const onReset = useCallback(() => {
onSetSetting(defaultSetting)
gradioApp().getElementById('settings_restart_gradio')?.click()
}, [])
onSetSetting(defaultSetting);
gradioApp().getElementById('settings_restart_gradio')?.click();
}, []);
const onFinish = useCallback((value: WebuiSetting) => {
onSetSetting(value)
gradioApp().getElementById('settings_restart_gradio')?.click()
}, [])
onSetSetting(value);
gradioApp().getElementById('settings_restart_gradio')?.click();
}, []);
return (
<Popover
title={<Title> Setting</Title>}
trigger="click"
content={
<Form size="small" initialValues={setting} layout="horizontal" onFinish={onFinish} style={{ maxWidth: 260 }}>
<Form
initialValues={setting}
layout="horizontal"
onFinish={onFinish}
size="small"
style={{ maxWidth: 260 }}
>
<Divider style={{ margin: '4px 0 8px' }} />
<SubTitle>Promot Textarea</SubTitle>
<FormItem label="Display mode" name="promotTextarea">
@ -98,17 +103,19 @@ const Setting: React.FC = () => {
<Button htmlType="button" onClick={onReset} style={{ borderRadius: 4 }}>
Reset
</Button>
<Button type="primary" htmlType="submit" style={{ borderRadius: 4 }}>
<Button htmlType="submit" style={{ borderRadius: 4 }} type="primary">
Apply and restart UI
</Button>
</Space>
</FormItem>
</Form>
}
title={<Title> Setting</Title>}
trigger="click"
>
<Button title="Setting" icon={<SettingOutlined />} />
<Button icon={<SettingOutlined />} title="Setting" />
</Popover>
)
}
);
};
export default React.memo(Setting)
export default React.memo(Setting);

View File

@ -1,17 +1,19 @@
import { DraggablePanel } from '@/components'
import { useAppStore } from '@/store'
import { BoldOutlined, GithubOutlined } from '@ant-design/icons'
import { Button, Modal, Space } from 'antd'
import { useResponsive } from 'antd-style'
import qs from 'query-string'
import React, { useCallback, useEffect, useState } from 'react'
import styled from 'styled-components'
import { shallow } from 'zustand/shallow'
import Giscus from './Giscus'
import Logo from './Logo'
import Setting from './Setting'
import Nav from './Nav'
import { civitaiLogo, themeIcon } from './style'
import { BoldOutlined, GithubOutlined } from '@ant-design/icons';
import { Button, Modal, Space } from 'antd';
import { useResponsive } from 'antd-style';
import qs from 'query-string';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { shallow } from 'zustand/shallow';
import { DraggablePanel } from '@/components';
import { useAppStore } from '@/store';
import Giscus from './Giscus';
import Logo from './Logo';
import Nav from './Nav';
import Setting from './Setting';
import { civitaiLogo, themeIcon } from './style';
/******************************************************
*********************** Style *************************
@ -27,48 +29,52 @@ const HeaderView = styled.div`
height: -webkit-fill-available;
height: -moz-available;
padding: 16px 24px;
`
`;
/******************************************************
************************* Dom *************************
******************************************************/
interface HeaderProps {
children: React.ReactNode
children: React.ReactNode;
}
const Header: React.FC<HeaderProps> = ({ children }) => {
const [themeMode] = useAppStore((st) => [st.themeMode], shallow)
const { mobile } = useResponsive()
const [expand, setExpand] = useState<boolean>(true)
const [isModalOpen, setIsModalOpen] = useState(false)
const [themeMode] = useAppStore((st) => [st.themeMode], shallow);
const { mobile } = useResponsive();
const [expand, setExpand] = useState<boolean>(true);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
if (mobile) setExpand(false)
}, [])
if (mobile) setExpand(false);
}, []);
const handleSetTheme = useCallback(() => {
const theme = themeMode === 'light' ? 'dark' : 'light'
const gradioURL = qs.parseUrl(window.location.href)
gradioURL.query.__theme = theme
window.location.replace(qs.stringifyUrl(gradioURL))
}, [themeMode])
const theme = themeMode === 'light' ? 'dark' : 'light';
const gradioURL = qs.parseUrl(window.location.href);
gradioURL.query.__theme = theme;
window.location.replace(qs.stringifyUrl(gradioURL));
}, [themeMode]);
const showModal = () => setIsModalOpen(true)
const showModal = () => setIsModalOpen(true);
const handleCancel = () => setIsModalOpen(false)
const handleCancel = () => setIsModalOpen(false);
return (
<>
<DraggablePanel
placement="top"
defaultSize={{ height: 'auto' }}
minHeight={64}
expand={expand}
minHeight={64}
onExpandChange={setExpand}
placement="top"
>
<HeaderView id="header" style={{ flexDirection: mobile ? 'column' : 'row' }}>
<a href="https://github.com/canisminor1990/sd-webui-kitchen-theme" target="_blank" rel="noreferrer">
<a
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
rel="noreferrer"
target="_blank"
>
<Logo themeMode={themeMode} />
</a>
@ -76,35 +82,43 @@ const Header: React.FC<HeaderProps> = ({ children }) => {
{children}
<Space.Compact>
<a href="https://civitai.com/" target="_blank" rel="noreferrer">
<Button title="Civitai" icon={civitaiLogo} />
<a href="https://civitai.com/" rel="noreferrer" target="_blank">
<Button icon={civitaiLogo} title="Civitai" />
</a>
<a href="https://www.birme.net/?target_width=512&target_height=512" target="_blank" rel="noreferrer">
<Button title="Birme" icon={<BoldOutlined />} />
<a
href="https://www.birme.net/?target_width=512&target_height=512"
rel="noreferrer"
target="_blank"
>
<Button icon={<BoldOutlined />} title="Birme" />
</a>
<Button title="Feedback" icon={<GithubOutlined />} onClick={showModal} />
<Button icon={<GithubOutlined />} onClick={showModal} title="Feedback" />
<Setting />
<Button title="Switch Theme" icon={themeIcon[themeMode]} onClick={handleSetTheme} />
<Button icon={themeIcon[themeMode]} onClick={handleSetTheme} title="Switch Theme" />
</Space.Compact>
</HeaderView>
</DraggablePanel>
<Modal
footer={null}
onCancel={handleCancel}
open={isModalOpen}
title={
<a href="https://github.com/canisminor1990/sd-webui-kitchen-theme" target="_blank" rel="noreferrer">
<a
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
rel="noreferrer"
target="_blank"
>
<Space>
<GithubOutlined />
{'canisminor1990/sd-webui-kitchen-theme'}
</Space>
</a>
}
open={isModalOpen}
onCancel={handleCancel}
footer={null}
>
<Giscus themeMode={themeMode} />
</Modal>
</>
)
}
);
};
export default React.memo(Header)
export default React.memo(Header);

View File

@ -1,27 +1,29 @@
export const themeIcon = {
light: (
<span role="img" className="anticon anticon-github">
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
<span className="anticon anticon-github" role="img">
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
<path d="M8 13a1 1 0 0 1 1 1v1a1 1 0 1 1-2 0v-1a1 1 0 0 1 1-1ZM8 3a1 1 0 0 1-1-1V1a1 1 0 1 1 2 0v1a1 1 0 0 1-1 1Zm7 4a1 1 0 1 1 0 2h-1a1 1 0 1 1 0-2h1ZM3 8a1 1 0 0 1-1 1H1a1 1 0 1 1 0-2h1a1 1 0 0 1 1 1Zm9.95 3.536.707.707a1 1 0 0 1-1.414 1.414l-.707-.707a1 1 0 0 1 1.414-1.414Zm-9.9-7.072-.707-.707a1 1 0 0 1 1.414-1.414l.707.707A1 1 0 0 1 3.05 4.464Zm9.9 0a1 1 0 0 1-1.414-1.414l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707Zm-9.9 7.072a1 1 0 0 1 1.414 1.414l-.707.707a1 1 0 0 1-1.414-1.414l.707-.707ZM8 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0 6.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5Z"></path>
</svg>
</span>
),
dark: (
<span role="img" className="anticon anticon-github">
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
<span className="anticon anticon-github" role="img">
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
<path d="M8.218 1.455c3.527.109 6.327 3.018 6.327 6.545 0 3.6-2.945 6.545-6.545 6.545a6.562 6.562 0 0 1-6.036-4h.218c3.6 0 6.545-2.945 6.545-6.545 0-.91-.182-1.745-.509-2.545m0-1.455c-.473 0-.909.218-1.2.618-.29.4-.327.946-.145 1.382.254.655.4 1.31.4 2 0 2.8-2.291 5.09-5.091 5.09h-.218c-.473 0-.91.22-1.2.62-.291.4-.328.945-.146 1.38C1.891 14.074 4.764 16 8 16c4.4 0 8-3.6 8-8a7.972 7.972 0 0 0-7.745-8h-.037Z"></path>
</svg>
</span>
),
}
};
export const darkLogo = 'https://gw.alipayobjects.com/zos/bmw-prod/9ecb2822-1592-4cb0-a087-ce0097fef2ca.svg'
export const lightLogo = 'https://gw.alipayobjects.com/zos/bmw-prod/e146116d-c65a-4306-a3d2-bb8d05e1c49b.svg'
export const darkLogo =
'https://gw.alipayobjects.com/zos/bmw-prod/9ecb2822-1592-4cb0-a087-ce0097fef2ca.svg';
export const lightLogo =
'https://gw.alipayobjects.com/zos/bmw-prod/e146116d-c65a-4306-a3d2-bb8d05e1c49b.svg';
export const civitaiLogo = (
<span role="img" className="anticon civitai">
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
<span className="anticon civitai" role="img">
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
<path d="M2 4.5L8 1l6 3.5v7L8 15l-6-3.5v-7zm6-1.194L3.976 5.653v4.694L8 12.694l4.024-2.347V5.653L8 3.306zm0 1.589l2.662 1.552v.824H9.25L8 6.54l-1.25.73v1.458L8 9.46l1.25-.73h1.412v.824L8 11.105 5.338 9.553V6.447L8 4.895z" />
</svg>
</span>
)
);

View File

@ -1,18 +1,19 @@
import React, { useCallback, useState } from 'react'
import styled from 'styled-components'
import TagList, { PromptType, TagItem } from './TagList'
import { formatPrompt } from './utils'
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import TagList, { PromptType, TagItem } from './TagList';
import { formatPrompt } from './utils';
const View = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`
`;
const BtnGroup = styled.div`
display: flex;
gap: 8px;
`
`;
const Btn = styled.button`
cursor: pointer;
@ -33,53 +34,54 @@ const Btn = styled.button`
background: var(--button-secondary-background-fill);
border: var(--button-border-width) solid var(--button-secondary-border-color);
border-radius: var(--input-radius);
`
`;
interface PromptProps {
type: PromptType
type: PromptType;
}
const Prompt: React.FC<PromptProps> = ({ type }) => {
const [tags, setTags] = useState<TagItem[]>([])
const [tags, setTags] = useState<TagItem[]>([]);
const id = type === 'positive' ? "[id$='2img_prompt'] textarea" : "[id$='2img_neg_prompt'] textarea"
const id =
type === 'positive' ? "[id$='2img_prompt'] textarea" : "[id$='2img_neg_prompt'] textarea";
const getValue = useCallback(() => {
try {
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
if (textarea) setTags(formatPrompt(textarea.value))
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
if (textarea) setTags(formatPrompt(textarea.value));
} catch {}
}, [])
}, []);
const setValue = useCallback(() => {
try {
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
if (textarea) textarea.value = tags.map((t) => t.text).join(', ')
updateInput(textarea)
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
if (textarea) textarea.value = tags.map((t) => t.text).join(', ');
updateInput(textarea);
} catch {}
}, [tags])
}, [tags]);
const setCurrentValue = useCallback((currentTags: TagItem[]) => {
try {
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ')
updateInput(textarea)
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ');
updateInput(textarea);
} catch {}
}, [])
}, []);
return (
<View>
<TagList type={type} tags={tags} setTags={setTags} setValue={setCurrentValue} />
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
<BtnGroup>
<Btn title="Load Prompt" onClick={getValue}>
<Btn onClick={getValue} title="Load Prompt">
🔄
</Btn>
<Btn title="Set Prompt" onClick={setValue}>
<Btn onClick={setValue} title="Set Prompt">
</Btn>
</BtnGroup>
</View>
)
}
);
};
export default React.memo(Prompt)
export default React.memo(Prompt);

View File

@ -1,13 +1,14 @@
import React from 'react'
import styled from 'styled-components'
import Prompt from './Prompt'
import React from 'react';
import styled from 'styled-components';
import Prompt from './Prompt';
const View = styled.div`
display: flex;
flex-direction: column;
gap: 1em;
margin-bottom: 1em;
`
`;
const Desc = styled.div`
position: relative;
@ -24,7 +25,7 @@ const Desc = styled.div`
background: var(--block-title-background-fill);
border: solid var(--block-title-border-width) var(--block-title-border-color);
border-radius: var(--block-title-radius);
`
`;
const PromptGroup: React.FC = () => {
return (
@ -34,7 +35,7 @@ const PromptGroup: React.FC = () => {
<Desc>Negative</Desc>
<Prompt type="negative" />
</View>
)
}
);
};
export default React.memo(PromptGroup)
export default React.memo(PromptGroup);

View File

@ -1,23 +1,24 @@
import React, { useCallback, useMemo } from 'react'
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input'
import styled from 'styled-components'
import { genTagType, suggestions } from './utils'
import React, { useCallback, useMemo } from 'react';
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input';
import styled from 'styled-components';
import { genTagType, suggestions } from './utils';
export interface TagItem {
id: string
text: string
className?: string
className?: string;
id: string;
text: string;
}
export type PromptType = 'positive' | 'negative'
export type PromptType = 'positive' | 'negative';
interface ReactTagsProps extends WithContextProps {
editable?: boolean
onTagUpdate?: (editIndex: number, tag: TagItem) => void
editable?: boolean;
onTagUpdate?: (editIndex: number, tag: TagItem) => void;
}
// @ts-ignore
const ReactTags: React.FC<ReactTagsProps> = WithContext
const ReactTags: React.FC<ReactTagsProps> = WithContext;
const View = styled.div<{ type: PromptType }>`
/* Styles for the input */
@ -143,81 +144,81 @@ const View = styled.div<{ type: PromptType }>`
background: var(--orange-2) !important;
border-color: var(--orange-3) !important;
}
`
`;
const KeyCodes = {
comma: 188,
enter: 13,
}
};
const delimiters = [KeyCodes.comma, KeyCodes.enter]
const delimiters = [KeyCodes.comma, KeyCodes.enter];
interface TagListProps {
tags: TagItem[]
setTags: (tags: TagItem[]) => void
type: PromptType
setValue: (tags: TagItem[]) => void
setTags: (tags: TagItem[]) => void;
setValue: (tags: TagItem[]) => void;
tags: TagItem[];
type: PromptType;
}
const TagList: React.FC<TagListProps> = ({ tags, setTags, type, setValue }) => {
const handleDelete = useCallback(
(i: number) => {
const newTags = tags.filter((tag, index) => index !== i)
setTags(newTags)
setValue(newTags)
const newTags = tags.filter((tag, index) => index !== i);
setTags(newTags);
setValue(newTags);
},
[tags]
)
[tags],
);
const handleAddition = useCallback(
(tag: TagItem) => {
const newTags = [...tags, genTagType(tag)]
setTags(newTags)
setValue(newTags)
const newTags = [...tags, genTagType(tag)];
setTags(newTags);
setValue(newTags);
},
[tags]
)
[tags],
);
const handleDrag = useCallback(
(tag: TagItem, currPos: number, newPos: number) => {
const newTags = tags.slice()
newTags.splice(currPos, 1)
newTags.splice(newPos, 0, genTagType(tag))
setTags(newTags)
setValue(newTags)
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, genTagType(tag));
setTags(newTags);
setValue(newTags);
},
[tags]
)
[tags],
);
const handleTagUpdate = useCallback(
(i: number, tag: TagItem) => {
const newTags = [...tags]
newTags[i] = genTagType(tag)
setTags(newTags)
setValue(newTags)
const newTags = [...tags];
newTags[i] = genTagType(tag);
setTags(newTags);
setValue(newTags);
},
[tags]
)
[tags],
);
const suggestionData = useMemo(() => suggestions[type], [type])
const suggestionData = useMemo(() => suggestions[type], [type]);
return (
<View type={type}>
<ReactTags
tags={tags}
autocomplete
delimiters={delimiters}
handleDelete={handleDelete}
editable
handleAddition={handleAddition}
handleDelete={handleDelete}
handleDrag={handleDrag}
inline
inputFieldPosition="bottom"
onTagUpdate={handleTagUpdate}
suggestions={suggestionData}
inputFieldPosition="bottom"
inline
autocomplete
editable
tags={tags}
/>
</View>
)
}
);
};
export default React.memo(TagList)
export default React.memo(TagList);

View File

@ -1,10 +1,12 @@
import { DraggablePanel } from '@/components'
import { useAppStore } from '@/store'
import { useResponsive } from 'antd-style'
import React, { CSSProperties, useEffect, useState } from 'react'
import styled from 'styled-components'
import { shallow } from 'zustand/shallow'
import PromptGroup from './PromptGroup'
import { useResponsive } from 'antd-style';
import React, { CSSProperties, useEffect, useState } from 'react';
import styled from 'styled-components';
import { shallow } from 'zustand/shallow';
import { DraggablePanel } from '@/components';
import { useAppStore } from '@/store';
import PromptGroup from './PromptGroup';
const SidebarView = styled.div`
overflow-x: hidden;
@ -13,45 +15,45 @@ const SidebarView = styled.div`
height: -webkit-fill-available;
height: -moz-available;
padding: 16px;
`
`;
interface SidebarProps {
children: React.ReactNode
loading?: boolean
style?: CSSProperties
children: React.ReactNode;
loading?: boolean;
style?: CSSProperties;
}
const Sidebar: React.FC<SidebarProps> = ({ children, loading, style }) => {
const [setting] = useAppStore((st) => [st.setting], shallow)
const [mode] = useState<'fixed' | 'float'>(setting.sidebarFixedMode)
const { mobile } = useResponsive()
const [expand, setExpand] = useState<boolean>(setting.sidebarExpand)
const [setting] = useAppStore((st) => [st.setting], shallow);
const [mode] = useState<'fixed' | 'float'>(setting.sidebarFixedMode);
const { mobile } = useResponsive();
const [expand, setExpand] = useState<boolean>(setting.sidebarExpand);
useEffect(() => {
if (mobile) setExpand(false)
}, [])
if (mobile) setExpand(false);
}, []);
return (
<DraggablePanel
defaultSize={{ width: setting.sidebarWidth }}
expand={expand}
minWidth={setting.sidebarWidth}
mode={mode}
onExpandChange={setExpand}
pin={mode === 'fixed'}
placement="left"
style={{
display: 'flex',
flexDirection: 'column',
...style,
}}
placement="left"
defaultSize={{ width: setting.sidebarWidth }}
minWidth={setting.sidebarWidth}
expand={expand}
onExpandChange={setExpand}
>
<SidebarView>
{!loading && <PromptGroup />}
{children}
</SidebarView>
</DraggablePanel>
)
}
);
};
export default React.memo(Sidebar)
export default React.memo(Sidebar);

View File

@ -1,47 +1,48 @@
import negativeData from '@/data/negative.json'
import positiveData from '@/data/positive.json'
import { Converter } from '@/script/formatPrompt'
import { TagItem } from './TagList'
import negativeData from '@/data/negative.json';
import positiveData from '@/data/positive.json';
import { Converter } from '@/script/formatPrompt';
import { TagItem } from './TagList';
export const genTagType = (tag: TagItem): TagItem => {
const newTag = tag
const newTag = tag;
if (newTag.text.includes('<lora')) {
newTag.className = 'ReactTags__lora'
newTag.className = 'ReactTags__lora';
} else if (newTag.text.includes('<hypernet')) {
newTag.className = 'ReactTags__hypernet'
newTag.className = 'ReactTags__hypernet';
} else if (newTag.text.includes('<embedding')) {
newTag.className = 'ReactTags__embedding'
newTag.className = 'ReactTags__embedding';
} else {
newTag.className = undefined
newTag.className = undefined;
}
return newTag
}
return newTag;
};
export const formatPrompt = (value: string) => {
const text = Converter.convertStr(value)
const text = Converter.convertStr(value);
const textArray = Converter.convertStr2Array(text).map((item) => {
if (item.includes('<')) return item
if (item.includes('<')) return item;
const newItem = item
.replace(/\s+/g, ' ')
.replace(/|\.\|。/g, ',')
.replace(/“||”|"|\/'/g, '')
.replace(/, /g, ',')
.replace(/,,/g, ',')
.replace(/,/g, ', ')
return Converter.convertStr2Array(newItem).join(', ')
})
return textArray.map((tag) => genTagType({ id: tag, text: tag }))
}
.replace(/,/g, ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return textArray.map((tag) => genTagType({ id: tag, text: tag }));
};
const genSuggestions = (array: string[]) =>
array.map((text) => {
return {
id: text,
text,
}
})
};
});
export const suggestions = {
positive: genSuggestions(positiveData),
negative: genSuggestions(negativeData),
}
};

View File

@ -1,13 +1,14 @@
import { Content, ExtraNetworkSidebar, Header, Sidebar } from '@/components'
import dragablePanel from '@/script/draggablePanel'
import replaceIcon from '@/script/replaceIcon'
import { useAppStore } from '@/store'
import { Spin } from 'antd'
import { useResponsive } from 'antd-style'
import React, { useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
import { shallow } from 'zustand/shallow'
import civitaiHelperFix from '@/script/civitaiHelperFix'
import { Spin } from 'antd';
import { useResponsive } from 'antd-style';
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { shallow } from 'zustand/shallow';
import { Content, ExtraNetworkSidebar, Header, Sidebar } from '@/components';
import civitaiHelperFix from '@/script/civitaiHelperFix';
import dragablePanel from '@/script/draggablePanel';
import replaceIcon from '@/script/replaceIcon';
import { useAppStore } from '@/store';
const View = styled.div`
position: relative;
@ -16,7 +17,7 @@ const View = styled.div`
display: flex;
flex: 1;
flex-direction: row !important;
`
`;
const MainView = styled.div`
position: relative;
@ -28,7 +29,7 @@ const MainView = styled.div`
width: 100vw;
height: 100vh;
`
`;
const LoadingBox = styled.div`
display: flex;
@ -37,74 +38,76 @@ const LoadingBox = styled.div`
width: 100%;
height: 100%;
`
`;
const App: React.FC = () => {
const [currentTab, setCurrentTab, setting] = useAppStore(
(st) => [st.currentTab, st.setCurrentTab, st.setting],
shallow
)
const { mobile } = useResponsive()
const [loading, setLoading] = useState(true)
const [extraLoading, setExtraLoading] = useState(true)
const sidebarRef: any = useRef<HTMLElement>()
const mainRef: any = useRef<HTMLElement>()
const txt2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>()
const img2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>()
shallow,
);
const { mobile } = useResponsive();
const [loading, setLoading] = useState(true);
const [extraLoading, setExtraLoading] = useState(true);
const sidebarRef: any = useRef<HTMLElement>();
const mainRef: any = useRef<HTMLElement>();
const txt2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>();
const img2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>();
useEffect(() => {
onUiLoaded(() => {
// Content
const main = gradioApp().querySelector('.app')
if (main) mainRef.current?.appendChild(main)
if (!mobile) dragablePanel()
const main = gradioApp().querySelector('.app');
if (main) mainRef.current?.appendChild(main);
if (!mobile) dragablePanel();
// Sidebar
const sidebar = gradioApp().querySelector('#quicksettings')
if (sidebar) sidebarRef.current?.appendChild(sidebar)
const sidebar = gradioApp().querySelector('#quicksettings');
if (sidebar) sidebarRef.current?.appendChild(sidebar);
// ExtraNetworkSidebar
if (setting.enableExtraNetworkSidebar) {
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks')
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks')
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks');
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks');
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
txt2imgExtraNetworkSidebarRef.current?.appendChild(txt2imgExtraNetworks)
img2imgExtraNetworkSidebarRef.current?.appendChild(img2imgExtraNetworks)
txt2imgExtraNetworkSidebarRef.current?.appendChild(txt2imgExtraNetworks);
img2imgExtraNetworkSidebarRef.current?.appendChild(img2imgExtraNetworks);
}
}
// Other
if (setting.svgIcon) replaceIcon()
if (setting.svgIcon) replaceIcon();
setLoading(false)
})
setLoading(false);
});
onUiUpdate(() => {
setCurrentTab()
})
}, [])
setCurrentTab();
});
}, []);
useEffect(() => {
if (!loading && setting.enableExtraNetworkSidebar) {
if (document.querySelector('#txt2img_lora_cards')) {
civitaiHelperFix()
setExtraLoading(false)
return
civitaiHelperFix();
setExtraLoading(false);
return;
}
setTimeout(() => {
const t2iBtn: any = document.querySelector('#txt2img_extra_refresh')
const i2iBtn: any = document.querySelector('#img2img_extra_refresh')
t2iBtn.click()
i2iBtn.click()
setExtraLoading(false)
const t2iBtn: any = document.querySelector('#txt2img_extra_refresh');
const i2iBtn: any = document.querySelector('#img2img_extra_refresh');
t2iBtn.click();
i2iBtn.click();
setExtraLoading(false);
try {
const civitaiBtn = document.querySelectorAll('button[title="Refresh Civitai Helper\'s additional buttons"]')
const civitaiBtn = document.querySelectorAll(
'button[title="Refresh Civitai Helper\'s additional buttons"]',
);
if (civitaiBtn) {
civitaiBtn.forEach((btn: any) => (btn.onclick = civitaiHelperFix))
civitaiBtn.forEach((btn: any) => (btn.onclick = civitaiHelperFix));
}
civitaiHelperFix()
civitaiHelperFix();
} catch {}
}, 2000)
}, 2000);
}
}, [loading])
}, [loading]);
return (
<MainView>
@ -122,15 +125,15 @@ const App: React.FC = () => {
<Spin size="small" />
</LoadingBox>
)}
<div style={loading ? { display: 'none' } : {}} id="sidebar" ref={sidebarRef} />
<div id="sidebar" ref={sidebarRef} style={loading ? { display: 'none' } : {}} />
</Sidebar>
<Content loading={loading}>
{loading && (
<LoadingBox>
<Spin tip="Loading" size="large" />
<Spin size="large" tip="Loading" />
</LoadingBox>
)}
<div style={loading ? { display: 'none' } : {}} id="content" ref={mainRef} />
<div id="content" ref={mainRef} style={loading ? { display: 'none' } : {}} />
</Content>
{setting?.enableExtraNetworkSidebar && (
<ExtraNetworkSidebar>
@ -142,20 +145,20 @@ const App: React.FC = () => {
<div style={extraLoading ? { display: 'none' } : {}}>
<div
id="txt2img-extra-netwrok-sidebar"
style={currentTab !== 'tab_img2img' ? {} : { display: 'none' }}
ref={txt2imgExtraNetworkSidebarRef}
style={currentTab !== 'tab_img2img' ? {} : { display: 'none' }}
/>
<div
id="img2img-extra-netwrok-sidebar"
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
ref={img2imgExtraNetworkSidebarRef}
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
/>
</div>
</ExtraNetworkSidebar>
)}
</View>
</MainView>
)
}
);
};
export default React.memo(App)
export default React.memo(App);

View File

@ -1,72 +1,75 @@
import { useIsDarkMode } from '@/components/theme/useIsDarkMode'
import formatPrompt from '@/script/formatPrompt'
import promptBracketChecker from '@/script/promptBracketChecker'
import setupHead from '@/script/setupHead'
import { useAppStore } from '@/store'
import '@/theme/style.less'
import { ThemeProvider, setupStyled } from 'antd-style'
import qs from 'query-string'
import React, { useEffect, useState } from 'react'
import { createRoot } from 'react-dom/client'
import { ThemeContext } from 'styled-components'
import { shallow } from 'zustand/shallow'
import App from './App'
import '@/theme/style.less';
import { setupStyled, ThemeProvider } from 'antd-style';
import qs from 'query-string';
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeContext } from 'styled-components';
import { shallow } from 'zustand/shallow';
import { useIsDarkMode } from '@/components/theme/useIsDarkMode';
import formatPrompt from '@/script/formatPrompt';
import promptBracketChecker from '@/script/promptBracketChecker';
import setupHead from '@/script/setupHead';
import { useAppStore } from '@/store';
import App from './App';
const Root: React.FC = () => {
setupStyled({ ThemeContext })
const [onSetThemeMode, onInit] = useAppStore((st) => [st.onSetThemeMode, st.onInit], shallow)
const isDarkMode = useIsDarkMode()
const [appearance, setAppearance] = useState<'light' | 'dark'>('light')
const [first, setFirst] = useState(true)
setupStyled({ ThemeContext });
const [onSetThemeMode, onInit] = useAppStore((st) => [st.onSetThemeMode, st.onInit], shallow);
const isDarkMode = useIsDarkMode();
const [appearance, setAppearance] = useState<'light' | 'dark'>('light');
const [first, setFirst] = useState(true);
useEffect(() => {
onInit()
}, [])
onInit();
}, []);
useEffect(() => {
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '')
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '');
if (queryTheme) {
setAppearance(queryTheme as any)
document.body.classList.add(queryTheme)
onSetThemeMode(queryTheme)
return
setAppearance(queryTheme as any);
document.body.classList.add(queryTheme);
onSetThemeMode(queryTheme);
return;
}
setAppearance(isDarkMode ? 'dark' : 'light')
document.body.classList.add(isDarkMode ? 'dark' : 'light')
onSetThemeMode(isDarkMode ? 'dark' : 'light')
}, [isDarkMode])
setAppearance(isDarkMode ? 'dark' : 'light');
document.body.classList.add(isDarkMode ? 'dark' : 'light');
onSetThemeMode(isDarkMode ? 'dark' : 'light');
}, [isDarkMode]);
useEffect(() => {
if (first) {
setFirst(false)
return
setFirst(false);
return;
}
window.location.reload()
}, [isDarkMode])
window.location.reload();
}, [isDarkMode]);
return (
<ThemeProvider appearance={appearance}>
<App />
</ThemeProvider>
)
}
);
};
document.addEventListener(
'DOMContentLoaded',
() => {
setupHead()
const root = document.createElement('div')
root.setAttribute('id', 'root')
setupHead();
const root = document.createElement('div');
root.setAttribute('id', 'root');
try {
gradioApp()?.append(root)
gradioApp()?.append(root);
} catch {
document.querySelector('gradio-app')?.append(root)
document.querySelector('gradio-app')?.append(root);
}
const client = createRoot(root)
client.render(<Root />)
const client = createRoot(root);
client.render(<Root />);
},
{ once: true }
)
{ once: true },
);
onUiLoaded(() => {
formatPrompt()
promptBracketChecker()
})
formatPrompt();
promptBracketChecker();
});
export default () => null
export default () => null;

View File

@ -8,7 +8,7 @@ export class Converter {
* @returns
*/
static round(value: number): number {
return Math.round(value * 10000) / 10000
return Math.round(value * 10000) / 10000;
}
/**
@ -17,7 +17,7 @@ export class Converter {
* @returns
*/
static convertStr(srt: string): string {
return srt.replace(//g, ':').replace(//g, '(').replace(//g, ')')
return srt.replace(//g, ':').replace(//g, '(').replace(//g, ')');
}
/**
@ -27,7 +27,7 @@ export class Converter {
*/
static convertStr2Array(str: string): string[] {
// 匹配各种括号中的内容,包括括号本身
const bracketRegex = /([()<>[\]])/g
const bracketRegex = /([()<>[\]])/g;
/**
*
@ -35,30 +35,30 @@ export class Converter {
* @returns
*/
const splitByBracket = (str: string): string[] => {
const arr: string[] = []
let start = 0
let depth = 0
let match
const arr: string[] = [];
let start = 0;
let depth = 0;
let match;
while ((match = bracketRegex.exec(str)) !== null) {
if (depth === 0 && match.index > start) {
arr.push(str.substring(start, match.index))
start = match.index
arr.push(str.substring(start, match.index));
start = match.index;
}
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
depth++
depth++;
} else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
depth--
depth--;
}
if (depth === 0) {
arr.push(str.substring(start, match.index + 1))
start = match.index + 1
arr.push(str.substring(start, match.index + 1));
start = match.index + 1;
}
}
if (start < str.length) {
arr.push(str.substring(start))
arr.push(str.substring(start));
}
return arr
}
return arr;
};
/**
*
@ -66,20 +66,20 @@ export class Converter {
* @returns
*/
const splitByComma = (str: string): string[] => {
const arr: string[] = []
let start = 0
let inBracket = false
const arr: string[] = [];
let start = 0;
let inBracket = false;
for (let i = 0; i < str.length; i++) {
if (str[i] === ',' && !inBracket) {
arr.push(str.substring(start, i).trim())
start = i + 1
arr.push(str.substring(start, i).trim());
start = i + 1;
} else if (str[i].match(bracketRegex)) {
inBracket = !inBracket
inBracket = !inBracket;
}
}
arr.push(str.substring(start).trim())
return arr
}
arr.push(str.substring(start).trim());
return arr;
};
/**
*
@ -87,20 +87,24 @@ export class Converter {
* @returns
*/
const cleanStr = (str: string): string[] => {
let arr = splitByBracket(str)
arr = arr.flatMap((s) => splitByComma(s))
return arr.filter((s) => s !== '')
}
let arr = splitByBracket(str);
arr = arr.flatMap((s) => splitByComma(s));
return arr.filter((s) => s !== '');
};
return cleanStr(str)
.filter((item) => {
const pattern = /^[,\s ]+$/
return !pattern.test(item)
const pattern = /^[,\s ]+$/;
return !pattern.test(item);
})
.filter(Boolean)
.sort((a, b) => {
return a.includes('<') && !b.includes('<') ? 1 : b.includes('<') && !a.includes('<') ? -1 : 0
})
return a.includes('<') && !b.includes('<')
? 1
: b.includes('<') && !a.includes('<')
? -1
: 0;
});
}
/**
@ -110,17 +114,17 @@ export class Converter {
*/
static convertArray2Str(array: string[]): string {
const newArray = array.map((item) => {
if (item.includes('<')) return item
if (item.includes('<')) return item;
const newItem = item
.replace(/\s+/g, ' ')
.replace(/|\.\|。/g, ',')
.replace(/“||”|"|\/'/g, '')
.replace(/, /g, ',')
.replace(/,,/g, ',')
.replace(/,/g, ', ')
return Converter.convertStr2Array(newItem).join(', ')
})
return newArray.join(', ')
.replace(/,/g, ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return newArray.join(', ');
}
/**
@ -129,21 +133,21 @@ export class Converter {
* @returns
*/
static convert(input: string): string {
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
let text = Converter.convertStr(input)
const textArray = Converter.convertStr2Array(text)
text = Converter.convertArray2Str(textArray)
let text = Converter.convertStr(input);
const textArray = Converter.convertStr2Array(text);
text = Converter.convertArray2Str(textArray);
let res: [string, number][] = []
let res: [string, number][] = [];
const curly_bracket_multiplier = 1.05
const square_bracket_multiplier = 1 / 1.05
const curly_bracket_multiplier = 1.05;
const square_bracket_multiplier = 1 / 1.05;
const brackets: Record<string, { stack: number[]; multiplier: number }> = {
const brackets: Record<string, { multiplier: number; stack: number[] }> = {
'{': { stack: [], multiplier: curly_bracket_multiplier },
'[': { stack: [], multiplier: square_bracket_multiplier },
}
};
/**
*
@ -152,50 +156,50 @@ export class Converter {
*/
function multiply_range(start_position: number, multiplier: number) {
for (let pos = start_position; pos < res.length; pos++) {
res[pos][1] = Converter.round(res[pos][1] * multiplier)
res[pos][1] = Converter.round(res[pos][1] * multiplier);
}
}
for (const match of text.matchAll(re_attention)) {
let word = match[0]
let word = match[0];
if (word in brackets) {
brackets[word].stack.push(res.length)
brackets[word].stack.push(res.length);
} else if (word === '}' || word === ']') {
const bracket = brackets[word === '}' ? '{' : '[']
const bracket = brackets[word === '}' ? '{' : '['];
if (bracket.stack.length > 0) {
multiply_range(bracket.stack.pop()!, bracket.multiplier)
multiply_range(bracket.stack.pop()!, bracket.multiplier);
}
} else {
res.push([word, 1.0])
res.push([word, 1.0]);
}
}
Object.keys(brackets).forEach((bracketType) => {
brackets[bracketType].stack.forEach((pos) => {
multiply_range(pos, brackets[bracketType].multiplier)
})
})
multiply_range(pos, brackets[bracketType].multiplier);
});
});
if (res.length === 0) {
res = [['', 1.0]]
res = [['', 1.0]];
}
let i = 0
let i = 0;
while (i + 1 < res.length) {
if (res[i][1] === res[i + 1][1]) {
res[i][0] += res[i + 1][0]
res.splice(i + 1, 1)
res[i][0] += res[i + 1][0];
res.splice(i + 1, 1);
} else {
i += 1
i += 1;
}
}
let result = ''
let result = '';
for (const [word, value] of res) {
result += value === 1.0 ? word : `(${word}:${value.toString()})`
result += value === 1.0 ? word : `(${word}:${value.toString()})`;
}
return result
return result;
}
/**
@ -203,9 +207,9 @@ export class Converter {
* @param target
*/
static dispatchInputEvent(target: EventTarget) {
let inputEvent = new Event('input')
Object.defineProperty(inputEvent, 'target', { value: target })
target.dispatchEvent(inputEvent)
let inputEvent = new Event('input');
Object.defineProperty(inputEvent, 'target', { value: target });
target.dispatchEvent(inputEvent);
}
/**
@ -213,22 +217,27 @@ export class Converter {
* @param type
*/
static onClickConvert(type: string) {
const default_prompt = ''
const default_negative = ''
const default_prompt = '';
const default_negative = '';
const prompt = gradioApp().querySelector(`#${type}_prompt > label > textarea`) as HTMLTextAreaElement
const result = Converter.convert(prompt.value)
prompt.value = result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result
Converter.dispatchInputEvent(prompt)
const negprompt = gradioApp().querySelector(`#${type}_neg_prompt > label > textarea`) as HTMLTextAreaElement
const negResult = Converter.convert(negprompt.value)
const prompt = gradioApp().querySelector(
`#${type}_prompt > label > textarea`,
) as HTMLTextAreaElement;
const result = Converter.convert(prompt.value);
prompt.value =
result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result;
Converter.dispatchInputEvent(prompt);
const negprompt = gradioApp().querySelector(
`#${type}_neg_prompt > label > textarea`,
) as HTMLTextAreaElement;
const negResult = Converter.convert(negprompt.value);
negprompt.value =
negResult.match(/^lowres,/) === null
? negResult.length === 0
? default_negative
: default_negative + negResult
: negResult
Converter.dispatchInputEvent(negprompt)
: negResult;
Converter.dispatchInputEvent(negprompt);
}
/**
@ -239,14 +248,14 @@ export class Converter {
* @returns
*/
static createButton(id: string, innerHTML: string, onClick: () => void): HTMLButtonElement {
const button = document.createElement('button')
button.id = id
button.type = 'button'
button.innerHTML = innerHTML
button.title = 'Format prompt~🪄'
button.className = 'lg secondary gradio-button tool svelte-1ipelgc'
button.addEventListener('click', onClick)
return button
const button = document.createElement('button');
button.id = id;
button.type = 'button';
button.innerHTML = innerHTML;
button.title = 'Format prompt~🪄';
button.className = 'lg secondary gradio-button tool svelte-1ipelgc';
button.addEventListener('click', onClick);
return button;
}
/**
@ -254,18 +263,18 @@ export class Converter {
* @param type -
*/
static addPromptButton(type: string): void {
const generateBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_generate`)
const actionsColumn: HTMLElement | null = gradioApp().querySelector(`#${type}_style_create`)
const nai2local: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`)
if (!generateBtn || !actionsColumn || nai2local) return
const generateBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_generate`);
const actionsColumn: HTMLElement | null = gradioApp().querySelector(`#${type}_style_create`);
const nai2local: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`);
if (!generateBtn || !actionsColumn || nai2local) return;
const convertBtn: HTMLElement = Converter.createButton(`${type}_formatconvert`, '🪄', () =>
Converter.onClickConvert(type)
)
actionsColumn.parentNode?.append(convertBtn)
Converter.onClickConvert(type),
);
actionsColumn.parentNode?.append(convertBtn);
}
}
export default () => {
Converter.addPromptButton('txt2img')
Converter.addPromptButton('img2img')
}
Converter.addPromptButton('txt2img');
Converter.addPromptButton('img2img');
};

View File

@ -1,16 +1,16 @@
interface ErrorString {
regex: string
error: string
error: string;
regex: string;
}
class BracketChecker {
private textArea: HTMLTextAreaElement
private counterElt: HTMLElement
private errorStrings: ErrorString[]
private textArea: HTMLTextAreaElement;
private counterElt: HTMLElement;
private errorStrings: ErrorString[];
constructor(textArea: HTMLTextAreaElement, counterElt: HTMLElement) {
this.textArea = textArea
this.counterElt = counterElt
this.textArea = textArea;
this.counterElt = counterElt;
this.errorStrings = [
{
regex: '\\(',
@ -24,31 +24,32 @@ class BracketChecker {
regex: '\\{',
error: '{...} - Different number of opening and closing curly brackets detected.\n',
},
]
];
}
/**
*
*/
public check = (): void => {
let title = ''
let title = '';
this.errorStrings.forEach(({ regex, error }) => {
const openMatches = (this.textArea.value.match(new RegExp(regex, 'g')) || []).length
const openMatches = (this.textArea.value.match(new RegExp(regex, 'g')) || []).length;
const closeMatches = (
this.textArea.value.match(new RegExp(regex.replace(/\(/g, ')').replace(/\[/g, ']').replace(/\{/g, '}'), 'g')) ||
[]
).length
this.textArea.value.match(
new RegExp(regex.replace(/\(/g, ')').replace(/\[/g, ']').replace(/\{/g, '}'), 'g'),
) || []
).length;
if (openMatches !== closeMatches) {
if (!this.counterElt.title.includes(error)) {
title += error
title += error;
}
} else {
title = this.counterElt.title.replace(error, '')
title = this.counterElt.title.replace(error, '');
}
})
this.counterElt.title = title
this.counterElt.classList.toggle('error', !!title)
}
});
this.counterElt.title = title;
this.counterElt.classList.toggle('error', !!title);
};
}
/**
@ -57,16 +58,18 @@ class BracketChecker {
* @param id_counter ID
*/
const setupBracketChecking = (idPrompt: string, idCounter: string): void => {
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`) as HTMLTextAreaElement
const counter = gradioApp().getElementById(idCounter) as HTMLElement
const bracketChecker = new BracketChecker(textarea, counter)
textarea.addEventListener('input', bracketChecker.check)
}
const textarea = gradioApp().querySelector(
`#${idPrompt} > label > textarea`,
) as HTMLTextAreaElement;
const counter = gradioApp().getElementById(idCounter) as HTMLElement;
const bracketChecker = new BracketChecker(textarea, counter);
textarea.addEventListener('input', bracketChecker.check);
};
export default () => {
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg']
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg'];
elements.forEach((prompt) => {
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`)
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`)
})
}
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`);
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`);
});
};

View File

@ -1,19 +1,19 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const SETTING_KEY = 'SD-KITCHEN-SETTING'
const SETTING_KEY = 'SD-KITCHEN-SETTING';
export interface WebuiSetting {
promotTextarea: 'scroll' | 'resizable'
sidebarExpand: boolean
sidebarFixedMode: 'fixed' | 'float'
sidebarWidth: number
enableExtraNetworkSidebar: boolean
extraNetworkSidebarExpand: boolean
extraNetworkFixedMode: 'fixed' | 'float'
extraNetworkSidebarWidth: number
extraNetworkCardSize: number
svgIcon: boolean
enableExtraNetworkSidebar: boolean;
extraNetworkCardSize: number;
extraNetworkFixedMode: 'fixed' | 'float';
extraNetworkSidebarExpand: boolean;
extraNetworkSidebarWidth: number;
promotTextarea: 'scroll' | 'resizable';
sidebarExpand: boolean;
sidebarFixedMode: 'fixed' | 'float';
sidebarWidth: number;
svgIcon: boolean;
}
export const defaultSetting: WebuiSetting = {
@ -27,16 +27,16 @@ export const defaultSetting: WebuiSetting = {
extraNetworkSidebarWidth: 340,
extraNetworkCardSize: 86,
svgIcon: false,
}
};
export interface AppState {
themeMode: 'light' | 'dark'
setting: WebuiSetting
currentTab: string
setCurrentTab: () => void
onSetThemeMode: (themeMode: 'light' | 'dark') => void
onLoadSetting: () => void
onSetSetting: (setting: WebuiSetting) => void
onInit: () => void
currentTab: string;
onInit: () => void;
onLoadSetting: () => void;
onSetSetting: (setting: WebuiSetting) => void;
onSetThemeMode: (themeMode: 'light' | 'dark') => void;
setCurrentTab: () => void;
setting: WebuiSetting;
themeMode: 'light' | 'dark';
}
export const useAppStore = create<AppState>()(
devtools((set, get) => ({
@ -44,28 +44,28 @@ export const useAppStore = create<AppState>()(
setting: defaultSetting,
currentTab: 'tab_txt2img',
setCurrentTab: () => {
const currentTab = get_uiCurrentTabContent().id
if (currentTab !== get().currentTab) set({ currentTab }, false, 'setCurrentTab')
const currentTab = get_uiCurrentTabContent().id;
if (currentTab !== get().currentTab) set({ currentTab }, false, 'setCurrentTab');
},
onSetThemeMode: (themeMode) => {
set(() => ({ themeMode }), false, 'onSetThemeMode')
set(() => ({ themeMode }), false, 'onSetThemeMode');
},
onLoadSetting: () => {
let setting: any = localStorage.getItem(SETTING_KEY)
let setting: any = localStorage.getItem(SETTING_KEY);
if (setting) {
setting = JSON.parse(setting)
setting = JSON.parse(setting);
} else {
setting = defaultSetting
localStorage.setItem(SETTING_KEY, JSON.stringify(defaultSetting))
setting = defaultSetting;
localStorage.setItem(SETTING_KEY, JSON.stringify(defaultSetting));
}
set(() => ({ setting: { ...defaultSetting, ...setting } }), false, 'onLoadSetting')
set(() => ({ setting: { ...defaultSetting, ...setting } }), false, 'onLoadSetting');
},
onSetSetting: (setting) => {
localStorage.setItem(SETTING_KEY, JSON.stringify(setting))
set(() => ({ setting }), false, 'onSetSetting')
localStorage.setItem(SETTING_KEY, JSON.stringify(setting));
set(() => ({ setting }), false, 'onSetSetting');
},
onInit: () => {
get().onLoadSetting()
get().onLoadSetting();
},
}))
)
})),
);

View File

@ -1,14 +0,0 @@
const { theme } = require('antd')
const { toCustomPropertiesString } = require('object-to-css-variables')
const fs = require('fs')
const { defaultAlgorithm, darkAlgorithm, defaultSeed } = theme
const mapToken = defaultAlgorithm(defaultSeed)
const mapDarkToken = darkAlgorithm(defaultSeed)
const cssVariables = toCustomPropertiesString(mapToken)
const cssDarkVariables = toCustomPropertiesString(mapDarkToken)
fs.writeFileSync('src/theme/var/antd-light.less', cssVariables)
fs.writeFileSync('src/theme/var/antd-dark.less', cssDarkVariables)

View File

@ -1,132 +0,0 @@
const fs = require('fs')
const path = require('path')
// 从本地文件读取CSS
const cssFilePath = path.resolve(__dirname, 'style.css')
const css = fs.readFileSync(cssFilePath, 'utf-8')
// 用于存储变量及其值的对象
const cssVariables = {}
// 正则表达式用于匹配CSS变量及其值
const cssVarRegex = /--\w+[^:]*:\s*([^;]*);/g
const cssVarReferenceRegex = /var\((--\w+)\)/g
// 解析CSS变量及其值
let match
while ((match = cssVarRegex.exec(css))) {
const variable = match[0].split(':')[0].trim()
const value = match[1].trim()
cssVariables[variable] = value
}
// 对变量进行排序,确保被引用的变量在引用它的变量之前
const sortedVariables = Object.keys(cssVariables)
// 按类别对变量进行分组
const groups = {
colors: [],
typography: [],
spacing: [],
border: [],
boxShadow: [],
body: [],
block: [],
panel: [],
button: [],
checkbox: [],
input: [],
table: [],
other: [],
}
sortedVariables.forEach((variable) => {
const value = cssVariables[variable]
const isColor =
value.startsWith('#') ||
value.includes('rgb') ||
value.includes('color') ||
variable.includes('color') ||
variable.includes('fill') ||
variable.includes('neutral')
const isTypography =
variable.includes('text') || variable.includes('font') || variable.includes('prose') || variable.includes('link')
const isSpacing =
variable.includes('spacing') ||
variable.includes('padding') ||
variable.includes('gap') ||
variable.includes('margin')
value.includes('padding') || value.includes('margin')
const isBorder = variable.includes('radius') || variable.includes('border')
const isBoxShadow = variable.includes('shadow')
if (variable.includes('--body')) {
groups.body.push(variable)
} else if (variable.includes('--block')) {
groups.block.push(variable)
} else if (variable.includes('--panel')) {
groups.panel.push(variable)
} else if (variable.includes('--button')) {
groups.button.push(variable)
} else if (variable.includes('--checkbox')) {
groups.checkbox.push(variable)
} else if (variable.includes('--input')) {
groups.input.push(variable)
} else if (variable.includes('--table')) {
groups.table.push(variable)
} else if (isBorder) {
groups.border.push(variable)
} else if (isBoxShadow) {
groups.boxShadow.push(variable)
} else if (isColor) {
groups.colors.push(variable)
} else if (isTypography) {
groups.typography.push(variable)
} else if (isSpacing) {
groups.spacing.push(variable)
} else {
groups.other.push(variable)
}
})
// 生成排序后的CSS变量
const generateGroupCss = (groupName) => {
const cache = groups[groupName].map((variable) => `${variable}: ${cssVariables[variable]};`)
const firstPart = cache.filter((item) => item.includes('var'))
const secondPart = cache.filter((item) => !item.includes('var'))
return [...secondPart, ...firstPart].join('\n')
}
const sortedCss =
'#output {\n' +
`/* Colors */\n` +
generateGroupCss('colors') +
`\n\n/* Typography */\n` +
generateGroupCss('typography') +
`\n\n/* Spacing */\n` +
generateGroupCss('spacing') +
`\n\n/* Border */\n` +
generateGroupCss('border') +
`\n\n/* BoxShadow */\n` +
generateGroupCss('boxShadow') +
`\n\n/* Body */\n` +
generateGroupCss('body') +
`\n\n/* Block */\n` +
generateGroupCss('block') +
`\n\n/* Panel */\n` +
generateGroupCss('panel') +
`\n\n/* Button */\n` +
generateGroupCss('button') +
`\n\n/* Checkbox */\n` +
generateGroupCss('checkbox') +
`\n\n/* Input */\n` +
generateGroupCss('input') +
`\n\n/* Table */\n` +
generateGroupCss('table') +
`\n\n/* Other */\n` +
generateGroupCss('other') +
'}'
console.log(sortedCss)
// 将排序后的CSS变量写入文件
fs.writeFileSync('sorted-css-variables.css', sortedCss)