poi-plugin-inventory-cleaner 1.17.1 → 1.18.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/i18n/en-US.json +4 -1
- package/i18n/zh-CN.json +4 -1
- package/index.js +86 -12
- package/lib/inventory.js +1 -1
- package/lib/quest-status.js +103 -0
- package/lib/style.js +71 -3
- package/package.json +1 -1
package/i18n/en-US.json
CHANGED
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
"stock.consumeQuestEquip": "quest (equip)",
|
|
30
30
|
"stock.consumeQuestStore": "quest (store)",
|
|
31
31
|
"stock.consumeOther": "other",
|
|
32
|
-
"stock.consumeHidden": "{{count}} completed quest(s) hidden",
|
|
32
|
+
"stock.consumeHidden": "{{count}} completed quest(s) hidden (click to view/undo)",
|
|
33
|
+
"stock.markDone": "Mark as completed",
|
|
34
|
+
"stock.undoDone": "Undo completed mark",
|
|
35
|
+
"stock.undo": "Undo",
|
|
33
36
|
"stock.keepLabel": "Keep",
|
|
34
37
|
"stock.keepUnit": "",
|
|
35
38
|
"stock.keepPlaceholder": "guide",
|
package/i18n/zh-CN.json
CHANGED
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
"stock.consumeQuestEquip": "任务·装",
|
|
30
30
|
"stock.consumeQuestStore": "任务·库",
|
|
31
31
|
"stock.consumeOther": "其他",
|
|
32
|
-
"stock.consumeHidden": "已隐藏 {{count}}
|
|
32
|
+
"stock.consumeHidden": "已隐藏 {{count}} 个已完成任务(点击查看/撤销)",
|
|
33
|
+
"stock.markDone": "标记为已完成",
|
|
34
|
+
"stock.undoDone": "撤销完成标记",
|
|
35
|
+
"stock.undo": "撤销",
|
|
33
36
|
"stock.keepLabel": "保留",
|
|
34
37
|
"stock.keepUnit": "件",
|
|
35
38
|
"stock.keepPlaceholder": "攻略",
|
package/index.js
CHANGED
|
@@ -10,10 +10,16 @@ const {
|
|
|
10
10
|
stepKeep,
|
|
11
11
|
sortRows,
|
|
12
12
|
summarize,
|
|
13
|
+
questNoFromText,
|
|
13
14
|
OTHER_CATEGORY,
|
|
14
15
|
} = require('./lib/inventory')
|
|
15
16
|
const { buildQuestNeeds, resetCaches: resetQuestNeeds } = require('./lib/quest-needs')
|
|
16
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
markManual,
|
|
19
|
+
recordQuestStates,
|
|
20
|
+
resetCaches: resetQuestStatus,
|
|
21
|
+
unmarkManual,
|
|
22
|
+
} = require('./lib/quest-status')
|
|
17
23
|
const { CSS } = require('./lib/style')
|
|
18
24
|
|
|
19
25
|
const DICTIONARIES = {
|
|
@@ -274,7 +280,7 @@ function CategoryPicker({ value, groups, onChange }) {
|
|
|
274
280
|
)
|
|
275
281
|
}
|
|
276
282
|
|
|
277
|
-
function StockRow({ row, onKeepChange }) {
|
|
283
|
+
function StockRow({ row, onKeepChange, onMarkDone, onUndoDone }) {
|
|
278
284
|
const verdict = VERDICT[row.verdict] || VERDICT.unknown
|
|
279
285
|
return h(
|
|
280
286
|
'div',
|
|
@@ -382,20 +388,63 @@ function StockRow({ row, onKeepChange }) {
|
|
|
382
388
|
h(
|
|
383
389
|
'ol',
|
|
384
390
|
{ className: 'stock-consume-list' },
|
|
385
|
-
row.consumedItems
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
+
row.consumedItems
|
|
392
|
+
.filter((item) => !item.done)
|
|
393
|
+
.map((item, index) =>
|
|
394
|
+
h(
|
|
395
|
+
'li',
|
|
396
|
+
{ key: index, className: 'is-' + item.kind },
|
|
397
|
+
h('span', { className: 'stock-consume-tag' }, consumeLabel(item.tag)),
|
|
398
|
+
h('span', { className: 'stock-consume-text' }, item.text),
|
|
399
|
+
item.kind === 'quest'
|
|
400
|
+
? h(
|
|
401
|
+
'button',
|
|
402
|
+
{
|
|
403
|
+
type: 'button',
|
|
404
|
+
className: 'stock-consume-done',
|
|
405
|
+
title: t('stock.markDone'),
|
|
406
|
+
onClick: () =>
|
|
407
|
+
onMarkDone(questNoFromText(item.text), item.text),
|
|
408
|
+
},
|
|
409
|
+
'✓',
|
|
410
|
+
)
|
|
411
|
+
: null,
|
|
412
|
+
),
|
|
391
413
|
),
|
|
392
|
-
),
|
|
393
414
|
),
|
|
394
415
|
row.consumedHidden > 0
|
|
395
416
|
? h(
|
|
396
|
-
'
|
|
417
|
+
'details',
|
|
397
418
|
{ className: 'stock-consume-hidden' },
|
|
398
|
-
|
|
419
|
+
h(
|
|
420
|
+
'summary',
|
|
421
|
+
null,
|
|
422
|
+
t('stock.consumeHidden', { count: row.consumedHidden }),
|
|
423
|
+
),
|
|
424
|
+
h(
|
|
425
|
+
'ul',
|
|
426
|
+
{ className: 'stock-consume-done-list' },
|
|
427
|
+
row.consumedItems
|
|
428
|
+
.filter((item) => item.done)
|
|
429
|
+
.map((item, index) =>
|
|
430
|
+
h(
|
|
431
|
+
'li',
|
|
432
|
+
{ key: index, className: 'is-' + item.kind },
|
|
433
|
+
h('span', { className: 'stock-consume-tag' }, consumeLabel(item.tag)),
|
|
434
|
+
h('span', { className: 'stock-consume-text' }, item.text),
|
|
435
|
+
h(
|
|
436
|
+
'button',
|
|
437
|
+
{
|
|
438
|
+
type: 'button',
|
|
439
|
+
className: 'stock-consume-undo',
|
|
440
|
+
title: t('stock.undoDone'),
|
|
441
|
+
onClick: () => onUndoDone(questNoFromText(item.text)),
|
|
442
|
+
},
|
|
443
|
+
t('stock.undo'),
|
|
444
|
+
),
|
|
445
|
+
),
|
|
446
|
+
),
|
|
447
|
+
),
|
|
399
448
|
)
|
|
400
449
|
: null,
|
|
401
450
|
)
|
|
@@ -460,6 +509,11 @@ function App() {
|
|
|
460
509
|
const detail = event && event.detail
|
|
461
510
|
const path = detail && detail.path ? String(detail.path) : ''
|
|
462
511
|
if (!/questlist/.test(path)) return
|
|
512
|
+
try {
|
|
513
|
+
recordQuestStates(detail)
|
|
514
|
+
} catch (_error) {
|
|
515
|
+
// 记录失败不影响刷新
|
|
516
|
+
}
|
|
463
517
|
resetQuestNeeds()
|
|
464
518
|
resetQuestStatus()
|
|
465
519
|
setQuestTick((value) => value + 1)
|
|
@@ -479,6 +533,18 @@ function App() {
|
|
|
479
533
|
setQuestTick((value) => value + 1)
|
|
480
534
|
}, [])
|
|
481
535
|
|
|
536
|
+
const handleMarkDone = React.useCallback((no, title) => {
|
|
537
|
+
if (!no) return
|
|
538
|
+
markManual(no, title)
|
|
539
|
+
setQuestTick((value) => value + 1)
|
|
540
|
+
}, [])
|
|
541
|
+
|
|
542
|
+
const handleUndoDone = React.useCallback((no) => {
|
|
543
|
+
if (!no) return
|
|
544
|
+
unmarkManual(no)
|
|
545
|
+
setQuestTick((value) => value + 1)
|
|
546
|
+
}, [])
|
|
547
|
+
|
|
482
548
|
const handleScroll = React.useCallback((event) => {
|
|
483
549
|
setShowTop(event.currentTarget.scrollTop > 400)
|
|
484
550
|
}, [])
|
|
@@ -781,7 +847,15 @@ function App() {
|
|
|
781
847
|
: h(
|
|
782
848
|
'div',
|
|
783
849
|
{ className: 'stock-list' },
|
|
784
|
-
visible.map((row) =>
|
|
850
|
+
visible.map((row) =>
|
|
851
|
+
h(StockRow, {
|
|
852
|
+
key: row.id,
|
|
853
|
+
row,
|
|
854
|
+
onKeepChange: handleKeepChange,
|
|
855
|
+
onMarkDone: handleMarkDone,
|
|
856
|
+
onUndoDone: handleUndoDone,
|
|
857
|
+
}),
|
|
858
|
+
),
|
|
785
859
|
),
|
|
786
860
|
h('p', { className: 'stock-warn' }, t('stock.warn')),
|
|
787
861
|
h('p', { className: 'stock-source' }, t('stock.source')),
|
package/lib/inventory.js
CHANGED
|
@@ -168,7 +168,7 @@ function buildInventory(state, options = {}) {
|
|
|
168
168
|
usage: rule ? rule.usage : '',
|
|
169
169
|
obtain: rule ? rule.obtain : '',
|
|
170
170
|
consumed,
|
|
171
|
-
consumedItems: allConsumed
|
|
171
|
+
consumedItems: allConsumed,
|
|
172
172
|
consumedHidden: allConsumed.filter((item) => item.done).length,
|
|
173
173
|
stage: guide ? guide.stage : '',
|
|
174
174
|
note: guide ? guide.note : '',
|
package/lib/quest-status.js
CHANGED
|
@@ -57,6 +57,102 @@ function questPlannerStatePath() {
|
|
|
57
57
|
return path.join(root, 'state.json')
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function pluginDataDir() {
|
|
61
|
+
return path.join(appDataPath(), 'poi', 'plugin-data', 'poi-plugin-inventory-cleaner')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readJsonFile(file) {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''))
|
|
67
|
+
} catch (_error) {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeJsonFile(file, value) {
|
|
73
|
+
try {
|
|
74
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
75
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 1), 'utf8')
|
|
76
|
+
} catch (_error) {
|
|
77
|
+
// 写不进就只影响本次
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function manualPath() {
|
|
82
|
+
return path.join(pluginDataDir(), 'quest-manual.json')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function autoPath() {
|
|
86
|
+
return path.join(pluginDataDir(), 'quest-auto.json')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readMarked(file) {
|
|
90
|
+
const raw = readJsonFile(file)
|
|
91
|
+
const nos = new Set()
|
|
92
|
+
if (raw && typeof raw === 'object') {
|
|
93
|
+
for (const key of Object.keys(raw)) {
|
|
94
|
+
const no = Number(key)
|
|
95
|
+
if (no) nos.add(no)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return nos
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 用户手动标记完成的任务。 */
|
|
102
|
+
function getManualCompleted() {
|
|
103
|
+
return readMarked(manualPath())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 从任务列表响应里记下来的「达成」任务(api_state === 3)。 */
|
|
107
|
+
function getAutoCompleted() {
|
|
108
|
+
return readMarked(autoPath())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function markManual(no, title) {
|
|
112
|
+
const value = Number(no)
|
|
113
|
+
if (!value) return false
|
|
114
|
+
const file = manualPath()
|
|
115
|
+
const raw = readJsonFile(file) || {}
|
|
116
|
+
raw[String(value)] = { at: Date.now(), title: String(title || '') }
|
|
117
|
+
writeJsonFile(file, raw)
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function unmarkManual(no) {
|
|
122
|
+
const value = Number(no)
|
|
123
|
+
if (!value) return false
|
|
124
|
+
const file = manualPath()
|
|
125
|
+
const raw = readJsonFile(file) || {}
|
|
126
|
+
if (!raw[String(value)]) return false
|
|
127
|
+
delete raw[String(value)]
|
|
128
|
+
writeJsonFile(file, raw)
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 记录一次 questlist 响应:api_state === 3 的任务记为已完成,长期保存。
|
|
134
|
+
*/
|
|
135
|
+
function recordQuestStates(detail) {
|
|
136
|
+
const body = detail && detail.body
|
|
137
|
+
const list = body && Array.isArray(body.api_list) ? body.api_list : null
|
|
138
|
+
if (!list || list.length === 0) return 0
|
|
139
|
+
|
|
140
|
+
const file = autoPath()
|
|
141
|
+
const raw = readJsonFile(file) || {}
|
|
142
|
+
let added = 0
|
|
143
|
+
for (const quest of list) {
|
|
144
|
+
if (!quest) continue
|
|
145
|
+
const no = Number(quest.api_no)
|
|
146
|
+
if (!no || Number(quest.api_state) !== 3) continue
|
|
147
|
+
if (!raw[String(no)]) {
|
|
148
|
+
raw[String(no)] = { at: Date.now(), title: String(quest.api_title || '') }
|
|
149
|
+
added += 1
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (added > 0) writeJsonFile(file, raw)
|
|
153
|
+
return added
|
|
154
|
+
}
|
|
155
|
+
|
|
60
156
|
function emptyPlannerState() {
|
|
61
157
|
return { completed: new Set(), available: new Set() }
|
|
62
158
|
}
|
|
@@ -199,6 +295,8 @@ function getCompletedQuestNos(state) {
|
|
|
199
295
|
const completed = new Set([
|
|
200
296
|
...fromState.completed,
|
|
201
297
|
...planner.completed,
|
|
298
|
+
...getManualCompleted(),
|
|
299
|
+
...getAutoCompleted(),
|
|
202
300
|
])
|
|
203
301
|
const available = new Set([
|
|
204
302
|
...fromState.available,
|
|
@@ -215,8 +313,13 @@ function resetCaches() {
|
|
|
215
313
|
|
|
216
314
|
module.exports = {
|
|
217
315
|
expandCompleted,
|
|
316
|
+
getAutoCompleted,
|
|
218
317
|
getCompletedQuestNos,
|
|
318
|
+
getManualCompleted,
|
|
319
|
+
markManual,
|
|
219
320
|
readCachedAvailable,
|
|
220
321
|
readPlannerState,
|
|
322
|
+
recordQuestStates,
|
|
221
323
|
resetCaches,
|
|
324
|
+
unmarkManual,
|
|
222
325
|
}
|
package/lib/style.js
CHANGED
|
@@ -438,9 +438,77 @@ const CSS = `
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
.stock-consume-hidden {
|
|
441
|
-
margin-top:
|
|
442
|
-
font-size:
|
|
443
|
-
opacity: 0.
|
|
441
|
+
margin-top: 4px;
|
|
442
|
+
font-size: 10.5px;
|
|
443
|
+
opacity: 0.55;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.stock-consume-hidden > summary {
|
|
447
|
+
cursor: pointer;
|
|
448
|
+
list-style: none;
|
|
449
|
+
user-select: none;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.stock-consume-hidden > summary::-webkit-details-marker {
|
|
453
|
+
display: none;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.stock-consume-done-list {
|
|
457
|
+
margin: 4px 0 0;
|
|
458
|
+
padding: 0;
|
|
459
|
+
list-style: none;
|
|
460
|
+
display: grid;
|
|
461
|
+
gap: 3px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.stock-consume-done-list li {
|
|
465
|
+
display: flex;
|
|
466
|
+
align-items: baseline;
|
|
467
|
+
gap: 6px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.stock-consume-done-list .stock-consume-text {
|
|
471
|
+
text-decoration: line-through;
|
|
472
|
+
opacity: 0.7;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.stock-consume-done {
|
|
476
|
+
flex: none;
|
|
477
|
+
width: 20px;
|
|
478
|
+
height: 20px;
|
|
479
|
+
padding: 0;
|
|
480
|
+
border: 1px solid var(--stock-border);
|
|
481
|
+
border-radius: 5px;
|
|
482
|
+
color: var(--stock-green);
|
|
483
|
+
background: transparent;
|
|
484
|
+
font-family: inherit;
|
|
485
|
+
font-size: 11px;
|
|
486
|
+
line-height: 1;
|
|
487
|
+
cursor: pointer;
|
|
488
|
+
opacity: 0.55;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.stock-consume-done:hover {
|
|
492
|
+
opacity: 1;
|
|
493
|
+
border-color: var(--stock-green);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.stock-consume-undo {
|
|
497
|
+
flex: none;
|
|
498
|
+
height: 18px;
|
|
499
|
+
padding: 0 6px;
|
|
500
|
+
border: 1px solid var(--stock-border);
|
|
501
|
+
border-radius: 5px;
|
|
502
|
+
color: inherit;
|
|
503
|
+
background: transparent;
|
|
504
|
+
font-family: inherit;
|
|
505
|
+
font-size: 9.5px;
|
|
506
|
+
cursor: pointer;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.stock-consume-undo:hover {
|
|
510
|
+
border-color: var(--stock-indigo);
|
|
511
|
+
color: var(--stock-indigo);
|
|
444
512
|
}
|
|
445
513
|
|
|
446
514
|
.stock-consume-text {
|