mirror of
https://github.com/lobehub/sd-webui-lobe-theme.git
synced 2026-01-09 06:23:44 +08:00
commit
cdadb6a7ab
@ -1,3 +1,13 @@
|
||||
module.exports = {
|
||||
displayTypes: ['feat', 'fix', 'styles', 'pref'],
|
||||
};
|
||||
/scripts
|
||||
/config
|
||||
/example
|
||||
_test_
|
||||
__test__
|
||||
|
||||
/node_modules
|
||||
jest*
|
||||
/es
|
||||
/lib
|
||||
/docs
|
||||
/dist
|
||||
/javascript
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
37
.umirc.ts
Normal file
37
.umirc.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { defineConfig } from 'umi'
|
||||
import WebpackShellPlugin from 'webpack-shell-plugin-next'
|
||||
|
||||
const mac = [
|
||||
'rm ./javascript/index.js',
|
||||
'rm ./style.css',
|
||||
'cp ./dist/index.js ./javascript/index.js',
|
||||
'cp ./dist/index.css ./style.css',
|
||||
]
|
||||
|
||||
const win = [
|
||||
'del javascript\\index.js',
|
||||
'del style.css',
|
||||
'copy dist\\index.js javascript\\index.js',
|
||||
'copy dist\\index.css style.css',
|
||||
]
|
||||
|
||||
export default defineConfig({
|
||||
routes: [{ path: '/', component: 'index' }],
|
||||
npmClient: 'yarn',
|
||||
mpa: {},
|
||||
codeSplitting: false,
|
||||
define: {
|
||||
'process.env': process.env,
|
||||
},
|
||||
chainWebpack(memo) {
|
||||
memo.plugin('shell').use(WebpackShellPlugin, [
|
||||
{
|
||||
onBuildExit: {
|
||||
scripts: process.platform === 'darwin' ? mac : win,
|
||||
blocking: false,
|
||||
parallel: false,
|
||||
},
|
||||
},
|
||||
])
|
||||
},
|
||||
})
|
||||
19
gulpfile.js
19
gulpfile.js
@ -1,19 +0,0 @@
|
||||
const gulp = require('gulp')
|
||||
const less = require('gulp-less')
|
||||
const ts = require('gulp-typescript')
|
||||
|
||||
const tsProject = ts.createProject('tsconfig.json')
|
||||
gulp.task('compile', () => {
|
||||
return gulp.src('src/script/**/*.ts').pipe(tsProject()).pipe(gulp.dest('javascript'))
|
||||
})
|
||||
|
||||
gulp.task('less', () => {
|
||||
return gulp.src('src/theme/*.less').pipe(less()).pipe(gulp.dest('./'))
|
||||
})
|
||||
|
||||
gulp.task('build', gulp.parallel('compile', 'less'))
|
||||
|
||||
gulp.task('watch', () => {
|
||||
gulp.watch('src/theme/**/*', gulp.parallel('less'))
|
||||
gulp.watch('src/script/**/*', gulp.parallel('compile'))
|
||||
})
|
||||
@ -1,19 +0,0 @@
|
||||
"use strict";
|
||||
/**
|
||||
* 处理网站的 favicon 图标
|
||||
*/
|
||||
class FaviconHandler {
|
||||
/**
|
||||
* 设置网站的 favicon 图标
|
||||
*/
|
||||
static setFavicon() {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
link.type = 'image/svg+xml';
|
||||
link.href = 'https://gw.alipayobjects.com/zos/bmw-prod/51a51720-8a30-4430-b6c9-be5712364f04.svg';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
}
|
||||
onUiLoaded(() => {
|
||||
FaviconHandler.setFavicon();
|
||||
});
|
||||
@ -1,256 +0,0 @@
|
||||
"use strict";
|
||||
/**
|
||||
* 转换器工具类
|
||||
*/
|
||||
class Converter {
|
||||
/**
|
||||
* 将数字四舍五入到小数点后四位
|
||||
* @param value 数字
|
||||
* @returns 四舍五入后的数字
|
||||
*/
|
||||
static round(value) {
|
||||
return Math.round(value * 10000) / 10000;
|
||||
}
|
||||
/**
|
||||
* 将字符串中的中文冒号和括号转换成英文冒号和括号
|
||||
* @param srt 字符串
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convertStr(srt) {
|
||||
return srt.replace(/:/g, ':').replace(/(/g, '(').replace(/)/g, ')');
|
||||
}
|
||||
/**
|
||||
* 将字符串按照括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
static convertStr2Array(str) {
|
||||
// 匹配各种括号中的内容,包括括号本身
|
||||
const bracketRegex = /([()<>[\]])/g;
|
||||
/**
|
||||
* 将字符串按照各种括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByBracket = (str) => {
|
||||
const arr = [];
|
||||
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;
|
||||
}
|
||||
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
|
||||
depth++;
|
||||
}
|
||||
else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
|
||||
depth--;
|
||||
}
|
||||
if (depth === 0) {
|
||||
arr.push(str.substring(start, match.index + 1));
|
||||
start = match.index + 1;
|
||||
}
|
||||
}
|
||||
if (start < str.length) {
|
||||
arr.push(str.substring(start));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* 将字符串按照逗号和各种括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByComma = (str) => {
|
||||
const arr = [];
|
||||
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;
|
||||
}
|
||||
else if (str[i].match(bracketRegex)) {
|
||||
inBracket = !inBracket;
|
||||
}
|
||||
}
|
||||
arr.push(str.substring(start).trim());
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* 清洗字符串并输出数组
|
||||
* @param str 字符串
|
||||
* @returns 清洗后的数组
|
||||
*/
|
||||
const cleanStr = (str) => {
|
||||
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);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => {
|
||||
return a.includes('<') && !b.includes('<') ? 1 : b.includes('<') && !a.includes('<') ? -1 : 0;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 将数组转换成字符串
|
||||
* @param array 数组
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convertArray2Str(array) {
|
||||
const newArray = array.map((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(', ');
|
||||
}
|
||||
/**
|
||||
* 将输入的字符串转换成特定格式的字符串
|
||||
* @param input 输入的字符串
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convert(input) {
|
||||
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
|
||||
let text = Converter.convertStr(input);
|
||||
const textArray = Converter.convertStr2Array(text);
|
||||
text = Converter.convertArray2Str(textArray);
|
||||
let res = [];
|
||||
const curly_bracket_multiplier = 1.05;
|
||||
const square_bracket_multiplier = 1 / 1.05;
|
||||
const brackets = {
|
||||
'{': { stack: [], multiplier: curly_bracket_multiplier },
|
||||
'[': { stack: [], multiplier: square_bracket_multiplier },
|
||||
};
|
||||
/**
|
||||
* 将指定范围内的数字乘以指定倍数
|
||||
* @param start_position 起始位置
|
||||
* @param multiplier 倍数
|
||||
*/
|
||||
function multiply_range(start_position, multiplier) {
|
||||
for (let pos = start_position; pos < res.length; pos++) {
|
||||
res[pos][1] = Converter.round(res[pos][1] * multiplier);
|
||||
}
|
||||
}
|
||||
for (const match of text.matchAll(re_attention)) {
|
||||
let word = match[0];
|
||||
if (word in brackets) {
|
||||
brackets[word].stack.push(res.length);
|
||||
}
|
||||
else if (word === '}' || word === ']') {
|
||||
const bracket = brackets[word === '}' ? '{' : '['];
|
||||
if (bracket.stack.length > 0) {
|
||||
multiply_range(bracket.stack.pop(), bracket.multiplier);
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.push([word, 1.0]);
|
||||
}
|
||||
}
|
||||
Object.keys(brackets).forEach((bracketType) => {
|
||||
brackets[bracketType].stack.forEach((pos) => {
|
||||
multiply_range(pos, brackets[bracketType].multiplier);
|
||||
});
|
||||
});
|
||||
if (res.length === 0) {
|
||||
res = [['', 1.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);
|
||||
}
|
||||
else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
let result = '';
|
||||
for (const [word, value] of res) {
|
||||
result += value === 1.0 ? word : `(${word}:${value.toString()})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 触发 input 事件
|
||||
* @param target 目标元素
|
||||
*/
|
||||
static dispatchInputEvent(target) {
|
||||
let inputEvent = new Event('input');
|
||||
Object.defineProperty(inputEvent, 'target', { value: target });
|
||||
target.dispatchEvent(inputEvent);
|
||||
}
|
||||
/**
|
||||
* 点击转换按钮的事件处理函数
|
||||
* @param type 类型
|
||||
*/
|
||||
static onClickConvert(type) {
|
||||
const default_prompt = '';
|
||||
const default_negative = '';
|
||||
const prompt = gradioApp().querySelector(`#${type}_prompt > label > textarea`);
|
||||
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`);
|
||||
const negResult = Converter.convert(negprompt.value);
|
||||
negprompt.value =
|
||||
negResult.match(/^lowres,/) === null
|
||||
? negResult.length === 0
|
||||
? default_negative
|
||||
: default_negative + negResult
|
||||
: negResult;
|
||||
Converter.dispatchInputEvent(negprompt);
|
||||
}
|
||||
/**
|
||||
* 创建转换按钮
|
||||
* @param id 按钮 id
|
||||
* @param innerHTML 按钮文本
|
||||
* @param onClick 点击事件处理函数
|
||||
* @returns 新建的按钮元素
|
||||
*/
|
||||
static createButton(id, innerHTML, onClick) {
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 添加转换按钮
|
||||
* @param type - 组件类型
|
||||
*/
|
||||
static addPromptButton(type) {
|
||||
const generateBtn = gradioApp().querySelector(`#${type}_generate`);
|
||||
const actionsColumn = gradioApp().querySelector(`#${type}_style_create`);
|
||||
const nai2local = gradioApp().querySelector(`#${type}_formatconvert`);
|
||||
if (!generateBtn || !actionsColumn || nai2local)
|
||||
return;
|
||||
const convertBtn = Converter.createButton(`${type}_formatconvert`, '🪄', () => Converter.onClickConvert(type));
|
||||
actionsColumn.parentNode?.append(convertBtn);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 注册UI更新回调函数
|
||||
* 在UI更新时添加提示按钮
|
||||
*/
|
||||
onUiUpdate(() => {
|
||||
Converter.addPromptButton('txt2img');
|
||||
Converter.addPromptButton('img2img');
|
||||
});
|
||||
587
javascript/index.js
Normal file
587
javascript/index.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,63 +0,0 @@
|
||||
"use strict";
|
||||
class BracketChecker {
|
||||
textArea;
|
||||
counterElt;
|
||||
errorStrings;
|
||||
constructor(textArea, counterElt) {
|
||||
this.textArea = textArea;
|
||||
this.counterElt = counterElt;
|
||||
this.errorStrings = [
|
||||
{
|
||||
regex: '\\(',
|
||||
error: '(...) - Different number of opening and closing parentheses detected.\n',
|
||||
},
|
||||
{
|
||||
regex: '\\[',
|
||||
error: '[...] - Different number of opening and closing square brackets detected.\n',
|
||||
},
|
||||
{
|
||||
regex: '\\{',
|
||||
error: '{...} - Different number of opening and closing curly brackets detected.\n',
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 检查文本框中的括号是否匹配,并更新计数器元素的标题和样式
|
||||
*/
|
||||
check = () => {
|
||||
let title = '';
|
||||
this.errorStrings.forEach(({ regex, error }) => {
|
||||
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;
|
||||
if (openMatches !== closeMatches) {
|
||||
if (!this.counterElt.title.includes(error)) {
|
||||
title += error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
title = this.counterElt.title.replace(error, '');
|
||||
}
|
||||
});
|
||||
this.counterElt.title = title;
|
||||
this.counterElt.classList.toggle('error', !!title);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 初始化括号匹配检查器
|
||||
* @param id_prompt 包含文本框的元素的 ID
|
||||
* @param id_counter 显示计数器的元素的 ID
|
||||
*/
|
||||
const setupBracketChecking = (idPrompt, idCounter) => {
|
||||
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`);
|
||||
const counter = gradioApp().getElementById(idCounter);
|
||||
const bracketChecker = new BracketChecker(textarea, counter);
|
||||
textarea.addEventListener('input', bracketChecker.check);
|
||||
};
|
||||
onUiUpdate(() => {
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg'];
|
||||
elements.forEach((prompt) => {
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`);
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`);
|
||||
});
|
||||
});
|
||||
32
package.json
32
package.json
@ -9,18 +9,16 @@
|
||||
"license": "MIT",
|
||||
"author": "canisminor1990 <i@canisminor.cc>",
|
||||
"sideEffects": false,
|
||||
"main": "style.css",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"dev": "gulp watch",
|
||||
"dev:with-sd": "concurrently \"gulp watch\" \"npm run sd-debug\"",
|
||||
"build": "umi build",
|
||||
"dev": "umi build",
|
||||
"lint": "eslint \"{src,javascript}/**/*.{js,jsx,ts,tsx}\" --fix",
|
||||
"prepare": "husky install",
|
||||
"prettier": "prettier -c --write \"**/**\"",
|
||||
"release": "semantic-release",
|
||||
"sd-debug": "cd ../../ && ./webui.sh ",
|
||||
"sd-debug": "cd ../../ && ./webui.sh",
|
||||
"setup": "umi setup",
|
||||
"stat": "npm run dev",
|
||||
"start": "umi build",
|
||||
"stylelint": "stylelint \"src/**/*.less\" --fix && stylelint \"./style.css\" --fix",
|
||||
"test": "npm run lint",
|
||||
"type-check": "tsc -p tsconfig-check.json"
|
||||
@ -44,29 +42,43 @@
|
||||
},
|
||||
"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",
|
||||
"commitlint": "^17",
|
||||
"commitlint-config-gitmoji": "^2",
|
||||
"concurrently": "^8.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-less": "^5.0.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"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",
|
||||
"stylelint": "^15.4.0",
|
||||
"stylelint-less": "^1.0.6",
|
||||
"typescript": "^5.0.0",
|
||||
"umi": "^4.0.64"
|
||||
"umi": "^4.0.64",
|
||||
"use-merge-value": "^1.2.0",
|
||||
"webpack-shell-plugin-next": "^2.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
171
src/components/DraggablePanel/DraggablePanel.tsx
Normal file
171
src/components/DraggablePanel/DraggablePanel.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
248
src/components/DraggablePanel/FixMode.tsx
Normal file
248
src/components/DraggablePanel/FixMode.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
)
|
||||
174
src/components/DraggablePanel/FloatMode.tsx
Normal file
174
src/components/DraggablePanel/FloatMode.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
)
|
||||
3
src/components/DraggablePanel/index.ts
Normal file
3
src/components/DraggablePanel/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type { Position } from 'react-rnd'
|
||||
export { Draggable as DraggablePanel } from './DraggablePanel'
|
||||
export type { DraggablePanelProps } from './DraggablePanel'
|
||||
177
src/components/DraggablePanel/style.ts
Normal file
177
src/components/DraggablePanel/style.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import { createStyles, css, cx } from 'antd-style'
|
||||
import { rgba } from 'polished'
|
||||
|
||||
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: ${rgba(token.colorBgContainer, 0.75)};
|
||||
backdrop-filter: blur(40px);
|
||||
overflow: hidden;
|
||||
`
|
||||
),
|
||||
float: cx(
|
||||
`${prefix}-float`,
|
||||
css`
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: ${rgba(token.colorBgElevated, 0.75)};
|
||||
backdrop-filter: blur(40px);
|
||||
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%;
|
||||
}
|
||||
`
|
||||
),
|
||||
}
|
||||
})
|
||||
14
src/components/Header/Logo.tsx
Normal file
14
src/components/Header/Logo.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { darkLogo, lightLogo } from './style'
|
||||
|
||||
interface LogoProps {
|
||||
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 }} />
|
||||
}
|
||||
|
||||
export default React.memo(Logo)
|
||||
78
src/components/Header/index.tsx
Normal file
78
src/components/Header/index.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { GithubOutlined } from '@ant-design/icons'
|
||||
import { Button, Space } from 'antd'
|
||||
import { rgba } from 'polished'
|
||||
import qs from 'query-string'
|
||||
import React, { useCallback } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Logo from './Logo'
|
||||
import { themeIcon } from './style'
|
||||
|
||||
const HeaderView = styled.div`
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: ${({ theme }) => rgba(theme.colorBgContainer, 0.5)};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colorBorderSecondary};
|
||||
backdrop-filter: blur(40px);
|
||||
#header {
|
||||
.tab-nav {
|
||||
border: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
flex: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
margin: 0 !important;
|
||||
flex: 0 !important;
|
||||
&:hover {
|
||||
border: none !important;
|
||||
color: var(--color-text) !important;
|
||||
background: var(--color-fill-tertiary) !important;
|
||||
flex: none;
|
||||
}
|
||||
&.selected {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
color: var(--color-text) !important;
|
||||
flex: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface HeaderProps {
|
||||
children: React.ReactNode
|
||||
themeMode: 'dark' | 'light'
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ children, themeMode }) => {
|
||||
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])
|
||||
|
||||
return (
|
||||
<HeaderView>
|
||||
<Logo themeMode={themeMode} style={{ paddingRight: 16 }} />
|
||||
{children}
|
||||
<Space.Compact>
|
||||
<a href="https://github.com/canisminor1990/sd-web-ui-kitchen-theme" target="_blank">
|
||||
<Button icon={<GithubOutlined />} />
|
||||
</a>
|
||||
<Button icon={themeIcon[themeMode]} onClick={handleSetTheme} />
|
||||
</Space.Compact>
|
||||
</HeaderView>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Header)
|
||||
19
src/components/Header/style.tsx
Normal file
19
src/components/Header/style.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
export const themeIcon = {
|
||||
light: (
|
||||
<span role="img" className="anticon anticon-github">
|
||||
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
|
||||
<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">
|
||||
<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'
|
||||
49
src/components/Sidebar/index.tsx
Normal file
49
src/components/Sidebar/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { DraggablePanel } from '@/components'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const SidebarView = styled.div`
|
||||
padding: 16px;
|
||||
#quicksettings {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
max-width: unset !important;
|
||||
min-width: unset !important;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface SidebarProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ children }) => {
|
||||
return (
|
||||
<DraggablePanel placement="right" defaultSize={{ width: 280 }}>
|
||||
<SidebarView>{children}</SidebarView>
|
||||
</DraggablePanel>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Sidebar)
|
||||
3
src/components/index.tsx
Normal file
3
src/components/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './DraggablePanel'
|
||||
export { default as Header } from './Header'
|
||||
export { default as Sidebar } from './Sidebar'
|
||||
62
src/pages/index/App.tsx
Normal file
62
src/pages/index/App.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { Header, Sidebar } from '@/components'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const View = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
`
|
||||
|
||||
const MainView = styled.div`
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
interface AppProps {
|
||||
themeMode: 'light' | 'dark'
|
||||
}
|
||||
|
||||
const App: React.FC<AppProps> = ({ 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 (
|
||||
<View>
|
||||
<MainView>
|
||||
<Header themeMode={themeMode}>
|
||||
<div id="header" ref={headerRef} />
|
||||
</Header>
|
||||
<Content>
|
||||
<div ref={mainRef} />
|
||||
</Content>
|
||||
</MainView>
|
||||
<Sidebar>
|
||||
<div ref={sidebarRef} />
|
||||
</Sidebar>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(App)
|
||||
45
src/pages/index/index.tsx
Normal file
45
src/pages/index/index.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import favicon from '@/script/favicon'
|
||||
import formatPrompt from '@/script/format-prompt'
|
||||
import promptBracketChecker from '@/script/prompt-bracket-checker'
|
||||
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 App from './App'
|
||||
|
||||
const Root: React.FC = () => {
|
||||
setupStyled({ ThemeContext })
|
||||
const [appearance, setAppearance] = useState<'light' | 'dark'>('light')
|
||||
useEffect(() => {
|
||||
const themeMode: any = String(qs.parseUrl(window.location.href).query.__theme) || 'light'
|
||||
setAppearance(themeMode)
|
||||
document.body.classList.add(themeMode)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ThemeProvider appearance={appearance}>
|
||||
<App themeMode={appearance} />
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const root = document.createElement('div')
|
||||
root.setAttribute('id', 'root')
|
||||
gradioApp().append(root)
|
||||
const client = createRoot(root)
|
||||
client.render(<Root />)
|
||||
})
|
||||
|
||||
onUiLoaded(() => {
|
||||
favicon()
|
||||
})
|
||||
|
||||
onUiUpdate(() => {
|
||||
formatPrompt()
|
||||
promptBracketChecker()
|
||||
})
|
||||
|
||||
export default () => null
|
||||
@ -14,6 +14,8 @@ class FaviconHandler {
|
||||
}
|
||||
}
|
||||
|
||||
onUiLoaded(() => {
|
||||
onUiLoaded(() => {})
|
||||
|
||||
export default () => {
|
||||
FaviconHandler.setFavicon()
|
||||
})
|
||||
}
|
||||
|
||||
@ -269,7 +269,9 @@ class Converter {
|
||||
* 注册UI更新回调函数
|
||||
* 在UI更新时添加提示按钮
|
||||
*/
|
||||
onUiUpdate(() => {
|
||||
onUiUpdate(() => {})
|
||||
|
||||
export default () => {
|
||||
Converter.addPromptButton('txt2img')
|
||||
Converter.addPromptButton('img2img')
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,10 +63,12 @@ const setupBracketChecking = (idPrompt: string, idCounter: string): void => {
|
||||
textarea.addEventListener('input', bracketChecker.check)
|
||||
}
|
||||
|
||||
onUiUpdate(() => {
|
||||
onUiUpdate(() => {})
|
||||
|
||||
export default () => {
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg']
|
||||
elements.forEach((prompt) => {
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`)
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,6 +1,48 @@
|
||||
.tabitem,
|
||||
.gradio-tabitem {
|
||||
background: var(--color-fill-quaternary);
|
||||
border: none !important;
|
||||
border-radius: var(--container-radius);
|
||||
background: var(--panel-background-fill);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
#tabs {
|
||||
> .tabitem,
|
||||
> .gradio-tabitem {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-nav {
|
||||
border: none !important;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
button {
|
||||
cursor: pointer;
|
||||
border: none !important;
|
||||
background: var(--color-fill-quaternary) !important;
|
||||
flex: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
flex: 1 !important;
|
||||
&:hover {
|
||||
border: none !important;
|
||||
color: var(--color-text) !important;
|
||||
background: var(--color-fill-tertiary) !important;
|
||||
flex: none;
|
||||
}
|
||||
&.selected {
|
||||
border: none !important;
|
||||
background: var(--color-fill-secondary) !important;
|
||||
color: var(--color-text) !important;
|
||||
flex: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected.svelte-1g805jl {
|
||||
@ -10,4 +52,41 @@
|
||||
[id$='2img_tools'] > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
button {
|
||||
max-width: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.image-buttons button {
|
||||
min-width: min(160px,100%) !important;
|
||||
}
|
||||
|
||||
#img2img_label_copy_to_img2img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#img2img_copy_to_img2img, .gap.compact, .image-buttons, .image_buttons_extras {
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
|
||||
.padded.svelte-mppz8v {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.wrap.svelte-1p9xokt.svelte-1p9xokt.svelte-1p9xokt {
|
||||
gap: 8px !important;
|
||||
> label {
|
||||
flex: 1 !important;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--border-radius)!important;
|
||||
}
|
||||
}
|
||||
|
||||
[id$="_settings"] {
|
||||
div.svelte-15lo0d8>*, div.svelte-15lo0d8>.form>* {
|
||||
min-width: unset !important;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
#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;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
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;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
> div,
|
||||
> fieldset {
|
||||
min-width: 240px !important;
|
||||
@media screen and (max-width: 640px) {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
button.svelte-1ipelgc {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#tabs {
|
||||
> .tabitem {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: var(--size-lg);
|
||||
margin-top: 118px;
|
||||
}
|
||||
|
||||
> .tab-nav:first-child {
|
||||
position: fixed;
|
||||
top: 90px;
|
||||
z-index: 999;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: auto;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,12 @@ ul.options {
|
||||
border: 2px solid var(--color-border) !important;
|
||||
border-radius: var(--border-radius) !important;
|
||||
display: block !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: var(--color-bg-elevated) !important;
|
||||
li {
|
||||
display: block !important;
|
||||
margin: 0 !important;
|
||||
&.selected {
|
||||
background: var(--color-primary) !important;
|
||||
color: #fff !important;
|
||||
|
||||
8
src/theme/components/prose.less
Normal file
8
src/theme/components/prose.less
Normal file
@ -0,0 +1,8 @@
|
||||
.prose {
|
||||
> p {
|
||||
border: var(--input-border-width) solid var(--color-primary-border);
|
||||
border-radius: var(--input-radius);
|
||||
background: var(--color-primary-bg);
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
|
||||
@ -96,3 +96,7 @@ input[type='range'] {
|
||||
background: var(--color-bg-elevated) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.gradio-slider input[type="number"] {
|
||||
padding: var(--spacing-sm) !important;
|
||||
}
|
||||
@ -9,9 +9,9 @@
|
||||
|
||||
/* Components */
|
||||
@import 'components/container';
|
||||
@import 'components/prose';
|
||||
@import 'components/scrollbar';
|
||||
@import 'components/options';
|
||||
@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);
|
||||
}
|
||||
@ -44,22 +47,33 @@ code {
|
||||
h1 {
|
||||
font-size: var(--font-size-heading1);
|
||||
line-height: var(--line-height-heading1);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--font-size-heading2);
|
||||
line-height: var(--line-height-heading2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--font-size-heading3);
|
||||
line-height: var(--line-height-heading3);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--font-size-heading4);
|
||||
line-height: var(--line-height-heading4);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h5 {
|
||||
font-size: var(--font-size-heading5);
|
||||
line-height: var(--line-height-heading5);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark, .light {
|
||||
div {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme Fix */
|
||||
@ -67,8 +81,7 @@ h5 {
|
||||
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 {
|
||||
@ -77,5 +90,6 @@ h5 {
|
||||
|
||||
#interrogate,
|
||||
#deepbooru {
|
||||
max-height: 72px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
@ -189,8 +189,8 @@
|
||||
--checkbox-label-text-color: var(--body-text-color);
|
||||
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
|
||||
--checkbox-border-radius: var(--radius-sm);
|
||||
--checkbox-label-gap: var(--spacing-lg);
|
||||
--checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
|
||||
--checkbox-label-gap: var(--spacing-sm);
|
||||
--checkbox-label-padding: var(--spacing-sm);
|
||||
--checkbox-label-text-size: var(--text-md);
|
||||
--checkbox-shadow: var(--input-shadow);
|
||||
|
||||
@ -205,8 +205,8 @@
|
||||
--input-border-color-focus: var(--neutral-700);
|
||||
--input-border-color-hover: var(--input-border-color);
|
||||
--input-placeholder-color: var(--neutral-500);
|
||||
--input-padding: var(--spacing-xl);
|
||||
--input-radius: var(--radius-lg);
|
||||
--input-padding: var(--spacing-sm);
|
||||
--input-radius: var(--radius-sm);
|
||||
--input-shadow-focus: var(--input-shadow);
|
||||
--input-text-size: var(--text-md);
|
||||
|
||||
|
||||
@ -189,8 +189,8 @@
|
||||
--checkbox-label-text-color: var(--body-text-color);
|
||||
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
|
||||
--checkbox-border-radius: var(--radius-sm);
|
||||
--checkbox-label-gap: var(--spacing-lg);
|
||||
--checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
|
||||
--checkbox-label-gap: var(--spacing-sm);
|
||||
--checkbox-label-padding: var(--spacing-sm);
|
||||
--checkbox-label-text-size: var(--text-md);
|
||||
--checkbox-shadow: var(--input-shadow);
|
||||
|
||||
@ -205,11 +205,12 @@
|
||||
--input-border-color-focus: var(--neutral-700);
|
||||
--input-border-color-hover: var(--input-border-color);
|
||||
--input-placeholder-color: var(--neutral-500);
|
||||
--input-padding: var(--spacing-xl);
|
||||
--input-radius: var(--radius-lg);
|
||||
--input-padding: var(--spacing-sm);
|
||||
--input-radius: var(--radius-sm);
|
||||
--input-shadow-focus: var(--input-shadow);
|
||||
--input-text-size: var(--text-md);
|
||||
|
||||
|
||||
/* Table */
|
||||
--table-border-color: var(--neutral-700);
|
||||
--table-even-background-fill: var(--neutral-950);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "./src/.umi-production/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
@ -14,7 +15,11 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"exclude": ["javascript"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user