From d382afe3d6aa95c353182a2b35bb0d859e978e80 Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Tue, 24 Sep 2024 16:20:07 -0400 Subject: [PATCH] Welcome to the lab quest (#137) ## Summary Adds welcome to the lab quest ## Implementation Notes * This provides the infrastructure needed for displaying collections with the puzzle list, and also specifically quests which are implemented through collections * Yeah, the duplicate request of the collection is rather silly and an easy place to introduce a race condition (if you go to load the puzzle list before the collection info is available), but the real fix for that is to rip out the unnecessary VueX usage entirely ## Testing * Loaded test version of quest on dev * Loaded other quest * Tested unsolved filter on both WTTL quest and general puzzle list ## Related Issues Resolves #136 --- src/components/BasePuzzleListPage.vue | 64 ++++++++++++++++----------- src/store/index.ts | 51 ++++++++++++++++++++- src/views/Quest.vue | 17 ++++--- src/views/QuestExplore.vue | 8 +++- 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/src/components/BasePuzzleListPage.vue b/src/components/BasePuzzleListPage.vue index 61be91b..1fb1a62 100644 --- a/src/components/BasePuzzleListPage.vue +++ b/src/components/BasePuzzleListPage.vue @@ -82,6 +82,10 @@ export default Vue.extend({ quest: { type: Object, default: null + }, + collection: { + type: String, + default: null } }, data() { @@ -99,7 +103,6 @@ export default Vue.extend({ this.availableFilters = [ { value: 'notcleared', text: 'Uncleared' }, ]; - if (!this.$route.query.switch) this.availableFilters.push({ value: 'single', text: 'Single State' }); } else { this.availableFilters = [ { value: 'challenge', text: 'Challenges' }, @@ -140,24 +143,27 @@ export default Vue.extend({ cleared: puzzleList.cleared ? puzzleList.cleared.some(cleared => cleared.nid === puzzle.id) : false, })); + // When we are displaying a collection, the API does not have a filter for uncleared puzzles, so we do that here + const filters = `${this.$route.query.filters}`.split(','); + const filteredPuzzles = filters.includes("notcleared") ? puzzlesWithCleared.filter((puz) => !puz.cleared) : puzzlesWithCleared; + if (this.$route.query.progression) { // Sort such that puzzle A which specifies its next puzzle is puzzle B is sorted before puzzle A // The first puzzle is the one that has no other puzzle pointing to it const orderedPuzzles: (PuzzleItem & {cleared: boolean})[] = []; - let puzzle = puzzlesWithCleared.find( - candidatePuzzle => !puzzlesWithCleared.some(otherPuzzle => otherPuzzle['next-puzzle'] === candidatePuzzle.id) + let puzzle = filteredPuzzles.find( + candidatePuzzle => !filteredPuzzles.some(otherPuzzle => otherPuzzle['next-puzzle'] === candidatePuzzle.id) ); while (puzzle) { orderedPuzzles.push(puzzle); const nextPuzzle = puzzle['next-puzzle']; - puzzle = puzzlesWithCleared.find(candidatePuzzle => candidatePuzzle.id === nextPuzzle); + puzzle = filteredPuzzles.find(candidatePuzzle => candidatePuzzle.id === nextPuzzle); } // Add any additional puzzles not part of the next puzzle "chain" - orderedPuzzles.push(...puzzlesWithCleared.filter(candidatePuzzle => !orderedPuzzles.includes(candidatePuzzle))); + orderedPuzzles.push(...filteredPuzzles.filter(candidatePuzzle => !orderedPuzzles.includes(candidatePuzzle))); return orderedPuzzles; } else { - console.log('unordered'); - return puzzlesWithCleared; + return filteredPuzzles; } } else { return []; @@ -167,7 +173,7 @@ export default Vue.extend({ return this.$store.state.user.lab_access; }, morePuzzlesAvailable(): boolean { - return this.numberOfPuzzles < parseInt(this.$store.state.puzzle_list.num_puzzles); + return this.$store.state.puzzle_list.puzzles.length < parseInt(this.$store.state.puzzle_list.num_puzzles); }, firstQuest(): boolean { return !!this.$route.query.firstQuest; @@ -176,33 +182,37 @@ export default Vue.extend({ methods: { async fetchNewPuzzles() { // Get filters from query, then convert to API's expected parameters - // Will change with Eterna-Next API const query = this.$route.query; - const filters = `${query.filters}`.split(','); - const queryParams = new URLSearchParams({type: 'puzzles'}); - - if (filters.includes("challenge") && !filters.includes("player")) queryParams.append('puzzle_type', 'Challenge'); - else if (filters.includes("player") && !filters.includes("challenge")) queryParams.append('puzzle_type', 'PlayerPuzzle'); - else if (query.progression) queryParams.append('puzzle_type', 'Progression'); - else queryParams.append('puzzle_type', 'AllChallengesPuzzle') - - if (filters.includes("single")) queryParams.append('single', 'checked'); - if (query.switch) queryParams.append('switch', 'checked'); + if (this.collection) { + await this.$store.dispatch(Action.GET_COLLECTION, {id: this.collection}); + } else { + const queryParams = new URLSearchParams({type: 'puzzles'}); + + const filters = `${query.filters}`.split(','); - if(filters.includes("notcleared")) queryParams.append('notcleared', 'true'); + if (filters.includes("challenge") && !filters.includes("player")) queryParams.append('puzzle_type', 'Challenge'); + else if (filters.includes("player") && !filters.includes("challenge")) queryParams.append('puzzle_type', 'PlayerPuzzle'); + else if (query.progression) queryParams.append('puzzle_type', 'Progression'); + else queryParams.append('puzzle_type', 'AllChallengesPuzzle') + + if (filters.includes("single")) queryParams.append('single', 'checked'); + if (query.switch) queryParams.append('switch', 'checked'); + + if (filters.includes("notcleared")) queryParams.append('notcleared', 'true'); - if (this.$store.state.uid) queryParams.append('uid', this.$store.state.uid); + if (this.$store.state.uid) queryParams.append('uid', this.$store.state.uid); - if (query.tags) queryParams.append('tags', query.tags as string); + if (query.tags) queryParams.append('tags', query.tags as string); - if (query.search) queryParams.append('search', query.search as string); - - queryParams.append('sort', query.sort ? query.sort as string : 'date'); + if (query.search) queryParams.append('search', query.search as string); + + queryParams.append('sort', query.sort ? query.sort as string : 'date'); - if (!query.progression) queryParams.append('size', this.numberOfPuzzles.toString(10)); + if (!query.progression) queryParams.append('size', this.numberOfPuzzles.toString(10)); - await this.$store.dispatch(Action.GET_PUZZLES, queryParams.toString()); + await this.$store.dispatch(Action.GET_PUZZLES, queryParams.toString()); + } }, async fetchMorePuzzles() { this.numberOfPuzzles += 9; diff --git a/src/store/index.ts b/src/store/index.ts index 89331d9..e408496 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -46,9 +46,9 @@ export interface ClearedPuzzle { export interface PuzzleList { puzzles: PuzzleItem[]; cleared: ClearedPuzzle[]; + num_puzzles: number; } - export interface Puzzle { title: string; created: string; @@ -88,6 +88,24 @@ export interface Puzzle { // "max-votes": 0 } +export interface Collection { + nid: string; + desc: string; + title: string; + userpicture: string; + username: string; + puzzles: string; + quest: string; + achievement: string; + created: string; + image: string; +} + +export interface CollectionList { + collections: Collection[]; + num_collections: string; +} + export interface LabCardData { affiliation: string; cover_image?: string; @@ -211,6 +229,8 @@ export const Action = { GET_QUEST_ACHIEVEMENT_ROADMAP: 'GET_QUEST_ACHIEVEMENT_ROADMAP', GET_LABS: 'GET_LABS', GET_LAB: 'GET_LAB', + GET_QUESTS: 'GET_QUESTS', + GET_COLLECTION: 'GET_COLLECTION', GET_PUZZLES: 'GET_PUZZLES', GET_PUZZLE: 'GET_PUZZLE', GET_PROFILE: 'GET_PROFILE' @@ -234,6 +254,7 @@ export default function createStore(http: AxiosInstance) { current_lab: null, puzzle_list: null, current_puzzle: <(Puzzle & { cleared: boolean }) | null>null, + quests: null, }, getters: { isLoading({isLoadingCount}) { @@ -284,6 +305,9 @@ export default function createStore(http: AxiosInstance) { }, setUserData(state, user) { state.user = user + }, + setQuests(state, quests) { + state.quests = quests } }, actions: { @@ -382,6 +406,31 @@ export default function createStore(http: AxiosInstance) { commit('popIsLoading'); } }, + async [Action.GET_QUESTS]({ commit }) { + commit('pushIsLoading'); + try{ + const { data } = (await http.get('/get/?type=collections&quest=true&sort=title&size=30')).data; + commit('setQuests', data); + } + finally{ + commit('popIsLoading'); + } + }, + async [Action.GET_COLLECTION]({ commit }, { id }: { id: string}) { + commit('pushIsLoading'); + try{ + const { data: collectionData } = (await http.get(`/get/?type=collection&nid=${id}`)).data; + const { data: clearedData } = (await http.get(`/get/?type=puzzle&nid=${collectionData.puzzles[0].id}`)).data; + commit('setPuzzles', { + puzzles: collectionData.puzzles, + num_puzzles: collectionData.puzzles.length, + cleared: clearedData?.cleared ?? [], + }); + } + finally{ + commit('popIsLoading'); + } + }, async [Action.GET_PUZZLES]({ commit }, queryString: string){ // commit('pushIsLoading'); try{ diff --git a/src/views/Quest.vue b/src/views/Quest.vue index 7e7c945..620a179 100644 --- a/src/views/Quest.vue +++ b/src/views/Quest.vue @@ -1,5 +1,5 @@