mirror of https://gitee.com/cf-fz/WebCAD.git
!2275 功能:配置列表可排序,重命名,优化配置名显示
parent
d78d6cf3e7
commit
aee595622c
@ -1,60 +1,69 @@
|
||||
import update from 'immutability-helper';
|
||||
import type { FC } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { CustomNumberStore } from './CustomNumberPanel';
|
||||
import type { CSSProperties, FC } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { CustomNumCard } from './CustomNumCard';
|
||||
import { CustomNumberStore } from './CustomNumberPanel';
|
||||
|
||||
const style = {
|
||||
width: 400,
|
||||
};
|
||||
|
||||
export interface CustomNumberItem
|
||||
{
|
||||
id: number;
|
||||
text: string;
|
||||
}
|
||||
interface ICustomNumContainer
|
||||
{
|
||||
state: CustomNumberStore;
|
||||
canDrag: boolean;
|
||||
renderConteainer: (state: CustomNumberStore, card: CustomNumberItem, index: number) => JSX.Element | string;
|
||||
clsssName?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const CustomNumContainer: FC<{ state: CustomNumberStore; }> = (prop) =>
|
||||
export const CustomNumContainer: FC<ICustomNumContainer> = (prop) =>
|
||||
{
|
||||
const [cards, setCards] = useState(prop.state.option.items);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const [cards, setCards] = useState(prop.state.option.items);
|
||||
setCards(prop.state.option.items);
|
||||
}, [prop.state.option.items]);
|
||||
|
||||
const moveCard = useCallback((dragIndex: number, hoverIndex: number) =>
|
||||
const moveCard = useCallback((dragIndex: number, hoverIndex: number) =>
|
||||
{
|
||||
setCards((prevCards: CustomNumberItem[]) =>
|
||||
{
|
||||
setCards((prevCards: CustomNumberItem[]) =>
|
||||
{
|
||||
let cards = update(prevCards, {
|
||||
$splice: [
|
||||
[dragIndex, 1],
|
||||
[hoverIndex, 0, prevCards[dragIndex] as CustomNumberItem],
|
||||
],
|
||||
});
|
||||
if (prop.state.onChange)
|
||||
prop.state.onChange(cards, dragIndex, hoverIndex);
|
||||
return cards;
|
||||
let cards = update(prevCards, {
|
||||
$splice: [
|
||||
[dragIndex, 1],
|
||||
[hoverIndex, 0, prevCards[dragIndex] as CustomNumberItem],
|
||||
],
|
||||
});
|
||||
}, []);
|
||||
if (prop.state.onChange)
|
||||
prop.state.onChange(cards, dragIndex, hoverIndex);
|
||||
return cards;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const renderCard = useCallback(
|
||||
(card: { id: number; text: string; }, index: number) =>
|
||||
{
|
||||
return (
|
||||
<CustomNumCard
|
||||
id={card.id}
|
||||
key={card.id}
|
||||
label={prop.state.label}
|
||||
orderName={prop.state.orderName}
|
||||
index={index}
|
||||
value={card.text}
|
||||
moveCard={moveCard}
|
||||
onDoubleClick={prop.state.onDoubleClick}
|
||||
/>
|
||||
);
|
||||
}, [],
|
||||
);
|
||||
const renderCard = useCallback(
|
||||
(card: { id: number; text: string; }, index: number) =>
|
||||
{
|
||||
return (
|
||||
<CustomNumCard
|
||||
id={card.id}
|
||||
key={card.id}
|
||||
index={index}
|
||||
canDrag={prop.canDrag}
|
||||
moveCard={moveCard}
|
||||
onDoubleClick={prop.state.onDoubleClick}
|
||||
conteainer={
|
||||
prop.renderConteainer(prop.state, card, index)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}, [prop.canDrag],
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={style}>{cards.map((card, i) => renderCard(card, i))}</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={prop.clsssName} style={prop.style}>{cards.map((card, i) => renderCard(card, i))}</div>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,119 @@
|
||||
import { Button, InputGroup, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { IObservableValue } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Intent } from '../../../Common/Toaster';
|
||||
import { IConfigStore } from '../../Store/BoardStore';
|
||||
import { userConfigStore } from '../../Store/UserConfigStore';
|
||||
import { BoardModalType } from './BoardModalType';
|
||||
|
||||
export interface ITabContainer
|
||||
{
|
||||
index: number;
|
||||
keyName: string;
|
||||
type: BoardModalType;
|
||||
store: IConfigStore;
|
||||
isResetName: IObservableValue<Boolean>;
|
||||
isTip?: boolean;
|
||||
}
|
||||
export const TabContainer: FC<ITabContainer> = observer(({ index, keyName, type, isResetName, store, isTip }) =>
|
||||
{
|
||||
const curIsResetName = useRef(false);
|
||||
let [curInputName, setCurInputName] = useState("");
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if (curIsResetName.current)
|
||||
{
|
||||
handleClose();
|
||||
}
|
||||
}, [store.configName]);
|
||||
|
||||
const handleOpen = (keyName: string) =>
|
||||
{
|
||||
curIsResetName.current = true;
|
||||
setCurInputName(keyName);
|
||||
isResetName.set(true);
|
||||
};
|
||||
|
||||
const handleClose = () =>
|
||||
{
|
||||
curIsResetName.current = false;
|
||||
isResetName.set(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
isResetName.get() && curIsResetName.current ?
|
||||
<div
|
||||
className='flex'
|
||||
style={{ paddingLeft: 2, backgroundColor: "color(display-p3 0.43 1 0.65 / 0.2)" }}
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
>
|
||||
<div style={{ flex: 1 }}>
|
||||
<InputGroup
|
||||
value={curInputName}
|
||||
style={{ width: "100%", height: 28, marginTop: 1 }}
|
||||
onChange={e =>
|
||||
{
|
||||
if (e.target.value.length <= 15)
|
||||
setCurInputName(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => { e.stopPropagation(); }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: 60 }}>
|
||||
<Button
|
||||
icon="small-tick"
|
||||
minimal
|
||||
intent={Intent.SUCCESS}
|
||||
onClick={async () =>
|
||||
{
|
||||
if (curInputName.length && curInputName != keyName)
|
||||
{
|
||||
await userConfigStore.ResetNameConfig(type, store, keyName, curInputName, index);
|
||||
}
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon="small-cross"
|
||||
minimal
|
||||
intent={Intent.DANGER}
|
||||
onClick={() => { handleClose(); }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className="flex" style={{ padding: "0 10px" }} >
|
||||
<div style={{ overflow: "hidden" }}>
|
||||
<Tooltip content={keyName} disabled={!isTip || keyName.length <= 10} position={Position.TOP} >
|
||||
<div>{keyName}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{
|
||||
!isResetName.get() &&
|
||||
<div className="rename">
|
||||
<Button
|
||||
small
|
||||
icon="edit"
|
||||
style={{
|
||||
backgroundImage: "none",
|
||||
boxShadow: "none",
|
||||
backgroundColor: "rgba(19, 124, 189, 0.2)"
|
||||
}}
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
handleOpen(keyName);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,116 +1,167 @@
|
||||
.config-list {
|
||||
width : 180px;
|
||||
height : 100%;
|
||||
display : flex;
|
||||
flex-direction: column;
|
||||
|
||||
h4 {
|
||||
font-size : 16px;
|
||||
margin-left : 3px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&>:nth-child(2) {
|
||||
padding : 3px;
|
||||
flex-grow : 1;
|
||||
flex-basis : 60px;
|
||||
border : 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
overflow-y : auto;
|
||||
}
|
||||
|
||||
.bp3-tab-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.config-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items : center;
|
||||
|
||||
&>:nth-child(n) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&>label {
|
||||
&>:nth-child(1) {
|
||||
font-size : 14px;
|
||||
font-weight : 600;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.bp3-input-group {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bp3-input {
|
||||
width : 7rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.err-input{
|
||||
.err-input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.err-input{
|
||||
border: 1px solid red;
|
||||
border-radius: 5px;
|
||||
|
||||
.bp3-input:focus{
|
||||
box-shadow: 0 0 0 0 red;
|
||||
}
|
||||
}
|
||||
|
||||
#commonModal .config-list .config-input .bp3-input {
|
||||
width : 7rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#commonModal .userconfig .bp3-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.userconfig {
|
||||
display: flex;
|
||||
|
||||
.input-select {
|
||||
&>span:first-of-type {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.userconfig-tag {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.err-input{
|
||||
.err-input();
|
||||
}
|
||||
|
||||
.input-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&>:nth-child(n) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
&>:nth-child(1) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.bp3-input{
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 180px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.config-tabs {
|
||||
padding: 3px;
|
||||
flex-grow: 1;
|
||||
flex-basis: 60px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.custom-number-list {
|
||||
flex-grow: 1;
|
||||
flex-basis: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.config-custom-number {
|
||||
flex: 1;
|
||||
border: 1px solid #ccc;
|
||||
margin: 3px;
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
overflow-y: auto;
|
||||
|
||||
.item {
|
||||
cursor: move;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 2px;
|
||||
background-color: #cccccc54;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: rgba(19, 124, 189, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-tabs.bp3-vertical>.bp3-tab-list {
|
||||
width: 100%;
|
||||
|
||||
.bp3-tab {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.config-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&>:nth-child(n) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&>label {
|
||||
&>:nth-child(1) {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.bp3-input-group {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bp3-input {
|
||||
width: 7rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.err-input {
|
||||
.err-input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.err-input {
|
||||
border: 1px solid red;
|
||||
border-radius: 5px;
|
||||
|
||||
.bp3-input:focus {
|
||||
box-shadow: 0 0 0 0 red;
|
||||
}
|
||||
}
|
||||
|
||||
#commonModal .config-list .config-input .bp3-input {
|
||||
width: 7rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#commonModal .userconfig .bp3-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.userconfig {
|
||||
display: flex;
|
||||
|
||||
.input-select {
|
||||
&>span:first-of-type {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.userconfig-tag {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.err-input {
|
||||
.err-input();
|
||||
}
|
||||
|
||||
.input-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&>:nth-child(n) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
&>:nth-child(1) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.bp3-input {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-tab,
|
||||
.config-menuitem {
|
||||
.rename {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: -1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.rename:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.config-menuitem {
|
||||
position: relative;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
import { arrayRemoveIf } from "../../Common/ArrayExt";
|
||||
import { ConfigUrls } from "../../Common/HostUrl";
|
||||
import { PostJson, RequestStatus } from "../../Common/Request";
|
||||
import { Singleton } from "../../Common/Singleton";
|
||||
import { GetIndexDBID } from "../../Common/Utils";
|
||||
import { IndexedDbStore, StoreName } from "../../IndexedDb/IndexedDbStore";
|
||||
import { BoardModalType } from "../Components/Board/BoardModalType";
|
||||
import { IConfigOption } from "../Components/Board/UserConfigComponent";
|
||||
import { IConfigStore } from "./BoardStore";
|
||||
import { ConfigListMapOption } from "./OptionInterface/IOptionInterface";
|
||||
import { userConfigStore } from "./UserConfigStore";
|
||||
|
||||
export class ConfigListMapStore extends Singleton
|
||||
{
|
||||
configListMap = new Map<string, string[]>();
|
||||
|
||||
m_Option: ConfigListMapOption = {
|
||||
version: 1,
|
||||
configList: Object.fromEntries(this.configListMap.entries())
|
||||
};
|
||||
|
||||
InitOption()
|
||||
{
|
||||
this.m_Option = {
|
||||
version: 1,
|
||||
configList: Object.fromEntries(this.configListMap.entries())
|
||||
};
|
||||
}
|
||||
|
||||
async GetConfig(key: string)
|
||||
{
|
||||
const configs = (await userConfigStore.GetAllConfigs(key)) || {};
|
||||
const confNames = Array.from(Object.keys(configs));
|
||||
|
||||
const names = [];
|
||||
if (!this.configListMap.get(key))
|
||||
{
|
||||
names.push(...confNames);
|
||||
}
|
||||
else
|
||||
{
|
||||
const configListNames = this.configListMap.get(key);
|
||||
if (JSON.stringify(confNames.concat().sort()) != JSON.stringify(configListNames.concat().sort()))
|
||||
{
|
||||
//这里提供一个数据验证,提示数据出错
|
||||
console.error(`配置名称对不上,type:${key}`);
|
||||
names.push(...confNames);
|
||||
}
|
||||
else
|
||||
names.push(...configListNames);
|
||||
}
|
||||
return names.concat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加store.configsNames的时候请调用这里
|
||||
*/
|
||||
async AddConfig(key: string, addNames: string[], store: IConfigStore)
|
||||
{
|
||||
const names = await this.GetConfig(key);
|
||||
|
||||
const isInit = addNames.length > 1; //初始配置时可能存在多个
|
||||
const isExist = addNames.length === 1 && names.includes(addNames[0]); //判断是否存在
|
||||
|
||||
if (isInit || !isExist)
|
||||
names.push(...addNames);
|
||||
|
||||
store.configsNames = names;
|
||||
|
||||
this.configListMap.set(key, names);
|
||||
await this.SaveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改store.configsNames的时候请调用这里
|
||||
*/
|
||||
async ResetConfig(key: string, resetName: string[], store: IConfigStore)
|
||||
{
|
||||
store.configsNames = resetName;
|
||||
|
||||
this.configListMap.set(key, resetName.concat());
|
||||
await this.SaveConfig();
|
||||
45;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除store.configsNames的时候请调用这里
|
||||
*/
|
||||
async DeleteConfig(key: string, name: string, store: IConfigStore)
|
||||
{
|
||||
const names = await this.GetConfig(key);
|
||||
|
||||
arrayRemoveIf(names, (n) => n === name);
|
||||
store.configsNames = names;
|
||||
|
||||
if (names.length)
|
||||
this.configListMap.set(key, names);
|
||||
else
|
||||
this.configListMap.delete(key);
|
||||
await this.SaveConfig();
|
||||
}
|
||||
|
||||
async SaveConfig()
|
||||
{
|
||||
let newConfig: IConfigOption = {};
|
||||
this.m_Option.configList = Object.fromEntries(this.configListMap.entries());
|
||||
newConfig.option = this.m_Option;
|
||||
|
||||
let dbStore = await IndexedDbStore.CADStore();
|
||||
let data = await PostJson(ConfigUrls.Edit, { key: BoardModalType.ConfigListMapStore, value: JSON.stringify(newConfig) });
|
||||
if (data.err_code === RequestStatus.Ok)
|
||||
{
|
||||
dbStore.Put(StoreName.ConfigData, GetIndexDBID(BoardModalType.ConfigListMapStore), newConfig);
|
||||
dbStore.Put(StoreName.ConfigVersion, GetIndexDBID(BoardModalType.ConfigListMapStore), data.version);
|
||||
}
|
||||
};
|
||||
|
||||
UpdateOption(cof: IConfigOption<ConfigListMapOption>)
|
||||
{
|
||||
this.configListMap = new Map(Object.entries(cof.option.configList));
|
||||
Object.assign(this.m_Option, cof.option);
|
||||
}
|
||||
}
|
||||
|
||||
export const configListMapStore = ConfigListMapStore.GetInstance();
|
Loading…
Reference in new issue