danoniplus 39.4.2 → 39.6.0

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 CHANGED
@@ -4,19 +4,19 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2025/02/12
7
+ * Revised : 2025/02/16
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 39.4.2`;
12
- const g_revisedDate = `2025/02/12`;
11
+ const g_version = `Ver 39.6.0`;
12
+ const g_revisedDate = `2025/02/16`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
16
16
  let g_localVersion2 = ``;
17
17
 
18
18
  // ショートカット用文字列(↓の文字列を検索することで対象箇所へジャンプできます)
19
- // 共通:water 初期化:peach タイトル:melon 設定:lime ディスプレイ:lemon 拡張設定:apple キーコンフィグ:orange 譜面読込:strawberry メイン:banana 結果:grape
19
+ // 共通:water 初期化:peach タイトル:melon データ管理:pear 設定:lime ディスプレイ:lemon 拡張設定:apple キーコンフィグ:orange 譜面読込:strawberry メイン:banana 結果:grape
20
20
  // シーンジャンプ:Scene
21
21
 
22
22
  /**
@@ -189,6 +189,7 @@ let g_langStorage = {};
189
189
  // ローカルストレージ設定 (作品別)
190
190
  let g_localStorage;
191
191
  let g_localStorageUrl;
192
+ let g_localStorageMgt;
192
193
 
193
194
  // ローカルストレージ設定 (ドメイン・キー別)
194
195
  let g_localKeyStorage;
@@ -406,6 +407,78 @@ const splitLF2 = (_str, _delim = `$`) => splitLF(_str)?.filter(val => val !== ``
406
407
  */
407
408
  const splitComma = _str => _str?.split(`, `).join(`*comma* `).split(`,`);
408
409
 
410
+ /**
411
+ * ストレージ処理のパース
412
+ * @param {string} _keyName
413
+ * @param {Object} _default
414
+ * @returns {Object}
415
+ */
416
+ const parseStorageData = (_keyName, _default = {}) => {
417
+ const storageText = localStorage.getItem(_keyName);
418
+ if (storageText === null) {
419
+ return _default;
420
+ }
421
+ try {
422
+ return JSON.parse(storageText);
423
+ } catch (err) {
424
+ return _default;
425
+ }
426
+ }
427
+
428
+ /**
429
+ * 画面表示用インデント処理
430
+ * @param {number} _level
431
+ * @returns {string}
432
+ */
433
+ const getIndent = (_level) => ' '.repeat(_level * 4);
434
+
435
+ /**
436
+ * ストレージ情報の取得
437
+ * @param {string} _name g_storageFuncの実行キー名
438
+ * @param {string} _key g_storageFuncの実行キーの引数
439
+ * @returns {string}
440
+ */
441
+ const viewKeyStorage = (_name, _key = ``) => {
442
+
443
+ // キャッシュ設定
444
+ if (!viewKeyStorage.cache) {
445
+ viewKeyStorage.cache = new Map();
446
+ }
447
+ const cacheKey = _key + _name;
448
+ if (viewKeyStorage.cache.has(cacheKey)) {
449
+ return viewKeyStorage.cache.get(cacheKey);
450
+ }
451
+ const result = formatObject(g_storageFunc.get(_name)(_key));
452
+ viewKeyStorage.cache.set(cacheKey, result);
453
+ return result;
454
+ }
455
+
456
+ /**
457
+ * オブジェクトのネスト表示処理
458
+ * @param {Object} _obj
459
+ * @param {Number} _indent
460
+ * @param {WeakSet} _seen
461
+ * @returns {string}
462
+ */
463
+ const formatObject = (_obj, _indent = 0, _seen = new WeakSet()) => {
464
+ if (_obj === null || typeof _obj !== 'object') {
465
+ return JSON.stringify(_obj);
466
+ }
467
+ if (_seen.has(_obj)) {
468
+ return '[Circular]';
469
+ }
470
+ _seen.add(_obj);
471
+ const baseIndent = getIndent(_indent);
472
+ const nestedIndent = getIndent(_indent + 1);
473
+ const formattedEntries = Object.entries(_obj)
474
+ .map(([key, value]) => {
475
+ const isNestedObject = typeof value === 'object' && value !== null && !Array.isArray(value);
476
+ const formattedValue = isNestedObject ? formatObject(value, _indent + 1, _seen) : JSON.stringify(value);
477
+ return `<br>${nestedIndent}"${key}": ${formattedValue}`;
478
+ }).join(`,`);
479
+ return `{${formattedEntries}<br>${baseIndent}}`;
480
+ }
481
+
409
482
  /**
410
483
  * 重複を排除した配列の生成
411
484
  * @param {any[]} _array1
@@ -1089,13 +1162,27 @@ const getStrWidth = (_str, _fontsize, _font) => {
1089
1162
  */
1090
1163
  const getFontSize = (_str, _maxWidth, _font = getBasicFont(), _maxFontsize = 64, _minFontsize = 5) => {
1091
1164
  for (let siz = _maxFontsize; siz >= _minFontsize; siz--) {
1092
- if (_maxWidth >= getStrWidth(_str, siz, _font)) {
1165
+ if (_maxWidth >= getStrWidth(getLongestStr(_str?.split(`<br>`)), siz, _font)) {
1093
1166
  return siz;
1094
1167
  }
1095
1168
  }
1096
1169
  return _minFontsize;
1097
1170
  };
1098
1171
 
1172
+ /**
1173
+ * 配列中から最も長い文字列を抽出
1174
+ * @param {string[]} _array
1175
+ * @returns {string}
1176
+ */
1177
+ const getLongestStr = _array => {
1178
+ if (_array === undefined) {
1179
+ return ``;
1180
+ }
1181
+ return _array.reduce((longest, current) => {
1182
+ return current.length > longest.length ? current : longest;
1183
+ }, ``);
1184
+ }
1185
+
1099
1186
  /**
1100
1187
  * 補足説明部分のラベル作成
1101
1188
  * @param {string} _id
@@ -1166,7 +1253,7 @@ const setUserSelect = (_style, _value = C_DIS_NONE) => {
1166
1253
  * @returns {HTMLDivElement}
1167
1254
  */
1168
1255
  const createDivCss2Label = (_id, _text, { x = 0, y = 0, w = g_limitObj.setLblWidth, h = g_limitObj.setLblHeight,
1169
- siz = g_limitObj.setLblSiz, align = C_ALIGN_CENTER, ...rest } = {}, ..._classes) => {
1256
+ siz = g_limitObj.setLblSiz, align = C_ALIGN_CENTER, type = `text`, ...rest } = {}, ..._classes) => {
1170
1257
  const div = createDiv(_id, x, y, w, h, [g_cssObj.title_base, ..._classes]);
1171
1258
 
1172
1259
  const style = div.style;
@@ -1174,7 +1261,7 @@ const createDivCss2Label = (_id, _text, { x = 0, y = 0, w = g_limitObj.setLblWid
1174
1261
  style.fontFamily = getBasicFont();
1175
1262
  style.textAlign = `${align}`;
1176
1263
  style.pointerEvents = C_DIS_NONE;
1177
- if (rest?.overflow === C_DIS_AUTO) {
1264
+ if (rest?.overflow === C_DIS_AUTO || type !== `text`) {
1178
1265
  style.pointerEvents = C_DIS_AUTO;
1179
1266
  }
1180
1267
  div.innerHTML = _text;
@@ -2351,44 +2438,43 @@ const loadLocalStorage = () => {
2351
2438
  };
2352
2439
 
2353
2440
  // ロケールの読込、警告メッセージの入替
2354
- const checkLocale = localStorage.getItem(`danoni-locale`);
2355
- if (checkLocale) {
2356
- g_langStorage = JSON.parse(checkLocale);
2441
+ g_langStorage = parseStorageData(`danoni-locale`);
2442
+ if (g_langStorage.locale !== undefined) {
2357
2443
  g_localeObj.val = g_langStorage.locale;
2358
2444
  g_localeObj.num = g_localeObj.list.findIndex(val => val === g_localeObj.val);
2359
2445
  }
2446
+ if (g_langStorage.safeMode === undefined) {
2447
+ g_langStorage.safeMode = C_FLG_OFF;
2448
+ }
2360
2449
  Object.assign(g_msgInfoObj, g_lang_msgInfoObj[g_localeObj.val]);
2361
2450
  Object.assign(g_kCd, g_lang_kCd[g_localeObj.val]);
2362
2451
 
2363
2452
  // 作品別ローカルストレージの読込
2364
- const checkStorage = localStorage.getItem(g_localStorageUrl);
2365
- if (checkStorage) {
2366
- g_localStorage = JSON.parse(checkStorage);
2367
-
2368
- // Adjustment, Volume, Appearance, Opacity, HitPosition初期値設定
2369
- checkLocalParam(`adjustment`, C_TYP_FLOAT, g_settings.adjustmentNum);
2370
- checkLocalParam(`volume`, C_TYP_NUMBER, g_settings.volumes.length - 1);
2371
- checkLocalParam(`appearance`);
2372
- checkLocalParam(`opacity`, C_TYP_NUMBER, g_settings.opacitys.length - 1);
2373
- checkLocalParam(`hitPosition`, C_TYP_FLOAT, g_settings.hitPositionNum);
2374
-
2375
- // ハイスコア取得準備
2376
- if (g_localStorage.highscores === undefined) {
2377
- g_localStorage.highscores = {};
2378
- }
2453
+ if (g_langStorage.safeMode === C_FLG_OFF) {
2454
+ g_localStorage = parseStorageData(g_localStorageUrl, {
2455
+ adjustment: 0, hitPosition: 0, volume: 100, highscores: {},
2456
+ });
2457
+ } else {
2458
+ g_localStorage = {};
2459
+ g_stateObj.dataSaveFlg = false;
2460
+ makeWarningWindow(g_msgInfoObj.W_0031);
2461
+ }
2379
2462
 
2380
- // 廃棄済みリストからデータを消去
2381
- g_storeSettingsEx.filter(val => g_localStorage[val] !== undefined)
2382
- .forEach(val => delete g_localStorage[val]);
2463
+ // Adjustment, Volume, Appearance, Opacity, HitPosition初期値設定
2464
+ checkLocalParam(`adjustment`, C_TYP_FLOAT, g_settings.adjustmentNum);
2465
+ checkLocalParam(`volume`, C_TYP_NUMBER, g_settings.volumes.length - 1);
2466
+ checkLocalParam(`appearance`);
2467
+ checkLocalParam(`opacity`, C_TYP_NUMBER, g_settings.opacitys.length - 1);
2468
+ checkLocalParam(`hitPosition`, C_TYP_FLOAT, g_settings.hitPositionNum);
2383
2469
 
2384
- } else {
2385
- g_localStorage = {
2386
- adjustment: 0,
2387
- hitPosition: 0,
2388
- volume: 100,
2389
- highscores: {},
2390
- };
2470
+ // ハイスコア取得準備
2471
+ if (g_localStorage.highscores === undefined) {
2472
+ g_localStorage.highscores = {};
2391
2473
  }
2474
+
2475
+ // 廃棄済みリストからデータを消去
2476
+ g_storeSettingsEx.filter(val => g_localStorage[val] !== undefined)
2477
+ .forEach(val => delete g_localStorage[val]);
2392
2478
  };
2393
2479
 
2394
2480
  /**
@@ -4477,8 +4563,6 @@ const titleInit = () => {
4477
4563
  }
4478
4564
  const releaseDate = (g_headerObj.releaseDate !== `` ? ` @${g_headerObj.releaseDate}` : ``);
4479
4565
  const versionName = `&copy; 2018-${g_revisedDate.slice(0, 4)} ティックル, CW ${g_version}${customVersion}${releaseDate}`;
4480
-
4481
- let reloadFlg = false;
4482
4566
  const getLinkSiz = _name => getFontSize(_name, g_sWidth / 2 - 20, getBasicFont(), g_limitObj.lnkSiz, 12);
4483
4567
 
4484
4568
  /**
@@ -4502,23 +4586,9 @@ const titleInit = () => {
4502
4586
 
4503
4587
  // Reset
4504
4588
  createCss2Button(`btnReset`, g_lblNameObj.dataReset, () => {
4505
- reloadFlg = false;
4506
- if (window.confirm(g_msgObj.dataResetConfirm)) {
4507
- g_localStorage = {
4508
- adjustment: 0,
4509
- volume: 100,
4510
- highscores: {},
4511
- };
4512
- localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorage));
4513
- reloadFlg = true;
4514
- }
4515
- }, Object.assign(g_lblPosObj.btnReset, {
4516
- resetFunc: () => {
4517
- if (reloadFlg) {
4518
- location.reload();
4519
- }
4520
- },
4521
- }), g_cssObj.button_Reset),
4589
+ clearTimeout(g_timeoutEvtTitleId);
4590
+ dataMgtInit();
4591
+ }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
4522
4592
 
4523
4593
  // ロケール切替
4524
4594
  createCss2Button(`btnReload`, g_localeObj.val, () => true,
@@ -4700,6 +4770,201 @@ const setWindowStyle = (_text, _bkColor, _textColor, _align = C_ALIGN_LEFT, { _x
4700
4770
  return lbl;
4701
4771
  };
4702
4772
 
4773
+ /*-----------------------------------------------------------*/
4774
+ /* Scene : DATA MANAGEMENT [pear] */
4775
+ /*-----------------------------------------------------------*/
4776
+
4777
+ const dataMgtInit = () => {
4778
+ clearWindow(true);
4779
+ const prevPage = g_currentPage;
4780
+ g_currentPage = `dataMgt`;
4781
+
4782
+ multiAppend(divRoot,
4783
+
4784
+ // 画面タイトル
4785
+ getTitleDivLabel(`lblTitle`,
4786
+ `<div class="settings_Title">DATA</div><div class="settings_Title2">MANAGEMENT</div>`
4787
+ .replace(/[\t\n]/g, ``), 0, 15, g_cssObj.flex_centering),
4788
+
4789
+ createDescDiv(`dataDelMsg`, g_lblNameObj[`dataDelete${g_langStorage.safeMode}Desc`]),
4790
+ );
4791
+
4792
+ // 各ボタン用のスプライトを作成
4793
+ const optionsprite = createEmptySprite(divRoot, `optionsprite`, g_windowObj.optionSprite);
4794
+
4795
+ let reloadFlg = false;
4796
+ const list = [C_FLG_OFF, C_FLG_ON];
4797
+ const cssBarList = [C_FLG_OFF, C_FLG_ON];
4798
+ const cssBgList = [g_settings.d_cssBgName, g_settings.d_cssBgName];
4799
+
4800
+ /**
4801
+ * データ管理用ラベルの作成
4802
+ * @param {string} _name
4803
+ * @param {number} _heightPos
4804
+ * @param {number} [x=0]
4805
+ * @returns {HTMLDivElement}
4806
+ */
4807
+ const createMgtLabel = (_name, _heightPos, { x = 0 } = {}) =>
4808
+ createDivCss2Label(`lbl${toCapitalize(_name)}`, getStgDetailName(toCapitalize(_name)), {
4809
+ x, y: g_limitObj.setLblHeight * _heightPos + 40,
4810
+ siz: g_limitObj.setLblSiz, align: C_ALIGN_LEFT,
4811
+ });
4812
+
4813
+ /**
4814
+ * データ管理用ボタンの作成
4815
+ * @param {string} _name
4816
+ * @param {number} _heightPos
4817
+ * @param {number} _widthPos
4818
+ * @param {number} [w=125]
4819
+ * @param {function} func
4820
+ * @returns {HTMLDivElement}
4821
+ */
4822
+ const createMgtButton = (_name, _heightPos, _widthPos, { w = 125, func = () => true, ...rest } = {}) => {
4823
+ const linkId = `btn${toCapitalize(_name)}`;
4824
+ return createCss2Button(linkId, getStgDetailName(toCapitalize(_name)), () => {
4825
+ const prevDisp = g_settings.dataMgtNum[_name];
4826
+ const [prevBarColor, prevBgColor] = [cssBarList[prevDisp], cssBgList[prevDisp]];
4827
+
4828
+ g_settings.dataMgtNum[_name] = (g_settings.dataMgtNum[_name] + 1) % 2;
4829
+ g_stateObj[`dm_${_name}`] = list[g_settings.dataMgtNum[_name]];
4830
+
4831
+ const nextDisp = g_settings.dataMgtNum[_name];
4832
+ const [nextBarColor, nextBgColor] = [cssBarList[nextDisp], cssBgList[nextDisp]];
4833
+ document.getElementById(linkId).classList.replace(g_cssObj[`button_${prevBarColor}`], g_cssObj[`button_${nextBarColor}`]);
4834
+ document.getElementById(linkId).classList.replace(g_cssObj[`button_${prevBgColor}`], g_cssObj[`button_${nextBgColor}`]);
4835
+ func();
4836
+ }, {
4837
+ x: _widthPos * (w + 5) + 20, y: g_limitObj.setLblHeight * _heightPos + 40,
4838
+ w, h: 20, siz: g_limitObj.setLblSiz, borderStyle: `solid`, title: g_msgObj[_name], ...rest
4839
+ }, g_cssObj[`button_${cssBgList[g_settings.dataMgtNum[_name]]}`], g_cssObj[`button_${cssBarList[g_settings.dataMgtNum[_name]]}`]);
4840
+ };
4841
+
4842
+ multiAppend(optionsprite,
4843
+ createMgtLabel(`workData`, 0),
4844
+ createMgtButton(`environment`, 1.5, 0),
4845
+ createMgtButton(`highscores`, 2.5, 0),
4846
+ createMgtButton(`customKey`, 3.5, 0),
4847
+ createMgtButton(`others`, 4.5, 0),
4848
+ createMgtLabel(`keyData`, 6),
4849
+ createDivCss2Label(`lblTargetKey`, `(${getKeyName(g_headerObj.keyLabels[0])})`, {
4850
+ x: 90, y: g_limitObj.setLblHeight * 6 + 40,
4851
+ siz: g_limitObj.setLblSiz, align: C_ALIGN_LEFT,
4852
+ })
4853
+ );
4854
+
4855
+ g_localStorageMgt = parseStorageData(g_localStorageUrl);
4856
+ multiAppend(divRoot,
4857
+ createDivCss2Label(`lblWorkDataView`,
4858
+ viewKeyStorage(`workStorage`), g_lblPosObj.lblWorkDataView),
4859
+ createDivCss2Label(`lblKeyDataView`, viewKeyStorage(`keyStorage`, g_headerObj.keyLabels[0]), g_lblPosObj.lblKeyDataView),
4860
+ );
4861
+ setUserSelect($id(`lblWorkDataView`), `text`);
4862
+ setUserSelect($id(`lblKeyDataView`), `text`);
4863
+
4864
+ const keyList = makeDedupliArray(g_headerObj.keyLabels).sort((a, b) => parseInt(a) - parseInt(b));
4865
+ const keyListSprite = createEmptySprite(optionsprite, `keyListSprite`, g_windowObj.keyListSprite);
4866
+ keyList.forEach((key, j) => {
4867
+ g_stateObj[`dm_${key}`] = C_FLG_OFF;
4868
+ g_settings.dataMgtNum[key] = 0;
4869
+ keyListSprite.appendChild(createMgtButton(key, j - 2, 0, {
4870
+ w: Math.max(50, getStrWidth(getKeyName(key) + ` `, g_limitObj.setLblSiz, getBasicFont())),
4871
+ func: () => {
4872
+ lblKeyDataView.innerHTML = viewKeyStorage(`keyStorage`, key);
4873
+ lblTargetKey.innerHTML = `(${getKeyName(key)})`;
4874
+ },
4875
+ }));
4876
+ document.getElementById(`btn${key}`).innerHTML = getKeyName(key);
4877
+ });
4878
+
4879
+ // ユーザカスタムイベント(初期)
4880
+ g_customJsObj.dataMgt.forEach(func => func());
4881
+
4882
+ multiAppend(divRoot,
4883
+ createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true,
4884
+ Object.assign(g_lblPosObj.btnResetBack, {
4885
+ resetFunc: () => prevPage === `title` ? titleInit() : g_moveSettingWindow(false),
4886
+ }), g_cssObj.button_Back),
4887
+
4888
+ createCss2Button(`btnSafeMode`, g_lblNameObj.b_safeMode +
4889
+ (g_langStorage.safeMode === C_FLG_ON ? C_FLG_OFF : C_FLG_ON), () => {
4890
+ if (window.confirm(g_msgObj[`safeMode${g_langStorage.safeMode}Confirm`])) {
4891
+ g_langStorage.safeMode = g_langStorage.safeMode === C_FLG_ON ? C_FLG_OFF : C_FLG_ON;
4892
+ localStorage.setItem(`danoni-locale`, JSON.stringify(g_langStorage));
4893
+ location.reload();
4894
+ }
4895
+ }, g_lblPosObj.btnSafeMode, g_cssObj.button_Setting),
4896
+
4897
+ createCss2Button(`btnReset`, g_lblNameObj.b_cReset, () => {
4898
+ reloadFlg = false;
4899
+ const backupData = new Map();
4900
+
4901
+ const selectedData = Object.keys(g_stateObj)
4902
+ .filter(key => key.startsWith('dm_') && g_stateObj[key] === C_FLG_ON)
4903
+ .map(key => key.slice(`dm_`.length));
4904
+
4905
+ if (window.confirm(g_msgObj.dataResetConfirm +
4906
+ `\n\n${selectedData.map(val => `- ${g_msgObj[val] || g_msgObj.keyTypes.split('{0}').join(val)}`).join(`\n`)}`)) {
4907
+ selectedData.forEach(key => {
4908
+ if (g_resetFunc.has(key)) {
4909
+ backupData.set(key, JSON.parse(JSON.stringify(g_localStorageMgt)));
4910
+ g_resetFunc.get(key)();
4911
+ localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorageMgt));
4912
+
4913
+ } else if (keyList.includes(key)) {
4914
+ const storage = parseStorageData(`danonicw-${key}k`);
4915
+
4916
+ if (Object.keys(storage).length > 0) {
4917
+ backupData.set(key, JSON.parse(JSON.stringify(storage)));
4918
+ g_settings.keyStorages.forEach(val => delete storage[val]);
4919
+ localStorage.setItem(`danonicw-${key}k`, JSON.stringify(storage));
4920
+ } else {
4921
+ backupData.set(`XX` + key, JSON.parse(JSON.stringify(g_localStorageMgt)));
4922
+ g_settings.keyStorages.forEach(val => delete g_localStorageMgt[`${val}${key}`]);
4923
+ localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorageMgt));
4924
+ }
4925
+ }
4926
+ });
4927
+ reloadFlg = true;
4928
+ sessionStorage.setItem('resetBackup', JSON.stringify(Array.from(backupData.entries())));
4929
+ }
4930
+ }, Object.assign(g_lblPosObj.btnResetN, {
4931
+ visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
4932
+ resetFunc: () => {
4933
+ if (reloadFlg) {
4934
+ location.reload();
4935
+ }
4936
+ },
4937
+ }), g_cssObj.button_Reset),
4938
+
4939
+ // リカバリー用のボタン
4940
+ createCss2Button(`btnUndo`, g_lblNameObj.b_undo, () => {
4941
+ const backup = JSON.parse(sessionStorage.getItem('resetBackup'));
4942
+ if (backup && window.confirm(g_msgObj.dataRestoreConfirm)) {
4943
+ backup.forEach(([key, data]) => {
4944
+ if (g_resetFunc.has(key) || keyList.includes(key.slice(`XX`.length))) {
4945
+ Object.assign(g_localStorageMgt, data);
4946
+ localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorageMgt));
4947
+ } else if (keyList.includes(key)) {
4948
+ localStorage.setItem(`danonicw-${key}k`, JSON.stringify(data));
4949
+ }
4950
+ });
4951
+ sessionStorage.removeItem('resetBackup');
4952
+ location.reload();
4953
+ }
4954
+ }, g_lblPosObj.btnUndo, g_cssObj.button_Tweet)
4955
+ );
4956
+ if (sessionStorage.getItem('resetBackup') === null) {
4957
+ btnUndo.style.display = C_DIS_NONE;
4958
+ }
4959
+
4960
+ // キー操作イベント(デフォルト)
4961
+ setShortcutEvent(g_currentPage, () => true, { dfEvtFlg: true });
4962
+
4963
+ document.oncontextmenu = () => true;
4964
+ divRoot.oncontextmenu = () => false;
4965
+
4966
+ g_skinJsObj.dataMgt.forEach(func => func());
4967
+ };
4703
4968
 
4704
4969
  /*-----------------------------------------------------------*/
4705
4970
  /* Scene : SETTINGS [lime] */
@@ -4743,7 +5008,13 @@ const commonSettingBtn = _labelName => {
4743
5008
  createCss2Button(`btnSave`, g_lblNameObj.dataSave, evt => switchSave(evt),
4744
5009
  Object.assign(g_lblPosObj.btnSave, {
4745
5010
  cxtFunc: evt => switchSave(evt),
5011
+ visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
4746
5012
  }), g_cssObj.button_Default, (g_stateObj.dataSaveFlg ? g_cssObj.button_ON : g_cssObj.button_OFF)),
5013
+
5014
+ // データ管理画面へ移動
5015
+ createCss2Button(`btnReset`, g_lblNameObj.dataReset, () => {
5016
+ dataMgtInit();
5017
+ }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
4747
5018
  );
4748
5019
  };
4749
5020
 
@@ -4804,26 +5075,15 @@ const setSpriteList = _settingList => {
4804
5075
  };
4805
5076
 
4806
5077
  /**
4807
- * スライダー共通処理 (Fadein)
4808
- * @param {HTMLInputElement} _slider
4809
- * @param {HTMLDivElement} _link
4810
- * @returns {string}
4811
- */
4812
- const inputSlider = (_slider, _link) => {
4813
- const value = parseInt(_slider.value);
4814
- _link.textContent = `${value}${g_lblNameObj.percent}`;
4815
- return value;
4816
- };
4817
-
4818
- /**
4819
- * スライダー共通処理 (Appearance)
5078
+ * スライダー共通処理 (Fadein, Appearance)
4820
5079
  * @param {HTMLInputElement} _slider
4821
5080
  * @param {HTMLDivElement} _link
5081
+ * @param {string} _type
4822
5082
  * @returns {string}
4823
5083
  */
4824
- const inputSliderAppearance = (_slider, _link) => {
5084
+ const inputSlider = (_slider, _link, _type) => {
4825
5085
  const value = parseInt(_slider.value);
4826
- _link.textContent = `${g_hidSudObj.distH[g_stateObj.appearance](value)}`;
5086
+ _link.textContent = g_sliderView.get(_type)(value);
4827
5087
  return value;
4828
5088
  };
4829
5089
 
@@ -5489,18 +5749,17 @@ const setDifficulty = (_initFlg) => {
5489
5749
  g_keyObj.currentPtn = 0;
5490
5750
  g_keycons.keySwitchNum = 0;
5491
5751
  }
5492
- const hasKeyStorage = localStorage.getItem(`danonicw-${g_keyObj.currentKey}k`);
5493
5752
  let storageObj, addKey = ``;
5494
5753
 
5495
5754
  if (!g_stateObj.extraKeyFlg) {
5496
5755
 
5497
5756
  // キー別のローカルストレージの初期設定 ※特殊キーは除く
5498
- g_localKeyStorage = hasKeyStorage ? JSON.parse(hasKeyStorage) : {
5757
+ g_localKeyStorage = parseStorageData(`danonicw-${g_keyObj.currentKey}k`, {
5499
5758
  reverse: C_FLG_OFF,
5500
5759
  keyCtrl: [[]],
5501
5760
  keyCtrlPtn: 0,
5502
5761
  setColor: [],
5503
- };
5762
+ });
5504
5763
  storageObj = g_localKeyStorage;
5505
5764
 
5506
5765
  } else {
@@ -5929,7 +6188,7 @@ const createOptionWindow = _sprite => {
5929
6188
 
5930
6189
  const fadeinSlider = document.getElementById(`fadeinSlider`);
5931
6190
  fadeinSlider.addEventListener(`input`, () =>
5932
- g_stateObj.fadein = inputSlider(fadeinSlider, lnkFadein), false);
6191
+ g_stateObj.fadein = inputSlider(fadeinSlider, lnkFadein, `fadein`), false);
5933
6192
 
5934
6193
  // ---------------------------------------------------
5935
6194
  // ボリューム (Volume)
@@ -6636,12 +6895,12 @@ const createSettingsDisplayWindow = _sprite => {
6636
6895
 
6637
6896
  const appearanceSlider = document.getElementById(`appearanceSlider`);
6638
6897
  appearanceSlider.addEventListener(`input`, () =>
6639
- g_hidSudObj.filterPos = inputSliderAppearance(appearanceSlider, lblAppearancePos), false);
6898
+ g_hidSudObj.filterPos = inputSlider(appearanceSlider, lblAppearancePos, `appearance`), false);
6640
6899
 
6641
6900
  const dispAppearanceSlider = () => {
6642
6901
  [`lblAppearanceBar`, `lnkLockBtn`, `lnkfilterLine`].forEach(obj =>
6643
6902
  $id(obj).visibility = g_appearanceRanges.includes(g_stateObj.appearance) ? `Visible` : `Hidden`);
6644
- inputSliderAppearance(appearanceSlider, lblAppearancePos);
6903
+ inputSlider(appearanceSlider, lblAppearancePos, `appearance`);
6645
6904
  };
6646
6905
  dispAppearanceSlider();
6647
6906
 
@@ -9847,7 +10106,9 @@ const getArrowSettings = () => {
9847
10106
 
9848
10107
  if (g_stateObj.dataSaveFlg) {
9849
10108
  // ローカルストレージへAdjustment, HitPosition, Volume設定を保存
10109
+ // 変更が確定した時点で表示用のキャッシュを解放
9850
10110
  g_storeSettings.forEach(setting => g_localStorage[setting] = g_stateObj[setting]);
10111
+ viewKeyStorage.cache = new Map();
9851
10112
  localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorage));
9852
10113
  }
9853
10114
 
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2025/02/11 (v39.4.1)
8
+ * Revised : 2025/02/16 (v39.6.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -93,6 +93,7 @@ const g_limitObj = {
93
93
 
94
94
  /** 設定項目の位置 */
95
95
  const g_settingPos = {
96
+ dataMgt: {},
96
97
  option: {
97
98
  difficulty: { heightPos: 0, y: -5, dw: 0, dh: 10 },
98
99
  speed: { heightPos: 2, y: 0, dw: 0, dh: 0 },
@@ -167,6 +168,7 @@ const g_windowObj = {
167
168
  divBack: { background: `linear-gradient(#000000, #222222)` },
168
169
 
169
170
  colorPickSprite: { x: 0, y: 90, w: 50, h: 280 },
171
+ keyListSprite: { x: 0, y: g_limitObj.setLblHeight * 7.5 + 40, w: 150, h: 120, overflow: C_DIS_AUTO },
170
172
  };
171
173
 
172
174
  const g_lblPosObj = {};
@@ -239,6 +241,33 @@ const updateWindowSiz = () => {
239
241
  x: g_btnX(1) - 160, y: (g_sHeight / 2) + 150, w: 140, h: 50, siz: 20, border: `solid 1px #999999`,
240
242
  },
241
243
 
244
+ /** データ管理 */
245
+ btnResetBack: {
246
+ x: g_btnX(), y: g_sHeight - 20, w: g_btnWidth(1 / 4), h: 16, siz: 12,
247
+ },
248
+ btnSafeMode: {
249
+ x: g_btnX(), siz: 18,
250
+ },
251
+ dataDelMsg: {
252
+ x: 0, y: 65, w: g_sWidth, h: 20, siz: g_limitObj.mainSiz,
253
+ },
254
+ btnResetN: {
255
+ x: g_btnX(1 / 3), y: g_sHeight - 100, w: g_btnWidth(1 / 3), h: g_limitObj.btnHeight,
256
+ },
257
+ btnUndo: {
258
+ x: g_btnX(2 / 3), y: g_sHeight - 100, w: g_btnWidth(1 / 3), h: g_limitObj.btnHeight,
259
+ },
260
+ lblWorkDataView: {
261
+ x: g_btnX(5 / 12), y: 100, w: g_btnWidth(1 / 2), h: g_sHeight / 4, siz: 12, align: C_ALIGN_LEFT,
262
+ overflow: C_DIS_AUTO, background: `#222222`, color: `#cccccc`,
263
+ whiteSpace: `nowrap`,
264
+ },
265
+ lblKeyDataView: {
266
+ x: g_btnX(5 / 12), y: 100 + g_sHeight / 4 + 10, w: g_btnWidth(1 / 2), h: g_sHeight / 3 - 10, siz: 12, align: C_ALIGN_LEFT,
267
+ overflow: C_DIS_AUTO, background: `#222222`, color: `#cccccc`,
268
+ whiteSpace: `nowrap`,
269
+ },
270
+
242
271
  /** 設定画面 */
243
272
  btnBack: {
244
273
  x: g_btnX(),
@@ -274,7 +303,7 @@ const updateWindowSiz = () => {
274
303
  x: g_limitObj.setLblLeft, y: 0,
275
304
  },
276
305
  lblFadeinBar: {
277
- x: g_limitObj.setLblLeft, y: 0,
306
+ x: g_limitObj.setLblLeft, y: 0, type: `range`,
278
307
  },
279
308
 
280
309
  /** 設定: 譜面明細子画面 */
@@ -327,7 +356,7 @@ const updateWindowSiz = () => {
327
356
  x: g_limitObj.setLblLeft, y: 20, siz: 12, align: C_ALIGN_CENTER,
328
357
  },
329
358
  lblAppearanceBar: {
330
- x: g_limitObj.setLblLeft, y: 15,
359
+ x: g_limitObj.setLblLeft, y: 15, type: `range`,
331
360
  },
332
361
  lnkLockBtn: {
333
362
  x: g_limitObj.setLblLeft + g_limitObj.setLblWidth - 40, y: 0, w: 40, h: g_limitObj.setLblHeight, siz: 12,
@@ -940,6 +969,11 @@ const g_stateObj = {
940
969
  rotateEnabled: true,
941
970
  flatStepHeight: C_ARW_WIDTH,
942
971
 
972
+ dm_environment: C_FLG_OFF,
973
+ dm_highscores: C_FLG_OFF,
974
+ dm_customKey: C_FLG_OFF,
975
+ dm_others: C_FLG_OFF,
976
+
943
977
  layerNum: 2,
944
978
  };
945
979
 
@@ -989,6 +1023,16 @@ const makeSpeedList = (_minSpd, _maxSpd) => [...Array((_maxSpd - _minSpd) * 20 +
989
1023
 
990
1024
  // 設定系全般管理
991
1025
  const g_settings = {
1026
+
1027
+ dataMgtNum: {
1028
+ environment: 0,
1029
+ highscores: 0,
1030
+ customKey: 0,
1031
+ others: 0,
1032
+ },
1033
+ environments: [`adjustment`, `volume`, `colorType`, `appearance`, `opacity`, `hitPosition`],
1034
+ keyStorages: [`reverse`, `keyCtrl`, `keyCtrlPtn`, `shuffle`, `color`, `stepRtn`],
1035
+
992
1036
  speeds: makeSpeedList(C_MIN_SPEED, C_MAX_SPEED),
993
1037
  speedNum: 0,
994
1038
  speedTerms: [20, 5, 1],
@@ -1247,6 +1291,55 @@ const resetXY = () => {
1247
1291
  Object.keys(g_posYs).forEach(_id => delete g_posYs[_id]);
1248
1292
  };
1249
1293
 
1294
+ /**
1295
+ * データ消去用管理関数
1296
+ */
1297
+ const g_resetFunc = new Map([
1298
+ ['highscores', () => {
1299
+ delete g_localStorageMgt.highscores;
1300
+ g_localStorageMgt.highscores = {};
1301
+ }],
1302
+ ['environment', () => g_settings.environments.forEach(key => delete g_localStorageMgt[key])],
1303
+ [`customKey`, () => Object.keys(g_localStorageMgt)
1304
+ .filter(key => listMatching(key, g_settings.keyStorages.concat(`setColor`), { prefix: `^` }))
1305
+ .forEach(key => delete g_localStorageMgt[key])],
1306
+ [`others`, () => Object.keys(g_localStorageMgt)
1307
+ .filter(key => !g_settings.environments.includes(key) && key !== `highscores` &&
1308
+ !listMatching(key, g_settings.keyStorages.concat(`setColor`), { prefix: `^` }))
1309
+ .forEach(key => delete g_localStorageMgt[key])],
1310
+ ]);
1311
+
1312
+ /**
1313
+ * ストレージ管理関数
1314
+ */
1315
+ const g_storageFunc = new Map([
1316
+
1317
+ // 作品別のストレージ情報
1318
+ ['workStorage', (_key) => {
1319
+ const settingStorage = {};
1320
+
1321
+ // カスタムキー定義のストレージデータを表示から除去
1322
+ Object.keys(g_localStorageMgt).filter(val => !listMatching(val, g_settings.keyStorages.concat(`setColor`), { prefix: `^` }))
1323
+ .forEach(val => settingStorage[val] = g_localStorageMgt[val]);
1324
+ return settingStorage;
1325
+ }],
1326
+
1327
+ // キー別のストレージ情報
1328
+ ['keyStorage', (_key) => {
1329
+ let keyStorage = parseStorageData(`danonicw-${_key}k`);
1330
+ if (Object.keys(keyStorage).length === 0) {
1331
+
1332
+ // キー別の情報が見つからない場合は作品別の情報から検索
1333
+ Object.keys(g_localStorageMgt).filter(val => val.endsWith(_key))
1334
+ .forEach(val => keyStorage[val] = g_localStorageMgt[val]);
1335
+ if (Object.keys(keyStorage).length === 0) {
1336
+ return ``;
1337
+ }
1338
+ }
1339
+ return keyStorage;
1340
+ }],
1341
+ ]);
1342
+
1250
1343
  /**
1251
1344
  * シャッフル適用関数
1252
1345
  * @param {number} keyNum
@@ -1450,6 +1543,11 @@ const g_effectFunc = new Map([
1450
1543
  ['Squids', () => g_setEffect(`effects-squids-arrow`, `effects-squids-frz`)],
1451
1544
  ]);
1452
1545
 
1546
+ const g_sliderView = new Map([
1547
+ ['fadein', _val => `${_val}${g_lblNameObj.percent}`],
1548
+ ['appearance', _val => `${g_hidSudObj.distH[g_stateObj.appearance](_val)}`],
1549
+ ]);
1550
+
1453
1551
  const g_keycons = {
1454
1552
  configTypes: [`Main`, `Replaced`, `ALL`],
1455
1553
  configTypeNum: 0,
@@ -1875,6 +1973,16 @@ const g_shortcutObj = {
1875
1973
  F1: { id: `btnHelp`, reset: true },
1876
1974
  ControlLeft_KeyC: { id: `` },
1877
1975
  KeyC: { id: `btnComment` },
1976
+ KeyD: { id: `btnReset` },
1977
+ },
1978
+ dataMgt: {
1979
+ KeyE: { id: `btnEnvironment` },
1980
+ KeyH: { id: `btnHighscores` },
1981
+ KeyK: { id: `btnCustomKey` },
1982
+ KeyO: { id: `btnOthers` },
1983
+ Escape: { id: `btnBack` },
1984
+ ShiftLeft_Tab: { id: `btnBack` },
1985
+ ShiftRight_Tab: { id: `btnBack` },
1878
1986
  },
1879
1987
  option: {
1880
1988
  ShiftLeft_KeyD: { id: `lnkDifficultyL` },
@@ -2142,6 +2250,8 @@ const g_shortcutObj = {
2142
2250
  Escape: { id: `btnBack` },
2143
2251
  Space: { id: `btnKeyConfig` },
2144
2252
  Enter: { id: `btnPlay` },
2253
+ ShiftLeft_Tab: { id: `btnBack` },
2254
+ ShiftRight_Tab: { id: `btnBack` },
2145
2255
  Tab: { id: `btnexSetting` },
2146
2256
  },
2147
2257
  keyConfig: {
@@ -2173,6 +2283,7 @@ const g_shortcutObj = {
2173
2283
  const g_btnWaitFrame = {
2174
2284
  initial: { b_frame: 0, s_frame: 0 },
2175
2285
  title: { b_frame: 0, s_frame: 0 },
2286
+ dataMgt: { b_frame: 0, s_frame: 0 },
2176
2287
  option: { b_frame: 0, s_frame: 0, initial: true },
2177
2288
  difSelector: { b_frame: 0, s_frame: 0 },
2178
2289
  settingsDisplay: { b_frame: 0, s_frame: 0 },
@@ -2187,6 +2298,7 @@ const g_btnWaitFrame = {
2187
2298
  // 主要ボタンのリスト
2188
2299
  const g_btnPatterns = {
2189
2300
  title: { Start: 0, Comment: -10 },
2301
+ dataMgt: { Back: 0, Environment: -35, Highscores: -35, CustomKey: -35, Others: -35 },
2190
2302
  option: { Back: 0, KeyConfig: 0, Play: 0, Display: -5, Save: -10, Graph: -25 },
2191
2303
  difSelector: {},
2192
2304
  settingsDisplay: { Back: 0, KeyConfig: 0, Play: 0, Save: -10, Settings: -5 },
@@ -3236,6 +3348,8 @@ const g_lang_msgInfoObj = {
3236
3348
  また、Fadein を使用した場合は通常よりズレが発生することがあります。<br>
3237
3349
  音源ファイルを js/txt 化するか、サーバー上動作とすれば解消します。(W-0012)`,
3238
3350
  W_0021: `クリップボードのコピーに失敗しました。`,
3351
+ W_0031: `セーフモード適用中です。ローカルストレージ情報を使わない設定になっています。<br>
3352
+ 「Data Management」から解除が可能です。(W-0031)`,
3239
3353
 
3240
3354
  E_0011: `アーティスト名が未入力です。(E-0011)`,
3241
3355
  E_0012: `曲名情報が未設定です。(E-0012)<br>
@@ -3286,6 +3400,9 @@ const g_lang_msgInfoObj = {
3286
3400
  It can be solved by converting the sound source file to encoded data (js, txt) or
3287
3401
  operating it on the server. (W-0012)`,
3288
3402
  W_0021: `Failed to copy the clipboard.`,
3403
+ W_0031: `Safe Mode is being applied. <br>
3404
+ The setting is set to not use local storage information <br>
3405
+ and can be removed from Data Management. (W-0031)`,
3289
3406
 
3290
3407
  E_0011: `The artist name is not set. (E-0011)`,
3291
3408
  E_0012: `The song title information is not set. (E-0012)<br>
@@ -3354,7 +3471,7 @@ const g_lblNameObj = {
3354
3471
  maker: `Maker`,
3355
3472
  artist: `Artist`,
3356
3473
 
3357
- dataReset: `Data Reset`,
3474
+ dataReset: `Data Management`,
3358
3475
  dataSave: `Data Save`,
3359
3476
  clickHere: `Click Here!!`,
3360
3477
  comment: `Comment`,
@@ -3366,6 +3483,8 @@ const g_lblNameObj = {
3366
3483
  b_keyConfig: `KeyConfig`,
3367
3484
  b_play: `PLAY!`,
3368
3485
  b_reset: `Reset Key`,
3486
+ b_safeMode: `Safe Mode -> `,
3487
+ b_undo: `Restore`,
3369
3488
  b_settings: `To Settings`,
3370
3489
  b_copy: `CopyResult`,
3371
3490
  b_tweet: `Post X`,
@@ -3607,6 +3726,9 @@ const g_linkObj = {
3607
3726
  */
3608
3727
  const g_lang_lblNameObj = {
3609
3728
  Ja: {
3729
+ dataDeleteOFFDesc: `消去したいデータの種類を選んで「Reset」を押してください`,
3730
+ dataDeleteONDesc: `セーフモード適用中はデータ消去は行えません。変更するにはセーフモードを解除してください`,
3731
+
3610
3732
  kcDesc: `[{0}:スキップ / {1}:(代替キーのみ)キー無効化]`,
3611
3733
  kcShuffleDesc: `番号をクリックでシャッフルグループ、矢印をクリックでカラーグループを変更`,
3612
3734
  kcNoShuffleDesc: `矢印をクリックでカラーグループを変更`,
@@ -3648,6 +3770,9 @@ const g_lang_lblNameObj = {
3648
3770
  securityUrl: `https://github.com/cwtickle/danoniplus/security/policy`,
3649
3771
  },
3650
3772
  En: {
3773
+ dataDeleteOFFDesc: `Select the type of data you wish to delete and press "Reset".`,
3774
+ dataDeleteONDesc: `Data erasure cannot be performed while safe mode is applied. <br>Please deactivate the safe mode to change the data.`,
3775
+
3651
3776
  kcDesc: `[{0}:Skip / {1}:Key invalidation (Alternate keys only)]`,
3652
3777
  kcShuffleDesc: `Click the number to change the shuffle group, and click the arrow to change the color.`,
3653
3778
  kcNoShuffleDesc: `Click the arrow to change the color group.`,
@@ -3708,7 +3833,16 @@ const g_lang_msgObj = {
3708
3833
  github: `Dancing☆Onigiri (CW Edition)のGitHubページへ移動します。`,
3709
3834
  security: `Dancing☆Onigiri (CW Edition)のサポート情報ページへ移動します。`,
3710
3835
 
3711
- dataResetConfirm: `この作品のローカル設定をクリアします。よろしいですか?\n(ハイスコアやAdjustment等のデータが全てクリアされます)`,
3836
+ environment: `${g_settings.environments.map(v => toCapitalize(v)).join(`, `)}の設定を初期化します。`,
3837
+ highscores: `全譜面のハイスコアを初期化します。\n個別に初期化したい場合はSettings画面より行ってください。`,
3838
+ customKey: `カスタムキーに関する全ての保存データを消去します。\n下記のKeyDataから個別に消去可能できないときに使用してください。`,
3839
+ others: `標準以外に関する保存データを消去します。`,
3840
+ keyTypes: `Key: {0} の保存データ(個別の色設定を除く)を消去します。`,
3841
+
3842
+ dataResetConfirm: `選択したローカル設定をクリアします。よろしいですか?`,
3843
+ dataRestoreConfirm: `ローカル設定を前回の状態に戻します(1回限り)。よろしいですか?\n消去した設定によっては今の設定が上書きされることがあります。`,
3844
+ safeModeONConfirm: `セーフモードを解除して、ローカルストレージ情報を利用します。\nよろしいですか?`,
3845
+ safeModeOFFConfirm: `セーフモードを設定して、ローカルストレージを使わずにリロードします。\nよろしいですか?`,
3712
3846
  keyResetConfirm: `キーを初期配置に戻します。よろしいですか?`,
3713
3847
  highscResetConfirm: `この譜面のハイスコアを消去します。よろしいですか?`,
3714
3848
  colorCopyConfirm: `フリーズアローの配色を矢印色に置き換えます\n(通常・ヒット時双方を置き換えます)。よろしいですか?`,
@@ -3790,7 +3924,16 @@ const g_lang_msgObj = {
3790
3924
  github: `Go to the GitHub page of Dancing Onigiri "CW Edition".`,
3791
3925
  security: `Go to the support information page for Dancing Onigiri "CW Edition".`,
3792
3926
 
3793
- dataResetConfirm: `Delete the local settings in this game. Is it OK?\n(High score, adjustment, volume and some settings will be initialized)`,
3927
+ environment: `Initialize ${g_settings.environments.map(v => toCapitalize(v)).join(`, `)} settings.`,
3928
+ highscores: `Initializes the high score of all charts. \nIf you want to initialize each chart individually, \nplease do so from the Highscore view in the Settings screen.`,
3929
+ customKey: `Delete stored data related to all custom keymodes. \nUse this option when you cannot delete individual KeyData from the following KeyData.`,
3930
+ others: `Delete non-standard stored data.`,
3931
+ keyTypes: `Deletes the stored data (except color settings) for Key: {0}.`,
3932
+
3933
+ dataResetConfirm: `Delete the selected local settings. Is it OK?`,
3934
+ dataRestoreConfirm: `Restore local settings to previous state (one time only). Is it OK?\nSome deleted settings may overwrite the current settings.`,
3935
+ safeModeONConfirm: `Exit safe mode and use local storage information. Is it OK?`,
3936
+ safeModeOFFConfirm: `Set safe mode and reload without local storage. Is it OK?`,
3794
3937
  keyResetConfirm: `Resets the assigned key to the initial state. Is it OK?`,
3795
3938
  highscResetConfirm: `Erases the high score for this chart. Is it OK?`,
3796
3939
  colorCopyConfirm: `Replace freeze arrow color scheme with arrow color\n(replace both normal and hit). Is this OK?`,
@@ -3873,8 +4016,10 @@ const g_lang_msgObj = {
3873
4016
  */
3874
4017
  const g_errMsgObj = {
3875
4018
  title: [],
4019
+ dataMgt: [],
3876
4020
  option: [],
3877
4021
  settingsDisplay: [],
4022
+ exSetting: [],
3878
4023
  loading: [],
3879
4024
  main: [],
3880
4025
  result: [],
@@ -3888,6 +4033,7 @@ const g_customJsObj = {
3888
4033
  preTitle: [],
3889
4034
  title: [],
3890
4035
  titleEnterFrame: [],
4036
+ dataMgt: [],
3891
4037
  option: [],
3892
4038
  difficulty: [],
3893
4039
  settingsDisplay: [],
@@ -3927,6 +4073,7 @@ const g_customJsObj = {
3927
4073
  */
3928
4074
  const g_skinJsObj = {
3929
4075
  title: [],
4076
+ dataMgt: [],
3930
4077
  option: [],
3931
4078
  settingsDisplay: [],
3932
4079
  exSetting: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "39.4.2",
3
+ "version": "39.6.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "index.js",
6
6
  "scripts": {