poi-plugin-inventory-cleaner 1.11.1 → 1.12.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/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,100 @@ function Stat({ label, value, tone }) {
133
133
  )
134
134
  }
135
135
 
136
+ function fallbackIcon(event) {
137
+ const img = event.currentTarget
138
+ if (img.dataset.fallback) return
139
+ img.dataset.fallback = '1'
140
+ img.src = iconUrl(-1)
141
+ }
142
+
143
+ function iconUrl(iconType) {
144
+ if (typeof window === 'undefined') return ''
145
+ const root = window.ROOT || window.__ROOT || ''
146
+ if (!root) return ''
147
+ const base = String(root).replace(/\\/g, '/').replace(/^\/+/, '')
148
+ return 'file:///' + base + '/assets/img/slotitem/' + (Number(iconType) || 0) + '.png'
149
+ }
150
+
151
+ function CategoryPicker({ value, groups, onChange }) {
152
+ const [open, setOpen] = React.useState(false)
153
+ const boxRef = React.useRef(null)
154
+
155
+ React.useEffect(() => {
156
+ if (!open) return undefined
157
+ const onDocClick = (event) => {
158
+ if (boxRef.current && !boxRef.current.contains(event.target)) setOpen(false)
159
+ }
160
+ document.addEventListener('mousedown', onDocClick)
161
+ return () => document.removeEventListener('mousedown', onDocClick)
162
+ }, [open])
163
+
164
+ return h(
165
+ 'div',
166
+ { className: 'stock-picker', ref: boxRef },
167
+ h(
168
+ 'button',
169
+ {
170
+ type: 'button',
171
+ className: 'stock-picker-btn',
172
+ title: t('stock.categoryTitle'),
173
+ onClick: () => setOpen((current) => !current),
174
+ },
175
+ h('span', { className: 'stock-picker-label' }, value || t('stock.categoryAll')),
176
+ h('span', { className: 'stock-picker-caret' }, '▾'),
177
+ ),
178
+ open
179
+ ? h(
180
+ 'div',
181
+ { className: 'stock-picker-panel' },
182
+ h(
183
+ 'button',
184
+ {
185
+ type: 'button',
186
+ className: 'stock-picker-item' + (value ? '' : ' is-active'),
187
+ onClick: () => {
188
+ onChange('')
189
+ setOpen(false)
190
+ },
191
+ },
192
+ h('span', { className: 'stock-picker-name' }, t('stock.categoryAll')),
193
+ ),
194
+ groups.map(([groupName, list]) =>
195
+ h(
196
+ 'div',
197
+ { className: 'stock-picker-group', key: groupName },
198
+ h('div', { className: 'stock-picker-title' }, groupName),
199
+ list.map((entry) => {
200
+ const key = entry.key || OTHER_CATEGORY
201
+ return h(
202
+ 'button',
203
+ {
204
+ type: 'button',
205
+ key,
206
+ className: 'stock-picker-item' + (value === key ? ' is-active' : ''),
207
+ onClick: () => {
208
+ onChange(key)
209
+ setOpen(false)
210
+ },
211
+ },
212
+ h('img', {
213
+ className: 'stock-picker-icon',
214
+ src: iconUrl(entry.icon),
215
+ alt: '',
216
+ draggable: 'false',
217
+ onError: fallbackIcon,
218
+ }),
219
+ h('span', { className: 'stock-picker-name' }, entry.key || t('stock.batchOther')),
220
+ h('span', { className: 'stock-picker-count' }, String(entry.kinds)),
221
+ )
222
+ }),
223
+ ),
224
+ ),
225
+ )
226
+ : null,
227
+ )
228
+ }
229
+
136
230
  function StockRow({ row, onKeepChange }) {
137
231
  const verdict = VERDICT[row.verdict] || VERDICT.unknown
138
232
  return h(
@@ -141,6 +235,13 @@ function StockRow({ row, onKeepChange }) {
141
235
  h(
142
236
  'div',
143
237
  { className: 'stock-row-main' },
238
+ h('img', {
239
+ className: 'stock-icon',
240
+ src: iconUrl(row.icon),
241
+ alt: '',
242
+ draggable: 'false',
243
+ onError: fallbackIcon,
244
+ }),
144
245
  h('span', { className: 'stock-name' }, row.cn || row.jp || `#${row.id}`),
145
246
  row.cn && row.jp && row.cn !== row.jp
146
247
  ? h('span', { className: 'stock-cn' }, row.jp)
@@ -307,6 +408,7 @@ function App() {
307
408
  const entry = map.get(key) || {
308
409
  key,
309
410
  kinds: 0,
411
+ icon: row.icon || 0,
310
412
  group: row.group || '',
311
413
  groupId: row.groupId || 99,
312
414
  categoryId: row.categoryId || 0,
@@ -391,29 +493,11 @@ function App() {
391
493
  placeholder: t('stock.search'),
392
494
  onChange: (event) => setKeyword(event.target.value),
393
495
  }),
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
- ),
496
+ h(CategoryPicker, {
497
+ value: category,
498
+ groups: categoryGroups,
499
+ onChange: setCategory,
500
+ }),
417
501
  h(
418
502
  'select',
419
503
  {
@@ -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.1",
4
4
  "description": "库存清理大师:对照改修攻略与保留上限,找出囤积过量、可以清理的装备,并标出不能拆的改修材料。",
5
5
  "main": "index.js",
6
6
  "files": [