poi-plugin-inventory-cleaner 1.11.1 → 1.12.2

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/README-CN.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - **联动当前任务**:任务需要的装备标「任务需要 N 件」,不参与可拆的件数就是预留的那部分。
18
18
  - **按装备类型筛选**:沿用游戏本体「装备筛选」的 14 个大类 / 60 多个细分类型(与「装备信息」插件一致,如舰上战斗机、夜间轰炸机、高角炮、大口径主炮(II)),下拉框按大类分组。
19
19
  - **排序**:按攻略判定 / 数量 / 可拆件数 / 名称 / 类型,随时切换。
20
- - **名称显示**:中文名为主,日文原名作为副标题保留。
20
+ - **名称显示**:中文名为主,日文原名作为副标题保留;每行和分类下拉都带游戏原版装备图标。
21
21
  - **筛选与搜索**:全部 / 可拆 / 留一些 / 不拆 / 看计划 / 受保护,支持按名称或 id 搜索。
22
22
 
23
23
  - **独立窗口**:使用 poi 原生的「在新窗口打开插件」功能(`poiPlugin.windowMode`,默认开启)。点击插件图标时 poi 会自己创建窗口并承载插件,带标题栏、主题、缩放和窗口位置记忆;不需要的话在「设置 → 插件 → 库存清理大师」里关掉即可。
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  - **Unfinished quest needs** — collects equipment from quest text (in-progress quests, `poi-plugin-quest-planner` observed quests, the `poi-plugin-quest-info-2` quest list, or the I-Tell-You cache — whichever is available), parses the required count, and reserves that many pieces. It does not depend on a single plugin; with none installed it still counts in-progress quests.
16
16
  - **Type filter** — the game's own 14 equipment categories and 60+ detail types, matching Equipment Info, grouped in the dropdown.
17
17
  - **Sorting** — verdict / count / clearable / name / type.
18
- - **Names** — Chinese name first, original Japanese shown underneath.
18
+ - **Names** — Chinese name first, original Japanese shown underneath, with the in-game equipment icon on every row and in the type picker.
19
19
  - **Filters & search** — all / scrap / keep some / keep / check plan / protected; search by name or id.
20
20
 
21
21
  - **Native window mode** — ships `poiPlugin.windowMode`, so poi opens the plugin in its own managed window (titlebar, theme, zoom, remembered bounds). Toggle it off in poi settings if you prefer the tab.
package/index.js CHANGED
@@ -133,6 +133,118 @@ function Stat({ label, value, tone }) {
133
133
  )
134
134
  }
135
135
 
136
+ function iconBase() {
137
+ if (typeof window === 'undefined') return ''
138
+ const root = window.ROOT || window.__ROOT || ''
139
+ if (!root) return ''
140
+ return 'file:///' + String(root).replace(/\\/g, '/').replace(/^\/+/, '')
141
+ }
142
+
143
+ function iconUrl(iconType, kind) {
144
+ const base = iconBase()
145
+ if (!base) return ''
146
+ const id = Number(iconType) || 0
147
+ return kind === 'png'
148
+ ? base + '/assets/img/slotitem/' + id + '.png'
149
+ : base + '/assets/svg/slotitem/' + id + '.svg'
150
+ }
151
+
152
+ // poi 的 PNG 图标只到 47,48~60 只有 SVG;按 svg → png → 通用图标 的顺序回落
153
+ function fallbackIcon(event) {
154
+ const img = event.currentTarget
155
+ const stage = img.dataset.iconStage || 'svg'
156
+ const id = Number(img.dataset.iconId) || 0
157
+ if (stage === 'svg') {
158
+ img.dataset.iconStage = 'png'
159
+ img.src = iconUrl(id, 'png')
160
+ return
161
+ }
162
+ if (stage === 'png') {
163
+ img.dataset.iconStage = 'none'
164
+ img.src = iconUrl(-1, 'svg')
165
+ }
166
+ }
167
+
168
+ function CategoryPicker({ value, groups, onChange }) {
169
+ const [open, setOpen] = React.useState(false)
170
+ const boxRef = React.useRef(null)
171
+
172
+ React.useEffect(() => {
173
+ if (!open) return undefined
174
+ const onDocClick = (event) => {
175
+ if (boxRef.current && !boxRef.current.contains(event.target)) setOpen(false)
176
+ }
177
+ document.addEventListener('mousedown', onDocClick)
178
+ return () => document.removeEventListener('mousedown', onDocClick)
179
+ }, [open])
180
+
181
+ return h(
182
+ 'div',
183
+ { className: 'stock-picker', ref: boxRef },
184
+ h(
185
+ 'button',
186
+ {
187
+ type: 'button',
188
+ className: 'stock-picker-btn',
189
+ title: t('stock.categoryTitle'),
190
+ onClick: () => setOpen((current) => !current),
191
+ },
192
+ h('span', { className: 'stock-picker-label' }, value || t('stock.categoryAll')),
193
+ h('span', { className: 'stock-picker-caret' }, '▾'),
194
+ ),
195
+ open
196
+ ? h(
197
+ 'div',
198
+ { className: 'stock-picker-panel' },
199
+ h(
200
+ 'button',
201
+ {
202
+ type: 'button',
203
+ className: 'stock-picker-item' + (value ? '' : ' is-active'),
204
+ onClick: () => {
205
+ onChange('')
206
+ setOpen(false)
207
+ },
208
+ },
209
+ h('span', { className: 'stock-picker-name' }, t('stock.categoryAll')),
210
+ ),
211
+ groups.map(([groupName, list]) =>
212
+ h(
213
+ 'div',
214
+ { className: 'stock-picker-group', key: groupName },
215
+ h('div', { className: 'stock-picker-title' }, groupName),
216
+ list.map((entry) => {
217
+ const key = entry.key || OTHER_CATEGORY
218
+ return h(
219
+ 'button',
220
+ {
221
+ type: 'button',
222
+ key,
223
+ className: 'stock-picker-item' + (value === key ? ' is-active' : ''),
224
+ onClick: () => {
225
+ onChange(key)
226
+ setOpen(false)
227
+ },
228
+ },
229
+ h('img', {
230
+ className: 'stock-picker-icon',
231
+ src: iconUrl(entry.icon),
232
+ alt: '',
233
+ draggable: 'false',
234
+ 'data-icon-id': String(entry.icon || 0),
235
+ onError: fallbackIcon,
236
+ }),
237
+ h('span', { className: 'stock-picker-name' }, entry.key || t('stock.batchOther')),
238
+ h('span', { className: 'stock-picker-count' }, String(entry.kinds)),
239
+ )
240
+ }),
241
+ ),
242
+ ),
243
+ )
244
+ : null,
245
+ )
246
+ }
247
+
136
248
  function StockRow({ row, onKeepChange }) {
137
249
  const verdict = VERDICT[row.verdict] || VERDICT.unknown
138
250
  return h(
@@ -141,6 +253,14 @@ function StockRow({ row, onKeepChange }) {
141
253
  h(
142
254
  'div',
143
255
  { className: 'stock-row-main' },
256
+ h('img', {
257
+ className: 'stock-icon',
258
+ src: iconUrl(row.icon),
259
+ alt: '',
260
+ draggable: 'false',
261
+ 'data-icon-id': String(row.icon || 0),
262
+ onError: fallbackIcon,
263
+ }),
144
264
  h('span', { className: 'stock-name' }, row.cn || row.jp || `#${row.id}`),
145
265
  row.cn && row.jp && row.cn !== row.jp
146
266
  ? h('span', { className: 'stock-cn' }, row.jp)
@@ -307,6 +427,7 @@ function App() {
307
427
  const entry = map.get(key) || {
308
428
  key,
309
429
  kinds: 0,
430
+ icon: row.icon || 0,
310
431
  group: row.group || '',
311
432
  groupId: row.groupId || 99,
312
433
  categoryId: row.categoryId || 0,
@@ -391,29 +512,11 @@ function App() {
391
512
  placeholder: t('stock.search'),
392
513
  onChange: (event) => setKeyword(event.target.value),
393
514
  }),
394
- h(
395
- 'select',
396
- {
397
- className: 'stock-category',
398
- value: category,
399
- title: t('stock.categoryTitle'),
400
- onChange: (event) => setCategory(event.target.value),
401
- },
402
- h('option', { value: '' }, t('stock.categoryAll')),
403
- categoryGroups.map(([groupName, list]) =>
404
- h(
405
- 'optgroup',
406
- { key: groupName, label: groupName },
407
- list.map((entry) =>
408
- h(
409
- 'option',
410
- { key: entry.key || 'other', value: entry.key || OTHER_CATEGORY },
411
- (entry.key || t('stock.batchOther')) + ' (' + entry.kinds + ')',
412
- ),
413
- ),
414
- ),
415
- ),
416
- ),
515
+ h(CategoryPicker, {
516
+ value: category,
517
+ groups: categoryGroups,
518
+ onChange: setCategory,
519
+ }),
417
520
  h(
418
521
  'select',
419
522
  {
@@ -162,6 +162,7 @@ function buildTypeIndex(state) {
162
162
  const group = detail ? GROUP_BY_ID.get(detail.group) : null
163
163
  index.set(Number(id), {
164
164
  filterType,
165
+ icon: Number(master.api_type && master.api_type[3]) || 0,
165
166
  detailId: detail ? detail.id : 0,
166
167
  groupId: detail ? detail.group : 99,
167
168
  category: labels ? labels.cn : '',
package/lib/inventory.js CHANGED
@@ -110,6 +110,7 @@ function buildInventory(state, options = {}) {
110
110
  verdict,
111
111
  rawVerdict: rule ? rule.raw : '',
112
112
  category: typeInfo && typeInfo.category ? typeInfo.category : (rule ? rule.category : ''),
113
+ icon: typeInfo ? typeInfo.icon : 0,
113
114
  categoryId: typeInfo ? typeInfo.detailId : 0,
114
115
  group: typeInfo ? typeInfo.group : '',
115
116
  groupId: typeInfo ? typeInfo.groupId : 99,
package/lib/style.js CHANGED
@@ -100,6 +100,121 @@ const CSS = `
100
100
  border-color: var(--stock-indigo);
101
101
  }
102
102
 
103
+ .stock-icon {
104
+ width: 24px;
105
+ height: 24px;
106
+ flex: none;
107
+ border-radius: 4px;
108
+ opacity: 0.92;
109
+ }
110
+
111
+ .stock-picker {
112
+ position: relative;
113
+ display: inline-block;
114
+ }
115
+
116
+ .stock-picker-btn {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 6px;
120
+ height: 32px;
121
+ max-width: 240px;
122
+ padding: 0 10px;
123
+ border: 1px solid var(--stock-border);
124
+ border-radius: 8px;
125
+ color: inherit;
126
+ background: var(--stock-surface);
127
+ font-family: inherit;
128
+ font-size: 12px;
129
+ cursor: pointer;
130
+ }
131
+
132
+ .stock-picker-btn:hover {
133
+ border-color: var(--stock-border-strong);
134
+ }
135
+
136
+ .stock-picker-label {
137
+ overflow: hidden;
138
+ text-overflow: ellipsis;
139
+ white-space: nowrap;
140
+ }
141
+
142
+ .stock-picker-caret {
143
+ flex: none;
144
+ opacity: 0.6;
145
+ }
146
+
147
+ .stock-picker-panel {
148
+ position: absolute;
149
+ top: calc(100% + 4px);
150
+ left: 0;
151
+ z-index: 60;
152
+ width: 330px;
153
+ max-height: 440px;
154
+ overflow-y: auto;
155
+ padding: 6px;
156
+ border: 1px solid var(--stock-border-strong);
157
+ border-radius: 10px;
158
+ background: #1b2129;
159
+ box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
160
+ }
161
+
162
+ .stock-picker-group + .stock-picker-group {
163
+ margin-top: 2px;
164
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
165
+ }
166
+
167
+ .stock-picker-title {
168
+ padding: 8px 8px 3px;
169
+ font-size: 10px;
170
+ letter-spacing: 0.06em;
171
+ color: rgba(232, 237, 244, 0.48);
172
+ }
173
+
174
+ .stock-picker-item {
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 8px;
178
+ width: 100%;
179
+ padding: 6px 8px;
180
+ border: 0;
181
+ border-radius: 7px;
182
+ color: #e8edf4;
183
+ background: transparent;
184
+ font-family: inherit;
185
+ font-size: 12px;
186
+ text-align: left;
187
+ cursor: pointer;
188
+ }
189
+
190
+ .stock-picker-item:hover {
191
+ background: rgba(255, 255, 255, 0.08);
192
+ }
193
+
194
+ .stock-picker-item.is-active {
195
+ background: rgba(99, 102, 241, 0.4);
196
+ }
197
+
198
+ .stock-picker-icon {
199
+ width: 22px;
200
+ height: 22px;
201
+ flex: none;
202
+ border-radius: 3px;
203
+ }
204
+
205
+ .stock-picker-name {
206
+ flex: 1 1 auto;
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
209
+ white-space: nowrap;
210
+ }
211
+
212
+ .stock-picker-count {
213
+ flex: none;
214
+ font-variant-numeric: tabular-nums;
215
+ opacity: 0.5;
216
+ }
217
+
103
218
  .stock-category {
104
219
  height: 32px;
105
220
  padding: 0 8px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-inventory-cleaner",
3
- "version": "1.11.1",
3
+ "version": "1.12.2",
4
4
  "description": "库存清理大师:对照改修攻略与保留上限,找出囤积过量、可以清理的装备,并标出不能拆的改修材料。",
5
5
  "main": "index.js",
6
6
  "files": [