✨ style(JSONEditor): add AGPL-3.0 license header, clean imports & refine Banner UI
* Added full GNU Affero General Public License v3 header at the top of `JSONEditor.js`.
* Removed unused `IconCode` and `IconRefresh` imports to eliminate dead code.
* Set `closeIcon={null}` and applied `!rounded-md` class for `Banner`, improving visual consistency and preventing unintended dismissal.
* Normalized whitespace and line-breaks for better readability and lint compliance.
This commit is contained in:
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -18,15 +37,19 @@ import {
|
||||
Tooltip,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconCode,
|
||||
IconPlus,
|
||||
IconDelete,
|
||||
IconRefresh,
|
||||
IconAlertTriangle,
|
||||
} from '@douyinfe/semi-icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// 唯一 ID 生成器,确保在组件生命周期内稳定且递增
|
||||
const generateUniqueId = (() => {
|
||||
let counter = 0;
|
||||
return () => `kv_${counter++}`;
|
||||
})();
|
||||
|
||||
const JSONEditor = ({
|
||||
value = '',
|
||||
onChange,
|
||||
@@ -46,13 +69,20 @@ const JSONEditor = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 将对象转换为键值对数组(包含唯一ID)
|
||||
const objectToKeyValueArray = useCallback((obj) => {
|
||||
const objectToKeyValueArray = useCallback((obj, prevPairs = []) => {
|
||||
if (!obj || typeof obj !== 'object') return [];
|
||||
return Object.entries(obj).map(([key, value], index) => ({
|
||||
id: `${Date.now()}_${index}_${Math.random()}`, // 唯一ID
|
||||
|
||||
const entries = Object.entries(obj);
|
||||
return entries.map(([key, value], index) => {
|
||||
// 如果上一次转换后同位置的键一致,则沿用其 id,保持 React key 稳定
|
||||
const prev = prevPairs[index];
|
||||
const shouldReuseId = prev && prev.key === key;
|
||||
return {
|
||||
id: shouldReuseId ? prev.id : generateUniqueId(),
|
||||
key,
|
||||
value
|
||||
}));
|
||||
value,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 将键值对数组转换为对象(重复键时后面的会覆盖前面的)
|
||||
@@ -135,7 +165,7 @@ const JSONEditor = ({
|
||||
// 只在外部值真正改变时更新,避免循环更新
|
||||
const currentObj = keyValueArrayToObject(keyValuePairs);
|
||||
if (JSON.stringify(parsed) !== JSON.stringify(currentObj)) {
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed));
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs));
|
||||
}
|
||||
setJsonError('');
|
||||
} catch (error) {
|
||||
@@ -175,7 +205,7 @@ const JSONEditor = ({
|
||||
if (newValue && newValue.trim()) {
|
||||
try {
|
||||
const parsed = JSON.parse(newValue);
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed));
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs));
|
||||
setJsonError('');
|
||||
onChange?.(newValue);
|
||||
} catch (error) {
|
||||
@@ -186,7 +216,7 @@ const JSONEditor = ({
|
||||
setJsonError('');
|
||||
onChange?.('');
|
||||
}
|
||||
}, [onChange, objectToKeyValueArray]);
|
||||
}, [onChange, objectToKeyValueArray, keyValuePairs]);
|
||||
|
||||
// 切换编辑模式
|
||||
const toggleEditMode = useCallback(() => {
|
||||
@@ -204,7 +234,7 @@ const JSONEditor = ({
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
parsed = value;
|
||||
}
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed));
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs));
|
||||
setJsonError('');
|
||||
setEditMode('visual');
|
||||
} catch (error) {
|
||||
@@ -225,7 +255,7 @@ const JSONEditor = ({
|
||||
newKey = `field_${counter}`;
|
||||
}
|
||||
newPairs.push({
|
||||
id: `${Date.now()}_${Math.random()}`,
|
||||
id: generateUniqueId(),
|
||||
key: newKey,
|
||||
value: ''
|
||||
});
|
||||
@@ -264,11 +294,11 @@ const JSONEditor = ({
|
||||
}
|
||||
|
||||
setManualText(templateString);
|
||||
setKeyValuePairs(objectToKeyValueArray(template));
|
||||
setKeyValuePairs(objectToKeyValueArray(template, keyValuePairs));
|
||||
onChange?.(templateString);
|
||||
setJsonError('');
|
||||
}
|
||||
}, [template, onChange, formApi, field, objectToKeyValueArray]);
|
||||
}, [template, onChange, formApi, field, objectToKeyValueArray, keyValuePairs]);
|
||||
|
||||
// 渲染值输入控件(支持嵌套)
|
||||
const renderValueInput = (pairId, value) => {
|
||||
@@ -327,7 +357,7 @@ const JSONEditor = ({
|
||||
let convertedValue = newValue;
|
||||
if (newValue === 'true') convertedValue = true;
|
||||
else if (newValue === 'false') convertedValue = false;
|
||||
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
|
||||
else if (!isNaN(newValue) && newValue !== '') {
|
||||
const num = Number(newValue);
|
||||
// 检查是否为整数
|
||||
if (Number.isInteger(num)) {
|
||||
@@ -471,7 +501,7 @@ const JSONEditor = ({
|
||||
updateValue(defaultPair.id, value);
|
||||
} else {
|
||||
const newPairs = [...keyValuePairs, {
|
||||
id: `${Date.now()}_${Math.random()}`,
|
||||
id: generateUniqueId(),
|
||||
key: 'default',
|
||||
value: value
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user