poi-plugin-quest-info-2 0.7.4 → 0.8.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/.eslintrc.js CHANGED
@@ -19,8 +19,6 @@ module.exports = {
19
19
  'plugin:react/recommended',
20
20
  'plugin:react-hooks/recommended',
21
21
  'prettier',
22
- 'prettier/react',
23
- 'prettier/@typescript-eslint',
24
22
  ],
25
23
  parser: '@typescript-eslint/parser',
26
24
  parserOptions: {
@@ -1 +1 @@
1
- 433261cf82381efd22702e882ad359bf426695b5
1
+ cf24003d54c1b9cb2f6a76cdd670de717de0886c
@@ -4,4 +4,4 @@ export const KcwikiQuestData = {
4
4
  'zh-CN': zh_CN,
5
5
  }
6
6
 
7
- export const version = '433261cf82381efd22702e882ad359bf426695b5'
7
+ export const version = 'cf24003d54c1b9cb2f6a76cdd670de717de0886c'
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-quest-info-2",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "show quest info",
6
6
  "homepage": "https://github.com/lawvs/poi-plugin-quest-2/",
@@ -44,33 +44,33 @@
44
44
  "@storybook/addon-essentials": "^6.3.4",
45
45
  "@storybook/addon-links": "^6.3.4",
46
46
  "@storybook/react": "^6.3.4",
47
- "@types/jest": "^26.0.20",
47
+ "@types/jest": "^27.4.1",
48
48
  "@types/pangu": "^3.3.0",
49
49
  "@types/react-virtualized": "^9.21.11",
50
50
  "@types/sharp": "^0.27.1",
51
51
  "@types/styled-components": "^5.1.7",
52
- "@typescript-eslint/eslint-plugin": "^4.14.1",
53
- "@typescript-eslint/parser": "^4.14.1",
52
+ "@typescript-eslint/eslint-plugin": "^5.22.0",
53
+ "@typescript-eslint/parser": "^5.22.0",
54
54
  "babel-loader": "^8.2.2",
55
- "eslint": "^7.18.0",
56
- "eslint-config-prettier": "^7.2.0",
57
- "eslint-plugin-prettier": "^3.3.1",
58
- "eslint-plugin-react": "^7.22.0",
59
- "eslint-plugin-react-hooks": "^4.2.0",
55
+ "eslint": "^8.14.0",
56
+ "eslint-config-prettier": "^8.5.0",
57
+ "eslint-plugin-prettier": "^4.0.0",
58
+ "eslint-plugin-react": "^7.29.4",
59
+ "eslint-plugin-react-hooks": "^4.5.0",
60
60
  "https-proxy-agent": "^5.0.0",
61
61
  "i18next": "^19.8.5",
62
62
  "jest": "^27.0.6",
63
63
  "pangu": "^4.0.7",
64
64
  "poi-asset-themes": "^4.2.0",
65
- "prettier": "^2.3.2",
65
+ "prettier": "^2.6.2",
66
66
  "react": "^17.0.1",
67
67
  "react-dom": "^17.0.1",
68
68
  "react-i18next": "^11.8.5",
69
69
  "sharp": "^0.28.3",
70
70
  "styled-components": "^5.3.3",
71
- "ts-jest": "^27.0.3",
72
- "ts-node": "^9.1.1",
73
- "typescript": "^4.4.4"
71
+ "ts-jest": "^27.1.4",
72
+ "ts-node": "^10.7.0",
73
+ "typescript": "^4.5.5"
74
74
  },
75
75
  "poiPlugin": {
76
76
  "title": "Quest Information",
@@ -4,7 +4,7 @@ import newQuestData from '../../build/kcQuestsData/quests-scn-new.json'
4
4
  describe('should version correct', () => {
5
5
  test('should KcwikiQuestData Game data version correct', () => {
6
6
  expect(version).toMatchInlineSnapshot(
7
- `"433261cf82381efd22702e882ad359bf426695b5"`
7
+ `"cf24003d54c1b9cb2f6a76cdd670de717de0886c"`
8
8
  )
9
9
  })
10
10
 
@@ -6,6 +6,9 @@ import type { QuestCardProps } from './index'
6
6
  import { CardBody, CardTail, CatIndicator, FlexCard } from './styles'
7
7
  import { questStatusMap } from './utils'
8
8
 
9
+ /**
10
+ * @deprecated
11
+ */
9
12
  export const MinimalQuestCard = forwardRef<
10
13
  Card,
11
14
  // eslint-disable-next-line @typescript-eslint/ban-types
@@ -2,7 +2,11 @@ import { Card, Elevation, H5, Text } from '@blueprintjs/core'
2
2
  import React, { forwardRef } from 'react'
3
3
  import type { StyledComponentProps } from 'styled-components'
4
4
  import { usePluginTranslation } from '../../poi/hooks'
5
- import { getPrePost, guessQuestCategory, QUEST_STATUS } from '../../questHelper'
5
+ import {
6
+ getQuestPrePost,
7
+ guessQuestCategory,
8
+ QUEST_STATUS,
9
+ } from '../../questHelper'
6
10
  import { QuestTag } from '../QuestTag'
7
11
  import {
8
12
  CardActionWrapper,
@@ -29,15 +33,15 @@ export type QuestCardProps = {
29
33
  const CardAction = ({ gameId }: { gameId: number }) => {
30
34
  const { t } = usePluginTranslation()
31
35
 
32
- const preQuests = getPrePost(gameId)
36
+ const prePostQuests = getQuestPrePost(gameId)
33
37
 
34
38
  return (
35
39
  <CardActionWrapper>
36
40
  <TagsWrapper>
37
- {!!preQuests.pre.length && (
41
+ {!!prePostQuests.pre.length && (
38
42
  <>
39
43
  <SpanText>{t('Requires')}</SpanText>
40
- {preQuests.pre.map((i) => (
44
+ {prePostQuests.pre.map((i) => (
41
45
  <QuestTag key={i} code={i}></QuestTag>
42
46
  ))}
43
47
  </>
@@ -45,10 +49,10 @@ const CardAction = ({ gameId }: { gameId: number }) => {
45
49
  </TagsWrapper>
46
50
 
47
51
  <TagsWrapper>
48
- {!!preQuests.post.length && (
52
+ {!!prePostQuests.post.length && (
49
53
  <>
50
54
  <SpanText>{t('Unlocks')}</SpanText>
51
- {preQuests.post.map((i) => (
55
+ {prePostQuests.post.map((i) => (
52
56
  <QuestTag key={i} code={i}></QuestTag>
53
57
  ))}
54
58
  </>
@@ -26,7 +26,7 @@ const cache = new CellMeasurerCache({
26
26
  fixedWidth: true,
27
27
  })
28
28
 
29
- const questStateToQuestStatus = (
29
+ const questApiStateToQuestStatus = (
30
30
  state: QUEST_API_STATE | undefined
31
31
  ): QUEST_STATUS => {
32
32
  switch (state) {
@@ -47,7 +47,7 @@ const useQuestsRowRenderer = (quests: UnionQuest[]) => {
47
47
  const quest = quests[index]
48
48
  const { gameId } = quest
49
49
  const { code, name, desc, memo, memo2, pre } = quest.docQuest
50
- const questStatus = questStateToQuestStatus(quest.gameQuest?.api_state)
50
+ const questStatus = questApiStateToQuestStatus(quest.gameQuest?.api_state)
51
51
 
52
52
  return (
53
53
  <CellMeasurer
@@ -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
@@ -3,6 +3,10 @@ import { useTranslation } from 'react-i18next'
3
3
  import { observePluginStore, observePoiStore } from './store'
4
4
  import { name as PACKAGE_NAME } from '../../package.json'
5
5
  import { GameQuest, PoiQuestState, PoiState, QuestTab } from './types'
6
+ import { createGlobalState } from 'react-use'
7
+
8
+ export const activeQuestsSelector = (state: PoiState): PoiQuestState =>
9
+ state?.info?.quests?.activeQuests ?? {}
6
10
 
7
11
  export const useActiveQuest = () => {
8
12
  const [activeQuests, setActiveQuests] = useState<PoiQuestState>({})
@@ -17,20 +21,19 @@ export const useActiveQuest = () => {
17
21
  return activeQuests
18
22
  }
19
23
 
20
- export const activeQuestsSelector = (state: PoiState): PoiQuestState =>
21
- state?.info?.quests?.activeQuests ?? {}
22
-
23
24
  export const usePluginTranslation = () => {
24
25
  return useTranslation(PACKAGE_NAME)
25
26
  }
26
27
 
28
+ const useGlobalGameQuest = createGlobalState<GameQuest[]>([])
29
+
27
30
  export const useGameQuest = () => {
28
- const [quests, setQuests] = useState<GameQuest[]>([])
31
+ const [quests, setQuests] = useGlobalGameQuest()
29
32
  useEffect(() => {
30
33
  const listener = (quests: GameQuest[] | null) => setQuests(quests ?? [])
31
34
  // See reducer.ts
32
35
  return observePluginStore(listener, (i) => i?._?.questList)
33
- }, [])
36
+ }, [setQuests])
34
37
  return quests
35
38
  }
36
39
 
@@ -1,9 +1,12 @@
1
- import questCategory from '../build/questCategory.json'
1
+ import { QuestData } from '../build/kcanotifyGamedata'
2
+ import { KcwikiQuestData } from '../build/kcQuestsData'
2
3
  import newQuestData from '../build/kcQuestsData/quests-scn-new.json'
3
4
  import prePostQuest from '../build/prePostQuest.json'
5
+ import questCategory from '../build/questCategory.json'
6
+ import questCodeMap from '../build/questCodeMap.json'
4
7
  import { GameQuest, QUEST_API_STATE } from './poi/types'
5
8
 
6
- type DocQuest = {
9
+ export type DocQuest = {
7
10
  code: string
8
11
  name: string
9
12
  desc: string
@@ -35,6 +38,9 @@ export type UnionQuest = {
35
38
  docQuest: DocQuest
36
39
  }
37
40
 
41
+ export const getKcwikiQuestData = () => KcwikiQuestData
42
+ export const getKcanotifyQuestData = () => QuestData
43
+
38
44
  const dailyQuest = new Set(questCategory.dailyQuest)
39
45
  const weeklyQuest = new Set(questCategory.weeklyQuest)
40
46
  const monthlyQuest = new Set(questCategory.monthlyQuest)
@@ -197,13 +203,20 @@ export const isUnknownCategoryQuest = ({ code }: DocQuest) =>
197
203
  // Starts with unknown character
198
204
  /^[^ABCDEFG]/.test(code)
199
205
 
200
- export const getPrePost = (gameId: number) => {
206
+ export const getQuestPrePost = (gameId: number) => {
201
207
  if (!(gameId in prePostQuest)) {
202
208
  return { pre: [], post: [] }
203
209
  }
204
210
  return prePostQuest[String(gameId) as keyof typeof prePostQuest]
205
211
  }
206
212
 
213
+ export const getQuestIdByCode = (code: string) => {
214
+ if (code in questCodeMap) {
215
+ return questCodeMap[code as keyof typeof questCodeMap]
216
+ }
217
+ return null
218
+ }
219
+
207
220
  export enum QUEST_STATUS {
208
221
  LOCKED,
209
222
  DEFAULT,
@@ -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,29 @@
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'
3
+ import {
4
+ DocQuest,
5
+ getCategory,
6
+ getKcanotifyQuestData,
7
+ getQuestIdByCode,
8
+ getQuestPrePost,
9
+ QUEST_STATUS,
10
+ } from '../questHelper'
5
11
  import type { UnionQuest } from '../questHelper'
6
12
  import { useKcwikiData, checkIsKcwikiSupportedLanguages } from './kcwiki'
7
13
  import { useStore, useSyncWithGame } from './store'
8
14
 
9
15
  const DEFAULT_LANG = 'ja-JP'
10
16
 
11
- export const checkIsKcanotifySupportedLanguages = (
17
+ const checkIsKcanotifySupportedLanguages = (
12
18
  lang: string
13
- ): lang is keyof typeof QuestData => lang in QuestData
19
+ ): lang is keyof typeof kcaQuestData => {
20
+ const kcaQuestData = getKcanotifyQuestData()
21
+ return lang in kcaQuestData
22
+ }
14
23
 
15
24
  export const isSupportedLanguages = (
16
25
  lang: string
17
- ): lang is keyof typeof QuestData =>
26
+ ): lang is keyof ReturnType<typeof getKcanotifyQuestData> =>
18
27
  checkIsKcanotifySupportedLanguages(lang) ||
19
28
  checkIsKcwikiSupportedLanguages(lang)
20
29
 
@@ -28,13 +37,14 @@ export const useLanguage = () => {
28
37
  return lang
29
38
  }
30
39
 
31
- const useQuestMap = () => {
40
+ const useQuestMap = (): Record<string, DocQuest> => {
32
41
  const lang = useLanguage()
33
42
  const kcwikiData = useKcwikiData(lang)
34
43
  if (kcwikiData) {
35
44
  return kcwikiData
36
45
  }
37
- return QuestData[lang]
46
+ const kcaQuestData = getKcanotifyQuestData()
47
+ return kcaQuestData[lang]
38
48
  }
39
49
 
40
50
  export const useQuest = (): UnionQuest[] => {
@@ -76,6 +86,77 @@ export const useQuest = (): UnionQuest[] => {
76
86
  }
77
87
  }
78
88
 
89
+ export const useQuestByCode = (code: string): UnionQuest | null => {
90
+ const questMap = useQuestMap()
91
+ const gameId = getQuestIdByCode(code)
92
+ if (gameId && gameId in questMap) {
93
+ return {
94
+ gameId,
95
+ docQuest: questMap[String(gameId) as keyof typeof questMap],
96
+ }
97
+ }
98
+ return null
99
+ }
100
+
101
+ const useCompletedQuest = () => {
102
+ const completedQuest: Record<number, true> = {}
103
+ const gameQuest = useGameQuest()
104
+ const queue: number[] = gameQuest.map((quest) => quest.api_no)
105
+ while (queue.length) {
106
+ const gameId = queue.shift()!
107
+ if (gameId in completedQuest) {
108
+ continue
109
+ }
110
+ completedQuest[gameId] = true
111
+
112
+ const prePostQuests = getQuestPrePost(gameId)
113
+ prePostQuests.pre.forEach((nextCode) => {
114
+ const nextGameId = getQuestIdByCode(nextCode)
115
+ if (nextGameId) {
116
+ queue.push(nextGameId)
117
+ }
118
+ })
119
+ }
120
+ return completedQuest
121
+ }
122
+
123
+ const useLockedQuest = () => {
124
+ const lockedQuest: Record<number, true> = {}
125
+ const gameQuest = useGameQuest()
126
+ const queue: number[] = gameQuest.map((quest) => quest.api_no)
127
+ while (queue.length) {
128
+ const gameId = queue.shift()!
129
+ if (gameId in lockedQuest) {
130
+ continue
131
+ }
132
+ lockedQuest[gameId] = true
133
+ const prePostQuests = getQuestPrePost(gameId)
134
+ prePostQuests.post.forEach((nextCode) => {
135
+ const nextGameId = getQuestIdByCode(nextCode)
136
+ if (nextGameId) {
137
+ queue.push(nextGameId)
138
+ }
139
+ })
140
+ }
141
+ return lockedQuest
142
+ }
143
+
144
+ export const useQuestStatus = (gameId: number | null) => {
145
+ const completedQuest = useCompletedQuest()
146
+ const lockedQuest = useLockedQuest()
147
+
148
+ if (!gameId) {
149
+ return QUEST_STATUS.DEFAULT
150
+ }
151
+ if (gameId in completedQuest) {
152
+ return QUEST_STATUS.ALREADY_COMPLETED
153
+ }
154
+ if (gameId in lockedQuest) {
155
+ return QUEST_STATUS.LOCKED
156
+ }
157
+ return QUEST_STATUS.DEFAULT
158
+ }
159
+
79
160
  /**
80
161
  * @deprecated Not large card now
81
162
  */
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