poi-plugin-quest-info-2 0.7.4 → 0.8.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.
@@ -1,23 +1,70 @@
1
- import { Tag } from '@blueprintjs/core'
2
- import React, { useCallback } from 'react'
1
+ import type { TooltipProps } from '@blueprintjs/core'
2
+ import { Tag, Tooltip } from '@blueprintjs/core'
3
+ import { IconNames } from '@blueprintjs/icons'
4
+ import React, { forwardRef, useCallback } from 'react'
3
5
  import styled from 'styled-components'
4
- import { guessQuestCategory } from '../questHelper'
6
+ import { DocQuest, guessQuestCategory, QUEST_STATUS } from '../questHelper'
5
7
  import { useFilterTags } from '../store/filterTags'
8
+ import { useQuestByCode, useQuestStatus } from '../store/quest'
6
9
  import { useSearchInput } from '../store/search'
7
10
 
8
11
  const TagWrapper = styled(Tag)`
9
12
  margin: 2px 4px;
10
- user-select: none;
13
+ user-select: ${({ interactive }) => (interactive ? 'none' : 'auto')};
11
14
  overflow: visible;
12
15
 
13
16
  & > span {
14
- cursor: pointer;
17
+ cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')};
15
18
  }
16
19
  `
17
20
 
21
+ const QuestTooltip = forwardRef<
22
+ Tooltip,
23
+ Omit<TooltipProps, 'content'> & {
24
+ quest: DocQuest
25
+ children: React.ReactNode
26
+ }
27
+ >(({ quest, children, ...props }, ref) => {
28
+ if (!quest) {
29
+ return <>{children}</>
30
+ }
31
+ return (
32
+ <Tooltip
33
+ ref={ref}
34
+ content={
35
+ <>
36
+ <div>{`${quest.code} - ${quest.name}`}</div>
37
+ <div>{quest.desc}</div>
38
+ {quest.memo2 && <b>{quest.memo2}</b>}
39
+ {quest.memo && <i>{quest.memo}</i>}
40
+ </>
41
+ }
42
+ placement={'top'}
43
+ {...props}
44
+ >
45
+ {children}
46
+ </Tooltip>
47
+ )
48
+ })
49
+
50
+ const getTagIcon = (questStatus: QUEST_STATUS) => {
51
+ switch (questStatus) {
52
+ case QUEST_STATUS.ALREADY_COMPLETED:
53
+ return IconNames.TICK
54
+ case QUEST_STATUS.LOCKED:
55
+ return IconNames.LOCK
56
+ default:
57
+ return null
58
+ }
59
+ }
60
+
18
61
  export const QuestTag = ({ code }: { code: string }) => {
19
62
  const { setSearchInput } = useSearchInput()
20
63
  const { setCategoryTagsAll, setTypeTagsAll } = useFilterTags()
64
+ const maybeQuest = useQuestByCode(code)
65
+ const maybeGameId = maybeQuest?.gameId ?? null
66
+ const questStatus = useQuestStatus(maybeGameId)
67
+ const tagIcon = getTagIcon(questStatus)
21
68
 
22
69
  const handleClick = useCallback(() => {
23
70
  setSearchInput(code)
@@ -29,13 +76,29 @@ export const QuestTag = ({ code }: { code: string }) => {
29
76
  indicatorColor === '#fff' || indicatorColor === '#87da61'
30
77
  ? 'black'
31
78
  : 'white'
79
+
80
+ if (!maybeQuest) {
81
+ return (
82
+ <TagWrapper
83
+ icon={IconNames.HELP}
84
+ style={{ color: fontColor, background: indicatorColor }}
85
+ >
86
+ {code}
87
+ </TagWrapper>
88
+ )
89
+ }
90
+
91
+ const quest = maybeQuest.docQuest
32
92
  return (
33
- <TagWrapper
34
- onClick={handleClick}
35
- interactive
36
- style={{ color: fontColor, background: indicatorColor }}
37
- >
38
- {code}
39
- </TagWrapper>
93
+ <QuestTooltip quest={quest}>
94
+ <TagWrapper
95
+ interactive
96
+ icon={tagIcon}
97
+ onClick={handleClick}
98
+ style={{ color: fontColor, background: indicatorColor }}
99
+ >
100
+ {code}
101
+ </TagWrapper>
102
+ </QuestTooltip>
40
103
  )
41
104
  }
package/src/poi/hooks.ts CHANGED
@@ -1,9 +1,12 @@
1
- import { useState, useEffect } from 'react'
1
+ import { useEffect, useState } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
- import { observePluginStore, observePoiStore } from './store'
4
3
  import { name as PACKAGE_NAME } from '../../package.json'
4
+ import { observePluginStore, observePoiStore } from './store'
5
5
  import { GameQuest, PoiQuestState, PoiState, QuestTab } from './types'
6
6
 
7
+ export const activeQuestsSelector = (state: PoiState): PoiQuestState =>
8
+ state?.info?.quests?.activeQuests ?? {}
9
+
7
10
  export const useActiveQuest = () => {
8
11
  const [activeQuests, setActiveQuests] = useState<PoiQuestState>({})
9
12
 
@@ -17,9 +20,6 @@ export const useActiveQuest = () => {
17
20
  return activeQuests
18
21
  }
19
22
 
20
- export const activeQuestsSelector = (state: PoiState): PoiQuestState =>
21
- state?.info?.quests?.activeQuests ?? {}
22
-
23
23
  export const usePluginTranslation = () => {
24
24
  return useTranslation(PACKAGE_NAME)
25
25
  }
@@ -30,7 +30,7 @@ export const useGameQuest = () => {
30
30
  const listener = (quests: GameQuest[] | null) => setQuests(quests ?? [])
31
31
  // See reducer.ts
32
32
  return observePluginStore(listener, (i) => i?._?.questList)
33
- }, [])
33
+ }, [setQuests])
34
34
  return quests
35
35
  }
36
36
 
@@ -1,9 +1,13 @@
1
- import questCategory from '../build/questCategory.json'
1
+ import moize from 'moize'
2
+ import { QuestData } from '../build/kcanotifyGamedata'
3
+ import { KcwikiQuestData } from '../build/kcQuestsData'
2
4
  import newQuestData from '../build/kcQuestsData/quests-scn-new.json'
3
5
  import prePostQuest from '../build/prePostQuest.json'
6
+ import questCategory from '../build/questCategory.json'
7
+ import questCodeMap from '../build/questCodeMap.json'
4
8
  import { GameQuest, QUEST_API_STATE } from './poi/types'
5
9
 
6
- type DocQuest = {
10
+ export type DocQuest = {
7
11
  code: string
8
12
  name: string
9
13
  desc: string
@@ -35,6 +39,9 @@ export type UnionQuest = {
35
39
  docQuest: DocQuest
36
40
  }
37
41
 
42
+ export const getKcwikiQuestData = () => KcwikiQuestData
43
+ export const getKcanotifyQuestData = () => QuestData
44
+
38
45
  const dailyQuest = new Set(questCategory.dailyQuest)
39
46
  const weeklyQuest = new Set(questCategory.weeklyQuest)
40
47
  const monthlyQuest = new Set(questCategory.monthlyQuest)
@@ -197,17 +204,80 @@ export const isUnknownCategoryQuest = ({ code }: DocQuest) =>
197
204
  // Starts with unknown character
198
205
  /^[^ABCDEFG]/.test(code)
199
206
 
200
- export const getPrePost = (gameId: number) => {
207
+ export const getQuestPrePost = (gameId: number) => {
201
208
  if (!(gameId in prePostQuest)) {
202
209
  return { pre: [], post: [] }
203
210
  }
204
211
  return prePostQuest[String(gameId) as keyof typeof prePostQuest]
205
212
  }
206
213
 
214
+ export const getQuestIdByCode = (code: string) => {
215
+ if (code in questCodeMap) {
216
+ return questCodeMap[code as keyof typeof questCodeMap]
217
+ }
218
+ return null
219
+ }
220
+
221
+ export const getPreQuestIds = (gameId: number): number[] =>
222
+ getQuestPrePost(gameId)
223
+ .pre.map((code) => getQuestIdByCode(code))
224
+ .filter(Boolean) as number[]
225
+
226
+ export const getPostQuestIds = (gameId: number): number[] =>
227
+ getQuestPrePost(gameId)
228
+ .post.map((code) => getQuestIdByCode(code))
229
+ .filter(Boolean) as number[]
230
+
231
+ const calcQuestMap = (
232
+ inProgressQuests: number[],
233
+ next: (gameId: number) => number[]
234
+ ) => {
235
+ const map: Record<number, true> = {}
236
+ const queue: number[] = inProgressQuests.flatMap(next)
237
+ while (queue.length) {
238
+ const gameId = queue.shift()!
239
+ if (gameId in map) {
240
+ continue
241
+ }
242
+ map[gameId] = true
243
+
244
+ next(gameId).forEach((nextGameId) => {
245
+ queue.push(nextGameId)
246
+ })
247
+ }
248
+ return map
249
+ }
250
+
251
+ export const getCompletedQuest = moize((inProgressQuest: number[]) => {
252
+ const completedQuest = calcQuestMap(inProgressQuest, getPreQuestIds)
253
+ return completedQuest
254
+ })
255
+
256
+ export const getLockedQuest = moize((inProgressQuest: number[]) => {
257
+ const lockedQuest = calcQuestMap(inProgressQuest, getPostQuestIds)
258
+ return lockedQuest
259
+ })
260
+
261
+ export const questApiStateToQuestStatus = (
262
+ state: QUEST_API_STATE | undefined
263
+ ): QUEST_STATUS => {
264
+ switch (state) {
265
+ case QUEST_API_STATE.DEFAULT:
266
+ return QUEST_STATUS.DEFAULT
267
+ case QUEST_API_STATE.COMPLETED:
268
+ return QUEST_STATUS.COMPLETED
269
+ case QUEST_API_STATE.IN_PROGRESS:
270
+ return QUEST_STATUS.IN_PROGRESS
271
+ default:
272
+ return QUEST_STATUS.DEFAULT
273
+ }
274
+ }
275
+
207
276
  export enum QUEST_STATUS {
208
277
  LOCKED,
209
278
  DEFAULT,
210
279
  IN_PROGRESS,
211
280
  COMPLETED,
212
281
  ALREADY_COMPLETED,
282
+ UNKNOWN,
213
283
  }
@@ -1,5 +1,5 @@
1
- import { KcwikiQuestData } from '../../build/kcQuestsData'
2
1
  import { useStore } from '.'
2
+ import { getKcwikiQuestData } from '../questHelper'
3
3
 
4
4
  export const usePreferKcwiki = () => {
5
5
  const {
@@ -13,7 +13,10 @@ export const usePreferKcwiki = () => {
13
13
 
14
14
  export const checkIsKcwikiSupportedLanguages = (
15
15
  lang: string
16
- ): lang is keyof typeof KcwikiQuestData => lang in KcwikiQuestData
16
+ ): lang is keyof typeof kcwikiQuestData => {
17
+ const kcwikiQuestData = getKcwikiQuestData()
18
+ return lang in kcwikiQuestData
19
+ }
17
20
 
18
21
  export const useKcwikiData = (lang: string) => {
19
22
  const [preferKcwiki] = usePreferKcwiki()
@@ -25,5 +28,6 @@ export const useKcwikiData = (lang: string) => {
25
28
  if (!supported) {
26
29
  return null
27
30
  }
28
- return KcwikiQuestData[lang]
31
+ const kcwikiQuestData = getKcwikiQuestData()
32
+ return kcwikiQuestData[lang]
29
33
  }
@@ -1,20 +1,31 @@
1
1
  import { useCallback } from 'react'
2
- import { QuestData } from '../../build/kcanotifyGamedata'
3
2
  import { useGameQuest, usePluginTranslation } from '../poi/hooks'
4
- import { getCategory } from '../questHelper'
5
- import type { UnionQuest } from '../questHelper'
6
- import { useKcwikiData, checkIsKcwikiSupportedLanguages } from './kcwiki'
3
+ import {
4
+ DocQuest,
5
+ getCategory,
6
+ getCompletedQuest,
7
+ getKcanotifyQuestData,
8
+ getLockedQuest,
9
+ getQuestIdByCode,
10
+ questApiStateToQuestStatus,
11
+ QUEST_STATUS,
12
+ UnionQuest,
13
+ } from '../questHelper'
14
+ import { checkIsKcwikiSupportedLanguages, useKcwikiData } from './kcwiki'
7
15
  import { useStore, useSyncWithGame } from './store'
8
16
 
9
17
  const DEFAULT_LANG = 'ja-JP'
10
18
 
11
- export const checkIsKcanotifySupportedLanguages = (
19
+ const checkIsKcanotifySupportedLanguages = (
12
20
  lang: string
13
- ): lang is keyof typeof QuestData => lang in QuestData
21
+ ): lang is keyof typeof kcaQuestData => {
22
+ const kcaQuestData = getKcanotifyQuestData()
23
+ return lang in kcaQuestData
24
+ }
14
25
 
15
26
  export const isSupportedLanguages = (
16
27
  lang: string
17
- ): lang is keyof typeof QuestData =>
28
+ ): lang is keyof ReturnType<typeof getKcanotifyQuestData> =>
18
29
  checkIsKcanotifySupportedLanguages(lang) ||
19
30
  checkIsKcwikiSupportedLanguages(lang)
20
31
 
@@ -28,13 +39,14 @@ export const useLanguage = () => {
28
39
  return lang
29
40
  }
30
41
 
31
- const useQuestMap = () => {
42
+ const useQuestMap = (): Record<string, DocQuest> => {
32
43
  const lang = useLanguage()
33
44
  const kcwikiData = useKcwikiData(lang)
34
45
  if (kcwikiData) {
35
46
  return kcwikiData
36
47
  }
37
- return QuestData[lang]
48
+ const kcaQuestData = getKcanotifyQuestData()
49
+ return kcaQuestData[lang]
38
50
  }
39
51
 
40
52
  export const useQuest = (): UnionQuest[] => {
@@ -76,6 +88,40 @@ export const useQuest = (): UnionQuest[] => {
76
88
  }
77
89
  }
78
90
 
91
+ export const useQuestByCode = (code: string): UnionQuest | null => {
92
+ const questMap = useQuestMap()
93
+ const gameId = getQuestIdByCode(code)
94
+ if (gameId && gameId in questMap) {
95
+ return {
96
+ gameId,
97
+ docQuest: questMap[String(gameId) as keyof typeof questMap],
98
+ }
99
+ }
100
+ return null
101
+ }
102
+
103
+ export const useQuestStatus = (gameId: number | null) => {
104
+ const gameQuest = useGameQuest()
105
+ const gameQuestId = gameQuest.map((quest) => quest.api_no)
106
+ const completedQuest = getCompletedQuest(gameQuestId)
107
+ const lockedQuest = getLockedQuest(gameQuestId)
108
+
109
+ if (!gameId) {
110
+ return QUEST_STATUS.UNKNOWN
111
+ }
112
+ const theGameQuest = gameQuest.find((quest) => quest.api_no === gameId)
113
+ if (theGameQuest) {
114
+ return questApiStateToQuestStatus(theGameQuest.api_state)
115
+ }
116
+ if (gameId in lockedQuest) {
117
+ return QUEST_STATUS.LOCKED
118
+ }
119
+ if (gameId in completedQuest) {
120
+ return QUEST_STATUS.ALREADY_COMPLETED
121
+ }
122
+ return QUEST_STATUS.UNKNOWN
123
+ }
124
+
79
125
  /**
80
126
  * @deprecated Not large card now
81
127
  */
package/src/tags.tsx CHANGED
@@ -38,12 +38,12 @@ export const ALL_CATEGORY_TAG = {
38
38
  export const ALL_TYPE_TAG = ALL_CATEGORY_TAG
39
39
 
40
40
  const withDocQuest =
41
- <T extends any>(filterFn: (q: UnionQuest['docQuest']) => T) =>
41
+ <T,>(filterFn: (q: UnionQuest['docQuest']) => T) =>
42
42
  (unionQuest: UnionQuest) =>
43
43
  filterFn(unionQuest.docQuest)
44
44
 
45
45
  const withGameQuestOr =
46
- <T extends any>(filterFn: (q: GameQuest) => T, fallback: T) =>
46
+ <T,>(filterFn: (q: GameQuest) => T, fallback: T) =>
47
47
  ({ gameQuest }: UnionQuest) => {
48
48
  if (!gameQuest) {
49
49
  return fallback
package/tsconfig.json CHANGED
@@ -44,7 +44,7 @@
44
44
  // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
45
45
  // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
46
46
  // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
47
- "importsNotUsedAsValues": "error",
47
+ // "importsNotUsedAsValues": "error",
48
48
 
49
49
  /* Module Resolution Options */
50
50
  // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */