danoniplus 40.2.0 → 40.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/danoni_main.js +144 -49
- package/js/lib/danoni_constants.js +39 -2
- package/package.json +1 -1
package/js/danoni_main.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Source by tickle
|
|
6
6
|
* Created : 2018/10/08
|
|
7
|
-
* Revised : 2025/03/
|
|
7
|
+
* Revised : 2025/03/10
|
|
8
8
|
*
|
|
9
9
|
* https://github.com/cwtickle/danoniplus
|
|
10
10
|
*/
|
|
11
|
-
const g_version = `Ver 40.
|
|
12
|
-
const g_revisedDate = `2025/03/
|
|
11
|
+
const g_version = `Ver 40.3.1`;
|
|
12
|
+
const g_revisedDate = `2025/03/10`;
|
|
13
13
|
|
|
14
14
|
// カスタム用バージョン (danoni_custom.js 等で指定可)
|
|
15
15
|
let g_localVersion = ``;
|
|
@@ -475,19 +475,20 @@ const getIndent = (_level) => ' '.repeat(_level * 4);
|
|
|
475
475
|
* ストレージ情報の取得
|
|
476
476
|
* @param {string} _name g_storageFuncの実行キー名
|
|
477
477
|
* @param {string} _key g_storageFuncの実行キーの引数
|
|
478
|
+
* @param {boolean} [_colorFmt=true]
|
|
478
479
|
* @returns {string}
|
|
479
480
|
*/
|
|
480
|
-
const viewKeyStorage = (_name, _key =
|
|
481
|
+
const viewKeyStorage = (_name, _key = ``, _colorFmt = true) => {
|
|
481
482
|
|
|
482
483
|
// キャッシュ設定
|
|
483
484
|
if (!viewKeyStorage.cache) {
|
|
484
485
|
viewKeyStorage.cache = new Map();
|
|
485
486
|
}
|
|
486
|
-
const cacheKey = _key + _name;
|
|
487
|
+
const cacheKey = _key + _name + String(_colorFmt);
|
|
487
488
|
if (viewKeyStorage.cache.has(cacheKey)) {
|
|
488
489
|
return viewKeyStorage.cache.get(cacheKey);
|
|
489
490
|
}
|
|
490
|
-
const result = formatObject(g_storageFunc.get(_name)?.(_key) || setVal(_name, ``, C_TYP_CALC));
|
|
491
|
+
const result = formatObject(g_storageFunc.get(_name)?.(_key) || setVal(_name, ``, C_TYP_CALC), 0, { colorFmt: _colorFmt });
|
|
491
492
|
viewKeyStorage.cache.set(cacheKey, result);
|
|
492
493
|
return result;
|
|
493
494
|
}
|
|
@@ -498,11 +499,11 @@ const viewKeyStorage = (_name, _key = ``) => {
|
|
|
498
499
|
* @param {Number} _indent
|
|
499
500
|
* @param {boolean} [colorFmt=true] フォーマット加工フラグ
|
|
500
501
|
* @param {string} [rootKey=''] オブジェクトの最上位プロパティ名
|
|
501
|
-
* @param {Object} [_parent=null]
|
|
502
502
|
* @returns {string}
|
|
503
503
|
*/
|
|
504
504
|
const formatObject = (_obj, _indent = 0, { colorFmt = true, rootKey = `` } = {}) => {
|
|
505
|
-
|
|
505
|
+
const isObj = _obj => typeof _obj === C_TYP_OBJECT && _obj !== null;
|
|
506
|
+
if (!isObj(_obj)) {
|
|
506
507
|
return JSON.stringify(_obj);
|
|
507
508
|
}
|
|
508
509
|
const baseIndent = getIndent(_indent);
|
|
@@ -541,53 +542,101 @@ const formatObject = (_obj, _indent = 0, { colorFmt = true, rootKey = `` } = {})
|
|
|
541
542
|
// keyCtrlXの対応キー表示処理
|
|
542
543
|
return (g_kCd[_value] && _value !== 0) ? `${_value}|<span style="color:#ffff66">${g_kCd[_value]}</span>` : `----`;
|
|
543
544
|
}
|
|
544
|
-
} else if (
|
|
545
|
+
} else if (isObj(_value)) {
|
|
545
546
|
return formatObject(_value, _indent + 1, { colorFmt, rootKey: _rootKey });
|
|
546
547
|
}
|
|
547
548
|
}
|
|
548
549
|
return JSON.stringify(_value);
|
|
549
550
|
};
|
|
550
551
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
552
|
+
/**
|
|
553
|
+
* 配列の装飾処理
|
|
554
|
+
* @param {number[]|string[]} _obj
|
|
555
|
+
* @returns {string}
|
|
556
|
+
*/
|
|
557
|
+
const formatArrayValue = (_obj) => {
|
|
558
|
+
|
|
559
|
+
const formatSetArray = (_list, _numOfSet = 2) => {
|
|
560
|
+
if (_list.findIndex(val => val === rootKey) >= 0) {
|
|
561
|
+
let result = `[`;
|
|
562
|
+
for (let j = 0; j < _obj.length; j += _numOfSet) {
|
|
563
|
+
result += `<br>${nestedIndent}${_obj[j]}: ${_obj[j + 1]}`;
|
|
564
|
+
for (let k = 0; k < _numOfSet - 2; k++) {
|
|
565
|
+
const idx = j + k + 2;
|
|
566
|
+
if (idx < _obj.length) {
|
|
567
|
+
result += `, ${formatValue(_obj[idx], rootKey)}`;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
560
570
|
}
|
|
571
|
+
result += (_obj.length === 0 ? `` : `<br>${baseIndent}`) + `]`;
|
|
561
572
|
return result;
|
|
562
|
-
}
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
573
|
+
}
|
|
574
|
+
return ``;
|
|
575
|
+
};
|
|
576
|
+
if (colorFmt) {
|
|
577
|
+
if (typeof _obj[0] === C_TYP_NUMBER) {
|
|
578
|
+
let result;
|
|
579
|
+
Object.keys(g_dataSetObj).forEach(key =>
|
|
580
|
+
result ||= formatSetArray(g_dataSetObj[key], Number(key)));
|
|
581
|
+
if (result !== ``) {
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (_obj.length > 100) {
|
|
586
|
+
const filteredArray = _obj.reduce((result, value, index) => {
|
|
587
|
+
if (hasVal(value)) {
|
|
588
|
+
result.push(`${index}: ${formatValue(value, rootKey)}`);
|
|
589
|
+
}
|
|
590
|
+
return result;
|
|
591
|
+
}, []);
|
|
592
|
+
return `[<br>${nestedIndent}${filteredArray.join(`,<br>${nestedIndent}`)}<br>${baseIndent}]`;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return ``;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 配列・オブジェクトのネスト整形処理
|
|
600
|
+
* @returns {string}
|
|
601
|
+
*/
|
|
602
|
+
const formatCollection = () => {
|
|
603
|
+
const isArray = Array.isArray(_obj);
|
|
604
|
+
const isArrayOfArrays = isArray && _obj.every(item => Array.isArray(item));
|
|
605
|
+
const getNextObject = (_item, _rootKey) => isObj(_item)
|
|
606
|
+
? formatObject(_item, _indent + 1, { colorFmt, rootKey: _rootKey })
|
|
607
|
+
: formatValue(_item, _rootKey);
|
|
608
|
+
|
|
609
|
+
if (isArray) {
|
|
610
|
+
let result = formatArrayValue(_obj);
|
|
611
|
+
if (result !== ``) {
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 配列またはオブジェクトの各要素をフォーマット
|
|
617
|
+
const formattedEntries = (isArray
|
|
618
|
+
? _obj.map(item => {
|
|
619
|
+
const formattedValue = isArrayOfArrays
|
|
620
|
+
? `<br>${nestedIndent}${formatValue(item, rootKey)}`
|
|
621
|
+
: getNextObject(item, rootKey);
|
|
622
|
+
return formattedValue;
|
|
623
|
+
})
|
|
624
|
+
: Object.entries(_obj).map(([key, value]) => {
|
|
625
|
+
const formattedValue = getNextObject(value, rootKey === `` ? key : rootKey);
|
|
626
|
+
return `<br>${nestedIndent}"${key}": ${formattedValue}`;
|
|
627
|
+
})).filter(val => !hasVal(val) || val !== `----`);
|
|
628
|
+
|
|
629
|
+
// 配列なら[]で囲む、オブジェクトなら{}で囲む
|
|
630
|
+
if (isArray) {
|
|
631
|
+
return _obj.length === 0
|
|
632
|
+
? '[]'
|
|
633
|
+
: `[${formattedEntries.join(', ')}${isArrayOfArrays ? `<br>${baseIndent}` : ''}]`;
|
|
634
|
+
} else {
|
|
635
|
+
return `{${formattedEntries.join(',')}<br>${baseIndent}}`;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
let result = formatCollection();
|
|
591
640
|
if (!colorFmt) {
|
|
592
641
|
result = result.replaceAll(`<br>`, `\r\n`).replaceAll(` `, ` `);
|
|
593
642
|
}
|
|
@@ -2538,6 +2587,44 @@ const initialControl = async () => {
|
|
|
2538
2587
|
delete g_keyObj[key];
|
|
2539
2588
|
}
|
|
2540
2589
|
});
|
|
2590
|
+
|
|
2591
|
+
// エディター用のフォーマッター作成
|
|
2592
|
+
const customKeyList = g_headerObj.keyLists.filter(val =>
|
|
2593
|
+
g_keyObj.defaultKeyList.findIndex(key => key === val) < 0);
|
|
2594
|
+
|
|
2595
|
+
customKeyList.forEach(key => {
|
|
2596
|
+
const keyBase = `${key}_0`;
|
|
2597
|
+
const keyCtrlPtn = `${g_keyObj.defaultProp}${keyBase}`;
|
|
2598
|
+
const keyGroup = g_keyObj[`keyGroup${keyBase}`];
|
|
2599
|
+
const keyGroupList = makeDedupliArray(keyGroup.flat());
|
|
2600
|
+
const orgKeyNum = g_keyObj[keyCtrlPtn].length;
|
|
2601
|
+
const baseX = Math.floor(Math.random() * (100 - keyGroupList.length));
|
|
2602
|
+
|
|
2603
|
+
keyGroupList.forEach((keyGroupNo, j) => {
|
|
2604
|
+
const keyN = keyGroupNo === `0` ? key : `${key}_${j + 1}`;
|
|
2605
|
+
const filterCond = (j) => keyGroup[j].findIndex(val => val === keyGroupNo) >= 0;
|
|
2606
|
+
const keyCtrlList = g_keyObj[keyCtrlPtn].filter((val, j) => filterCond(j));
|
|
2607
|
+
const charaList = g_keyObj[`chara${keyBase}`].filter((val, j) => filterCond(j));
|
|
2608
|
+
const colorList = g_keyObj[`color${keyBase}_0`].filter((val, j) => filterCond(j));
|
|
2609
|
+
const keyNum = g_keyObj[keyCtrlPtn].filter((val, j) => filterCond(j)).length;
|
|
2610
|
+
|
|
2611
|
+
g_editorTmp[keyN] = {};
|
|
2612
|
+
g_editorTmp[keyN].id = orgKeyNum * 100 + baseX + j;
|
|
2613
|
+
g_editorTmp[keyN].num = keyNum;
|
|
2614
|
+
g_editorTmp[keyN].chars = keyCtrlList.map(val => g_kCd[val[0]]);
|
|
2615
|
+
g_editorTmp[keyN].keys = keyCtrlList.map(val => g_kCdN[val[0]]).map(val => replaceStr(val, g_escapeStr.editorKey));
|
|
2616
|
+
g_editorTmp[keyN].alternativeKeys = keyCtrlList.map(val => val[1] === 0 ? `` : g_kCdN[val[1]]).map(val => replaceStr(val, g_escapeStr.editorKey));
|
|
2617
|
+
g_editorTmp[keyN].noteNames = charaList.map(val => `${val}_data`);
|
|
2618
|
+
g_editorTmp[keyN].freezeNames = charaList.map(val => {
|
|
2619
|
+
let frzName = replaceStr(val, g_escapeStr.frzName);
|
|
2620
|
+
if (frzName.indexOf(`frz`) === -1 && frzName.indexOf(`foni`) === -1) {
|
|
2621
|
+
frzName = frzName.replaceAll(frzName, `frz${toCapitalize(frzName)}`);
|
|
2622
|
+
}
|
|
2623
|
+
return `${frzName}_data`;
|
|
2624
|
+
});
|
|
2625
|
+
g_editorTmp[keyN].colorGroup = colorList.map(val => val % 3);
|
|
2626
|
+
});
|
|
2627
|
+
});
|
|
2541
2628
|
};
|
|
2542
2629
|
|
|
2543
2630
|
/**
|
|
@@ -5009,10 +5096,12 @@ const dataMgtInit = () => {
|
|
|
5009
5096
|
g_stateObj[`dm_${key}`] = C_FLG_OFF;
|
|
5010
5097
|
g_settings.dataMgtNum[key] = 0;
|
|
5011
5098
|
|
|
5099
|
+
const keyWidth = Math.min(Math.max(50, getStrWidth(getKeyName(key), g_limitObj.setLblSiz, getBasicFont())), 80);
|
|
5012
5100
|
keyListSprite.appendChild(createMgtButton(key, j - 2, 0, {
|
|
5013
|
-
w:
|
|
5101
|
+
w: keyWidth,
|
|
5102
|
+
siz: getFontSize(getKeyName(key), keyWidth, getBasicFont(), g_limitObj.setLblSiz, 10),
|
|
5014
5103
|
}));
|
|
5015
|
-
document.getElementById(`btn${key}`).innerHTML = getKeyName(key);
|
|
5104
|
+
document.getElementById(`btn${toCapitalize(key)}`).innerHTML = getKeyName(key);
|
|
5016
5105
|
|
|
5017
5106
|
keyListSprite.appendChild(createCss2Button(`btnView${key}`, ``, evt => {
|
|
5018
5107
|
keyList.forEach(keyx => {
|
|
@@ -5146,6 +5235,11 @@ const preconditionInit = () => {
|
|
|
5146
5235
|
.replace(/[\t\n]/g, ``), 0, 15, g_cssObj.flex_centering),
|
|
5147
5236
|
|
|
5148
5237
|
createDivCss2Label(`lblPrecondView`, viewKeyStorage(`g_rootObj`), g_lblPosObj.lblPrecondView),
|
|
5238
|
+
createCss2Button(`btnPrecondView`, g_lblNameObj.b_copyStorage, () =>
|
|
5239
|
+
copyTextToClipboard(
|
|
5240
|
+
viewKeyStorage(g_settings.preconditions[g_settings.preconditionNum * numOfPrecs + g_settings.preconditionNumSub], ``, false),
|
|
5241
|
+
g_msgInfoObj.I_0007),
|
|
5242
|
+
g_lblPosObj.btnPrecondView, g_cssObj.button_Default, g_cssObj.button_ON),
|
|
5149
5243
|
);
|
|
5150
5244
|
setUserSelect($id(`lblPrecondView`), `text`);
|
|
5151
5245
|
|
|
@@ -5175,6 +5269,7 @@ const preconditionInit = () => {
|
|
|
5175
5269
|
}
|
|
5176
5270
|
lblPrecondView.innerHTML = viewKeyStorage(g_settings.preconditions[g_settings.preconditionNum * numOfPrecs + j]);
|
|
5177
5271
|
lblPrecondView.scrollTop = 0;
|
|
5272
|
+
g_settings.preconditionNumSub = j;
|
|
5178
5273
|
evt.target.classList.replace(g_cssObj.button_Default, g_cssObj.button_Reset);
|
|
5179
5274
|
}, {
|
|
5180
5275
|
x: g_btnX() + g_btnWidth((j % (numOfPrecs / 2)) / (numOfPrecs / 2 + 1)),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2019/11/19
|
|
8
|
-
* Revised : 2025/03/
|
|
8
|
+
* Revised : 2025/03/10 (v40.3.1)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -283,6 +283,9 @@ const updateWindowSiz = () => {
|
|
|
283
283
|
btnKeyStorage: {
|
|
284
284
|
x: g_btnX(1) - 140, y: 100 + g_sHeight / 4 + 10, w: 70, h: 20, siz: 16,
|
|
285
285
|
},
|
|
286
|
+
btnPrecondView: {
|
|
287
|
+
x: g_btnX(1) - 90, y: 110, w: 70, h: 20, siz: 16,
|
|
288
|
+
},
|
|
286
289
|
|
|
287
290
|
/** 設定画面 */
|
|
288
291
|
btnBack: {
|
|
@@ -785,6 +788,18 @@ const g_escapeStr = {
|
|
|
785
788
|
[`all[]`, `sumData(g_detailObj.arrowCnt[{0}]) + sumData(g_detailObj.frzCnt[{0}])`],
|
|
786
789
|
[`maxlife[]`, `g_headerObj.maxLifeVal`],
|
|
787
790
|
],
|
|
791
|
+
editorKey: [
|
|
792
|
+
[`ArrowLeft`, `KeyU`],
|
|
793
|
+
[`ArrowDown`, `KeyI`],
|
|
794
|
+
[`ArrowUp`, `Digit8`],
|
|
795
|
+
[`ArrowRight`, `KeyO`],
|
|
796
|
+
[`Space`, `KeyG`],
|
|
797
|
+
[`KeyB`, `KeyH`],
|
|
798
|
+
[`Enter`, `BackSlash`],
|
|
799
|
+
[`ShiftLeft`, `KeyZ`],
|
|
800
|
+
[`ShiftRight`, `Slash`],
|
|
801
|
+
[`Tab`, `KeyQ`],
|
|
802
|
+
]
|
|
788
803
|
};
|
|
789
804
|
|
|
790
805
|
/** 設定・オプション画面用共通 */
|
|
@@ -1156,8 +1171,9 @@ const g_settings = {
|
|
|
1156
1171
|
settingWindowNum: 0,
|
|
1157
1172
|
|
|
1158
1173
|
preconditions: [`g_rootObj`, `g_headerObj`, `g_keyObj`, `g_scoreObj`, `g_workObj`,
|
|
1159
|
-
`g_detailObj`, `g_stateObj`, `g_attrObj`],
|
|
1174
|
+
`g_detailObj`, `g_stateObj`, `g_attrObj`, `g_editorTmp`],
|
|
1160
1175
|
preconditionNum: 0,
|
|
1176
|
+
preconditionNumSub: 0,
|
|
1161
1177
|
};
|
|
1162
1178
|
|
|
1163
1179
|
g_settings.volumeNum = g_settings.volumes.length - 1;
|
|
@@ -2487,6 +2503,7 @@ const g_keyObj = {
|
|
|
2487
2503
|
dfPtnNum: 0,
|
|
2488
2504
|
|
|
2489
2505
|
minKeyCtrlNum: 2,
|
|
2506
|
+
defaultKeyList: [],
|
|
2490
2507
|
|
|
2491
2508
|
// キー別ヘッダー
|
|
2492
2509
|
// - 譜面データ中に出てくる矢印(ノーツ)の種類と順番(ステップゾーン表示順)を管理する
|
|
@@ -3023,6 +3040,11 @@ const g_keyObj = {
|
|
|
3023
3040
|
// g_keyObj.defaultProp の上書きを禁止
|
|
3024
3041
|
Object.defineProperty(g_keyObj, `defaultProp`, { writable: false });
|
|
3025
3042
|
|
|
3043
|
+
// 既定のキー定義リストを動的に作成
|
|
3044
|
+
Object.keys(g_keyObj)
|
|
3045
|
+
.filter(key => key.startsWith(g_keyObj.defaultProp) && key.endsWith(`_0`))
|
|
3046
|
+
.forEach(key => g_keyObj.defaultKeyList.push(key.split(`_`)[0].slice(g_keyObj.defaultProp.length)));
|
|
3047
|
+
|
|
3026
3048
|
// キーパターンのコピーリスト
|
|
3027
3049
|
// ・コピー先:コピー元の順に指定する
|
|
3028
3050
|
// ・上から順に処理する
|
|
@@ -3157,6 +3179,8 @@ g_keycons.groups.forEach(type => {
|
|
|
3157
3179
|
tmpName.forEach(property => g_keyObj[`${property.slice(0, -2)}`] = g_keyObj[property].concat());
|
|
3158
3180
|
});
|
|
3159
3181
|
|
|
3182
|
+
const g_editorTmp = {};
|
|
3183
|
+
|
|
3160
3184
|
// 特殊キーのコピー種 (simple: 代入、multiple: 配列ごと代入)
|
|
3161
3185
|
// 後でプロパティ削除に影響するため、先頭文字が全く同じ場合は長い方を先に定義する (例: divMax, div)
|
|
3162
3186
|
const g_keyCopyLists = {
|
|
@@ -3235,6 +3259,17 @@ const g_dataMinObj = {
|
|
|
3235
3259
|
style: 1,
|
|
3236
3260
|
};
|
|
3237
3261
|
|
|
3262
|
+
/**
|
|
3263
|
+
* データフォーマット管理用
|
|
3264
|
+
* - セット数: 対象の配列名の組で記述
|
|
3265
|
+
*/
|
|
3266
|
+
const g_dataSetObj = {
|
|
3267
|
+
2: [`speedData`, `boostData`],
|
|
3268
|
+
4: [`colorData`, `arrowCssMotionData`, `frzCssMotionData`,
|
|
3269
|
+
`dummyArrowCssMotionData`, `dummyFrzCssMotionData`],
|
|
3270
|
+
5: [`ncolorData`, `scrollchData`],
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3238
3273
|
const g_dfColorObj = {
|
|
3239
3274
|
|
|
3240
3275
|
// 矢印初期色情報
|
|
@@ -3447,6 +3482,7 @@ const g_lang_msgInfoObj = {
|
|
|
3447
3482
|
I_0004: `musicUrlが設定されていないため、無音モードで再生します`,
|
|
3448
3483
|
I_0005: `正規のミラー譜面で無いため、ハイスコアは保存されません`,
|
|
3449
3484
|
I_0006: `ローカルストレージ情報をクリップボードにコピーしました!`,
|
|
3485
|
+
I_0007: `オブジェクト情報をクリップボードにコピーしました!`,
|
|
3450
3486
|
},
|
|
3451
3487
|
En: {
|
|
3452
3488
|
W_0001: `Your browser is not guaranteed to work.<br>
|
|
@@ -3501,6 +3537,7 @@ const g_lang_msgInfoObj = {
|
|
|
3501
3537
|
I_0004: `Play in silence mode because "musicUrl" is not set`,
|
|
3502
3538
|
I_0005: `Highscore is not saved because not a regular mirrored chart.`,
|
|
3503
3539
|
I_0006: `Local storage information copied to clipboard!`,
|
|
3540
|
+
I_0007: `Object information copied to clipboard!`,
|
|
3504
3541
|
},
|
|
3505
3542
|
};
|
|
3506
3543
|
|