diff --git a/package.json b/package.json
index 9c374f32..c72b3a26 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "toshokan",
- "version": "0.2.47",
+ "version": "0.2.48",
"private": true,
"scripts": {
"build": "vite build",
@@ -20,7 +20,7 @@
"@tailwindcss/typography": "^0.5.2",
"@vuelidate/core": "^2.0.0-alpha.35",
"@vuelidate/validators": "^2.0.0-alpha.27",
- "@vueuse/core": "^8.0.1",
+ "@vueuse/core": "^8.1.1",
"apexcharts": "^3.33.2",
"axios": "^0.26.1",
"core-js": "^3.21.1",
@@ -43,7 +43,7 @@
"slugify": "^1.6.5",
"vue": "^3.2.31",
"vue-i18n": "^9.1.9",
- "vue-query": "^1.19.2",
+ "vue-query": "^1.19.3",
"vue-router": "^4.0.14",
"vue3-apexcharts": "^1.4.1",
"vuedraggable": "^4.1.0",
@@ -54,6 +54,7 @@
"@babel/eslint-parser": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@types/dedent": "^0.7.0",
+ "@types/gapi.client": "^1.0.5",
"@types/gapi.client.drive": "^3.0.13",
"@types/gapi.client.sheets": "^4.0.20201029",
"@types/google.visualization": "^0.0.68",
@@ -61,7 +62,7 @@
"@types/tailwindcss": "^3.0.9",
"@vitejs/plugin-vue": "^2.2.4",
"@vue/compiler-sfc": "^3.2.31",
- "autoprefixer": "^10.4.2",
+ "autoprefixer": "^10.4.4",
"babel-jest": "^27.5.1",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
@@ -75,9 +76,9 @@
"git-describe": "^4.1.0",
"husky": "^7.0.4",
"jest": "^27.5.1",
- "postcss": "^8.4.8",
+ "postcss": "^8.4.12",
"postcss-loader": "^6.2.1",
- "prettier": "^2.5.1",
+ "prettier": "^2.6.0",
"source-map": "^0.7.3",
"tailwindcss": "^3.0.23",
"vite": "^2.8.6",
diff --git a/src/components/GroupedStatistics.vue b/src/components/GroupedStatistics.vue
index 2166f944..6c16bc07 100644
--- a/src/components/GroupedStatistics.vue
+++ b/src/components/GroupedStatistics.vue
@@ -52,7 +52,8 @@ const groups = computed(() => [
monthlyReversed.value?.[0]?.totalSpent,
monthlyReversed.value?.[1]?.totalSpent
),
- currency: true
+ currency: true,
+ inverted: true
},
{
label: t('dashboard.stats.bought'),
@@ -94,7 +95,7 @@ const groups = computed(() => [
{{
n(
@@ -115,9 +116,9 @@ const groups = computed(() => [
v-if="(group.percentage?.percentage || 0) !== 0"
:class="[
'flex items-center text-xxs lg:text-xs font-semibold tracking-wide uppercase px-2 py-0.5 lg:py-1 rounded-full shrink-0',
- group.percentage?.increased
- ? 'bg-red-100 dark:bg-red-700/40 text-red-800 dark:text-red-200'
- : 'bg-green-100 dark:bg-green-600/40 text-green-800 dark:text-green-200'
+ group.percentage?.increased !== !!group.inverted
+ ? 'bg-green-100 dark:bg-green-600/40 text-green-800 dark:text-green-200'
+ : 'bg-red-100 dark:bg-red-700/40 text-red-800 dark:text-red-200'
]"
>
diff --git a/src/components/SignInWithGoogleButton.vue b/src/components/SignInWithGoogleButton.vue
index 027dce68..125183a4 100644
--- a/src/components/SignInWithGoogleButton.vue
+++ b/src/components/SignInWithGoogleButton.vue
@@ -16,8 +16,17 @@ import useDarkMode from '@/composables/useDarkMode'
import useTailwindTheme from '@/composables/useTailwindTheme'
import { useAuthStore } from '@/stores/auth'
-const props = defineProps({ prompt: { type: Boolean, default: true } })
-const { prompt } = toRefs(props)
+const props = defineProps({
+ prompt: {
+ type: Boolean,
+ default: true
+ },
+ type: {
+ type: String,
+ validator: (value) => ['icon', 'standard'].includes(value)
+ }
+})
+const { prompt, type } = toRefs(props)
const emit = defineEmits(['notification'])
@@ -64,7 +73,7 @@ function renderButton() {
size: 'large',
locale: locale.value,
text: 'continue_with',
- type: isMdBreakpoint.value ? 'standard' : 'icon'
+ type: type.value || (isMdBreakpoint.value ? 'standard' : 'icon')
})
}
}
diff --git a/src/data/Query.js b/src/data/Query.js
index 6c9b505e..616de7db 100644
--- a/src/data/Query.js
+++ b/src/data/Query.js
@@ -48,17 +48,19 @@ export default class Query {
const reasons = response.getReasons()
if (reasons.includes('timeout')) {
- reject(new Error(errorMessage), { reason: 'timeout' })
+ reject({ message: errorMessage, reason: 'timeout' })
} else if (reasons.includes('access_denied')) {
- reject(
- new Error(errorMessage, { code: 401, reason: 'access_denied' })
- )
+ reject({
+ message: errorMessage,
+ status: 401,
+ reason: 'access_denied'
+ })
} else {
const message = i18n.global.t('errors.badQuery', {
error: errorMessage
})
- reject(new Error(message, { reason: 'invalid_query' }))
+ reject({ message, reason: 'invalid_query' })
}
return
diff --git a/src/mutations/useBulkDeleteBookMutation.js b/src/mutations/useBulkDeleteBookMutation.js
index fc35cb8a..0e8df247 100644
--- a/src/mutations/useBulkDeleteBookMutation.js
+++ b/src/mutations/useBulkDeleteBookMutation.js
@@ -2,12 +2,17 @@ import { useMutation, useQueryClient } from 'vue-query'
import { useSheetStore } from '@/stores/sheet'
import bulkDeleteBooks from '@/services/sheet/bulkDeleteBooks'
+import { fetch } from '@/util/gapi'
export default function useBulkDeleteBookMutation() {
const sheetStore = useSheetStore()
const queryClient = useQueryClient()
- return useMutation((books) => bulkDeleteBooks(sheetStore.sheetId, books), {
+ async function mutate(books) {
+ return await fetch(bulkDeleteBooks(sheetStore.sheetId, books))
+ }
+
+ return useMutation(mutate, {
onSuccess(_, books) {
books.forEach((book) => {
queryClient.setQueryData(['book', book.id], null)
diff --git a/src/mutations/useBulkEditBookMutation.js b/src/mutations/useBulkEditBookMutation.js
index 9f43fa4e..2fe82dca 100644
--- a/src/mutations/useBulkEditBookMutation.js
+++ b/src/mutations/useBulkEditBookMutation.js
@@ -2,12 +2,17 @@ import { useMutation, useQueryClient } from 'vue-query'
import { useSheetStore } from '@/stores/sheet'
import bulkUpdateBooks from '@/services/sheet/bulkUpdateBooks'
+import { fetch } from '@/util/gapi'
export default function useBulkEditBookMutation() {
const sheetStore = useSheetStore()
const queryClient = useQueryClient()
- return useMutation((books) => bulkUpdateBooks(sheetStore.sheetId, books), {
+ async function mutate(books) {
+ return await fetch(bulkUpdateBooks(sheetStore.sheetId, books))
+ }
+
+ return useMutation(mutate, {
onSuccess(_, books) {
books.forEach((book) => {
queryClient.setQueryData(['book', book.id], book)
diff --git a/src/mutations/useCreateBookMutation.js b/src/mutations/useCreateBookMutation.js
index fcea5f06..9fbe6fbd 100644
--- a/src/mutations/useCreateBookMutation.js
+++ b/src/mutations/useCreateBookMutation.js
@@ -3,13 +3,18 @@ import { useMutation, useQueryClient } from 'vue-query'
import { useCollectionStore } from '@/stores/collection'
import { useSheetStore } from '@/stores/sheet'
import insertBook from '@/services/sheet/insertBook'
+import { fetch } from '@/util/gapi'
export default function useCreateBookMutation() {
const collectionStore = useCollectionStore()
const sheetStore = useSheetStore()
const queryClient = useQueryClient()
- return useMutation((book) => insertBook(sheetStore.sheetId, book), {
+ async function mutate(book) {
+ return await fetch(insertBook(sheetStore.sheetId, book))
+ }
+
+ return useMutation(mutate, {
onSuccess(_, book) {
const groups = collectionStore.filters.groups
diff --git a/src/mutations/useDeleteBookMutation.js b/src/mutations/useDeleteBookMutation.js
index e0c078e4..fbba5a60 100644
--- a/src/mutations/useDeleteBookMutation.js
+++ b/src/mutations/useDeleteBookMutation.js
@@ -2,12 +2,17 @@ import { useMutation, useQueryClient } from 'vue-query'
import { useSheetStore } from '@/stores/sheet'
import deleteBook from '@/services/sheet/deleteBook'
+import { fetch } from '@/util/gapi'
export default function useDeleteBookMutation() {
const sheetStore = useSheetStore()
const queryClient = useQueryClient()
- return useMutation((book) => deleteBook(sheetStore.sheetId, book), {
+ async function mutate(book) {
+ return await fetch(deleteBook(sheetStore.sheetId, book))
+ }
+
+ return useMutation(mutate, {
onSuccess(_, book) {
queryClient.setQueryData(['book', book.id], null)
diff --git a/src/mutations/useEditBookMutation.js b/src/mutations/useEditBookMutation.js
index f1437df9..b6ca4125 100644
--- a/src/mutations/useEditBookMutation.js
+++ b/src/mutations/useEditBookMutation.js
@@ -3,13 +3,18 @@ import { useMutation, useQueryClient } from 'vue-query'
import { useCollectionStore } from '@/stores/collection'
import { useSheetStore } from '@/stores/sheet'
import updateBook from '@/services/sheet/updateBook'
+import { fetch } from '@/util/gapi'
export default function useEditBookMutation() {
const collectionStore = useCollectionStore()
const sheetStore = useSheetStore()
const queryClient = useQueryClient()
- return useMutation((book) => updateBook(sheetStore.sheetId, book), {
+ async function mutate(book) {
+ return await fetch(updateBook(sheetStore.sheetId, book))
+ }
+
+ return useMutation(mutate, {
onSuccess(_, book) {
const groups = collectionStore.filters.groups
diff --git a/src/queries/useBookCollectionQuery.js b/src/queries/useBookCollectionQuery.js
index 8ecf27ad..9370b4a9 100644
--- a/src/queries/useBookCollectionQuery.js
+++ b/src/queries/useBookCollectionQuery.js
@@ -2,28 +2,18 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getBooksFromCollection from '@/services/sheet/getBooksFromCollection'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useBookCollectionQuery(book, { enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getBooksFromCollection(sheetId.value, book.value)
+ return await fetch(getBooksFromCollection(sheetId.value, book.value))
}
const bookTitle = computed(() => book.value?.titleParts?.title + ' #')
- return useQuery(['book-collection', bookTitle], fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery(['book-collection', bookTitle], fetcher, { enabled })
}
diff --git a/src/queries/useBookExistsQuery.js b/src/queries/useBookExistsQuery.js
index d937cf24..7635af42 100644
--- a/src/queries/useBookExistsQuery.js
+++ b/src/queries/useBookExistsQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getBookByCode from '@/services/sheet/getBookByCode'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useBookExistsQuery(isbn, { enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getBookByCode(sheetId.value, isbn.value)
+ return await fetch(getBookByCode(sheetId.value, isbn.value))
}
- return useQuery(['book-exists', isbn], fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery(['book-exists', isbn], fetcher, { enabled })
}
diff --git a/src/queries/useBookQuery.js b/src/queries/useBookQuery.js
index e65018f1..2c6eb16e 100644
--- a/src/queries/useBookQuery.js
+++ b/src/queries/useBookQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getBookById from '@/services/sheet/getBookById'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useBookQuery(bookId, { enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getBookById(sheetId.value, bookId.value)
+ return await fetch(getBookById(sheetId.value, bookId.value))
}
- return useQuery(['book', bookId], fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery(['book', bookId], fetcher, { enabled })
}
diff --git a/src/queries/useBookSearchQuery.js b/src/queries/useBookSearchQuery.js
index b5f75931..2a22ba2b 100644
--- a/src/queries/useBookSearchQuery.js
+++ b/src/queries/useBookSearchQuery.js
@@ -3,19 +3,18 @@ import { useQuery } from 'vue-query'
import { PropertyToColumn } from '@/model/Book'
import searchBooks from '@/services/sheet/searchBooks'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useBookSearchQuery(
{ query, sortBy, sortDirection, page },
{ enabled, keepPreviousData }
) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await searchBooks({
+ const promise = searchBooks({
sheetId: sheetId.value,
searchTerm: query.value,
sort: {
@@ -24,21 +23,13 @@ export default function useBookSearchQuery(
},
page: page.value
})
+
+ return await fetch(promise)
}
return useQuery(
['book-search', { query, sortBy, sortDirection, page }],
fetcher,
- {
- enabled,
- keepPreviousData,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- }
+ { enabled, keepPreviousData }
)
}
diff --git a/src/queries/useBooksQuery.js b/src/queries/useBooksQuery.js
index f3ca8125..2b37c6f2 100644
--- a/src/queries/useBooksQuery.js
+++ b/src/queries/useBooksQuery.js
@@ -3,39 +3,31 @@ import { useQuery } from 'vue-query'
import { PropertyToColumn } from '@/model/Book'
import getBooks from '@/services/sheet/getBooks'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useBooksQuery(
{ favorites, futureItems, groups, page, sortBy, sortDirection },
{ enabled }
) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getBooks(sheetId.value, page.value, {
+ const promise = getBooks(sheetId.value, page.value, {
favorites: favorites.value,
futureItems: futureItems.value,
groups: groups.value,
orderBy: PropertyToColumn[sortBy.value],
orderDirection: sortDirection.value
})
+
+ return await fetch(promise)
}
return useQuery(
['books', { favorites, futureItems, groups, page, sortBy, sortDirection }],
fetcher,
- {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- }
+ { enabled }
)
}
diff --git a/src/queries/useGroupsQuery.js b/src/queries/useGroupsQuery.js
index 4e900458..9f5c8700 100644
--- a/src/queries/useGroupsQuery.js
+++ b/src/queries/useGroupsQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getGroups from '@/services/sheet/getGroups'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useGroupsQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getGroups(sheetId.value)
+ return await fetch(getGroups(sheetId.value))
}
- return useQuery('groups', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('groups', fetcher, { enabled })
}
diff --git a/src/queries/useLastAddedQuery.js b/src/queries/useLastAddedQuery.js
index bb6acb5a..d9bcaa81 100644
--- a/src/queries/useLastAddedQuery.js
+++ b/src/queries/useLastAddedQuery.js
@@ -2,31 +2,23 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getBooks from '@/services/sheet/getBooks'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useLastAddedQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- const { books } = await getBooks(sheetId.value, 1, {
+ const promise = getBooks(sheetId.value, 1, {
limit: 6,
dontCount: true
})
+ const { books } = await fetch(promise)
+
return books
}
- return useQuery('last-added', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('last-added', fetcher, { enabled })
}
diff --git a/src/queries/useLatestReadingsQuery.js b/src/queries/useLatestReadingsQuery.js
index b742c59a..1cc29ade 100644
--- a/src/queries/useLatestReadingsQuery.js
+++ b/src/queries/useLatestReadingsQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getLatestReadings from '@/services/sheet/getLatestReadings'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useLatestReadingsQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getLatestReadings(sheetId.value, { limit: 6 })
+ return await fetch(getLatestReadings(sheetId.value, { limit: 6 }))
}
- return useQuery('latest-readings', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('latest-readings', fetcher, { enabled })
}
diff --git a/src/queries/useNextReadsQuery.js b/src/queries/useNextReadsQuery.js
index d7d13dea..cdc3d03e 100644
--- a/src/queries/useNextReadsQuery.js
+++ b/src/queries/useNextReadsQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getNextReads from '@/services/sheet/getNextReads'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useNextReadsQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getNextReads(sheetId.value)
+ return await fetch(getNextReads(sheetId.value))
}
- return useQuery('next-reads', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('next-reads', fetcher, { enabled })
}
diff --git a/src/queries/usePublishersQuery.js b/src/queries/usePublishersQuery.js
index 6661065e..185d5695 100644
--- a/src/queries/usePublishersQuery.js
+++ b/src/queries/usePublishersQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getPublishers from '@/services/sheet/getPublishers'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function usePublishersQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getPublishers(sheetId.value)
+ return await fetch(getPublishers(sheetId.value))
}
- return useQuery('publishers', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('publishers', fetcher, { enabled })
}
diff --git a/src/queries/useStatisticsQuery.js b/src/queries/useStatisticsQuery.js
index bdb59756..a3c31e74 100644
--- a/src/queries/useStatisticsQuery.js
+++ b/src/queries/useStatisticsQuery.js
@@ -2,28 +2,18 @@ import { computed, watch } from 'vue'
import { useQuery } from 'vue-query'
import getStatistics from '@/services/sheet/getStatistics'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useStatisticsQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getStatistics(sheetId.value)
+ return await fetch(getStatistics(sheetId.value))
}
- const query = useQuery('statistics', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ const query = useQuery('statistics', fetcher, { enabled })
watch(query.data, (newData) => {
sheetStore.updateIsEmpty(newData?.count === 0)
diff --git a/src/queries/useStoresQuery.js b/src/queries/useStoresQuery.js
index fb8aeecf..019de138 100644
--- a/src/queries/useStoresQuery.js
+++ b/src/queries/useStoresQuery.js
@@ -2,26 +2,16 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getStores from '@/services/sheet/getStores'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useStoresQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getStores(sheetId.value)
+ return await fetch(getStores(sheetId.value))
}
- return useQuery('stores', fetcher, {
- enabled,
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
- }
- })
+ return useQuery('stores', fetcher, { enabled })
}
diff --git a/src/queries/useTimeZoneQuery.js b/src/queries/useTimeZoneQuery.js
index 8f7dd14d..f472722e 100644
--- a/src/queries/useTimeZoneQuery.js
+++ b/src/queries/useTimeZoneQuery.js
@@ -2,16 +2,15 @@ import { computed } from 'vue'
import { useQuery } from 'vue-query'
import getTimeZone from '@/services/sheet/getTimeZone'
-import { useAuthStore } from '@/stores/auth'
import { useSheetStore } from '@/stores/sheet'
+import { fetch } from '@/util/gapi'
export default function useTimeZoneQuery({ enabled }) {
- const authStore = useAuthStore()
const sheetStore = useSheetStore()
const sheetId = computed(() => sheetStore.sheetId)
async function fetcher() {
- return await getTimeZone(sheetId.value)
+ return await fetch(getTimeZone(sheetId.value))
}
return useQuery('timezone', fetcher, {
@@ -21,13 +20,6 @@ export default function useTimeZoneQuery({ enabled }) {
offset: -3,
offsetStr: '-03:00',
timezoneOffset: 180
- },
- retry(_, error) {
- if (error.code === 401) {
- authStore.refreshToken()
- }
-
- return 2
}
})
}
diff --git a/src/services/sheet/bulkDeleteBooks.js b/src/services/sheet/bulkDeleteBooks.js
index b7ec23be..374e8215 100644
--- a/src/services/sheet/bulkDeleteBooks.js
+++ b/src/services/sheet/bulkDeleteBooks.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Bulk delete books in the sheet.
*
@@ -5,7 +7,7 @@
* @param {import('@/model/Book').default[]} books The books to delete
*/
export default async function bulkDeleteBooks(sheetId, books) {
- await window.gapi.client.sheets.spreadsheets.batchUpdate({
+ const thenable = window.gapi.client.sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetId,
resource: {
requests: books.map((book) => {
@@ -24,4 +26,6 @@ export default async function bulkDeleteBooks(sheetId, books) {
})
}
})
+
+ return await promisify(thenable)
}
diff --git a/src/services/sheet/bulkUpdateBooks.js b/src/services/sheet/bulkUpdateBooks.js
index 45bc2353..c92c32ae 100644
--- a/src/services/sheet/bulkUpdateBooks.js
+++ b/src/services/sheet/bulkUpdateBooks.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Bulk update books in the sheet.
*
@@ -5,7 +7,7 @@
* @param {import('@/model/Book').default[]} books The books to update.
*/
export default async function bulkUpdateBooks(sheetId, books) {
- await window.gapi.client.sheets.spreadsheets.values.batchUpdate({
+ const thenable = window.gapi.client.sheets.spreadsheets.values.batchUpdate({
spreadsheetId: sheetId,
resource: {
valueInputOption: 'USER_ENTERED',
@@ -15,4 +17,6 @@ export default async function bulkUpdateBooks(sheetId, books) {
}))
}
})
+
+ return await promisify(thenable)
}
diff --git a/src/services/sheet/deleteBook.js b/src/services/sheet/deleteBook.js
index 963d4ba9..d53b133b 100644
--- a/src/services/sheet/deleteBook.js
+++ b/src/services/sheet/deleteBook.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Delete a book in the sheet.
*
@@ -7,7 +9,7 @@
export default async function deleteBook(sheetId, book) {
const bookRow = parseInt(book.sheetLocation.substring(12))
- await window.gapi.client.sheets.spreadsheets.batchUpdate({
+ const thenable = window.gapi.client.sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetId,
resource: {
requests: [
@@ -24,4 +26,6 @@ export default async function deleteBook(sheetId, book) {
]
}
})
+
+ return await promisify(thenable)
}
diff --git a/src/services/sheet/findSheetId.js b/src/services/sheet/findSheetId.js
index eb57a2a3..2948fe92 100644
--- a/src/services/sheet/findSheetId.js
+++ b/src/services/sheet/findSheetId.js
@@ -1,4 +1,5 @@
import i18n from '@/i18n'
+import { promisify } from '@/util/gapi'
const SHEET_FILE_NAME = 'Toshokan'
const SHEET_MIME_TYPE = 'application/vnd.google-apps.spreadsheet'
@@ -16,13 +17,15 @@ export default async function findSheetId(useDevSheet = false) {
const sheetSuffix = isDevEnvironment && useDevSheet ? SHEET_DEV_SUFFIX : ''
const fileName = SHEET_FILE_NAME + sheetSuffix
- const response = await window.gapi.client.drive.files.list({
+ const thenable = window.gapi.client.drive.files.list({
q: `name='${fileName}' and mimeType='${SHEET_MIME_TYPE}'`,
orderBy: 'starred',
fields:
'files(capabilities/canEdit,id,modifiedTime,name,ownedByMe,owners(displayName,emailAddress,photoLink),starred)'
})
+ const response = await promisify(thenable)
+
if (response.result.files.length === 0) {
throw new Error(i18n.global.t('sheet.notFound'))
}
diff --git a/src/services/sheet/getSheetData.js b/src/services/sheet/getSheetData.js
index 18808217..11cb5a91 100644
--- a/src/services/sheet/getSheetData.js
+++ b/src/services/sheet/getSheetData.js
@@ -1,3 +1,8 @@
+import { promisify } from '@/util/gapi'
+
+import getStatistics from './getStatistics'
+import getTimeZone from './getTimeZone'
+
/**
* Get the sheet timezone data and statistics.
*
@@ -5,83 +10,8 @@
* @returns The sheet data and statistics
*/
export default async function getSheetData(sheetId) {
- const response = await window.gapi.client.sheets.spreadsheets.values.batchGet(
- {
- spreadsheetId: sheetId,
- ranges: [
- 'TotalItems',
- 'Expense',
- 'Reading',
- 'MonthlyStatistics',
- 'AuthorRank',
- 'SeriesRank',
- 'PublisherRank',
- 'Currency'
- ]
- }
- )
-
- const stats = {
- count: parseInt(response.result.valueRanges[0].values[0][0]),
- money: {
- totalSpentLabel: parseFloat(response.result.valueRanges[1].values[0][1]),
- totalSpentPaid: parseFloat(response.result.valueRanges[1].values[1][1]),
- saved: parseFloat(response.result.valueRanges[1].values[2][1]),
- percent: parseFloat(response.result.valueRanges[1].values[3][1]),
- currency: response.result.valueRanges[7].values[0][0]
- },
- status: {
- read: parseInt(response.result.valueRanges[2].values[0][1], 10),
- unread: parseInt(response.result.valueRanges[2].values[1][1], 10),
- percent: parseFloat(response.result.valueRanges[2].values[2][1], 10)
- },
- monthly: (response.result.valueRanges[3].values || [])
- .slice(0, 10)
- .reverse()
- .filter((row) => row[0].length > 0)
- .map((row) => ({
- month: new Date(`${row[0]}-02`),
- totalSpent: parseFloat(row[1]),
- count: parseInt(row[2], 10),
- read: parseInt(row[3], 10)
- })),
- authors: (response.result.valueRanges[4].values || [])
- .slice(0, 10)
- .map((row) => ({ name: row[0], count: parseInt(row[1], 10) })),
- series: (response.result.valueRanges[5].values || [])
- .slice(0, 10)
- .map((row) => ({ name: row[0], count: parseInt(row[1], 10) })),
- publishers: (response.result.valueRanges[6].values || [])
- .slice(0, 10)
- .map((row) => ({ name: row[0], count: parseInt(row[1], 10) }))
- }
-
- const propertiesResponse = await window.gapi.client.sheets.spreadsheets.get({
- spreadsheetId: sheetId,
- fields: 'properties.timeZone'
- })
-
- const timeZoneName = propertiesResponse.result.properties.timeZone
- const offset = parseInt(
- new Date()
- .toLocaleString('en-US', {
- timeZone: timeZoneName,
- timeZoneName: 'short',
- hour12: false
- })
- .replace(/.*GMT/, '')
- )
-
- const timeZone = {
- name: timeZoneName,
- offset,
- offsetStr:
- offset.toLocaleString('en-US', {
- minimumIntegerDigits: 2,
- signDisplay: 'always'
- }) + ':00',
- timezoneOffset: -offset * 60
- }
+ const stats = await getStatistics(sheetId)
+ const timeZone = await getTimeZone(sheetId)
return { stats, timeZone }
}
diff --git a/src/services/sheet/getStatistics.js b/src/services/sheet/getStatistics.js
index a54cb0a1..65d2db1a 100644
--- a/src/services/sheet/getStatistics.js
+++ b/src/services/sheet/getStatistics.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Get the sheet statistics.
*
@@ -5,21 +7,21 @@
* @returns The sheet statistics
*/
export default async function getStatistics(sheetId) {
- const response = await window.gapi.client.sheets.spreadsheets.values.batchGet(
- {
- spreadsheetId: sheetId,
- ranges: [
- 'TotalItems',
- 'Expense',
- 'Reading',
- 'MonthlyStatistics',
- 'AuthorRank',
- 'SeriesRank',
- 'PublisherRank',
- 'Currency'
- ]
- }
- )
+ const thenable = window.gapi.client.sheets.spreadsheets.values.batchGet({
+ spreadsheetId: sheetId,
+ ranges: [
+ 'TotalItems',
+ 'Expense',
+ 'Reading',
+ 'MonthlyStatistics',
+ 'AuthorRank',
+ 'SeriesRank',
+ 'PublisherRank',
+ 'Currency'
+ ]
+ })
+
+ const response = await promisify(thenable)
return {
count: parseInt(response.result.valueRanges[0].values[0][0]),
diff --git a/src/services/sheet/getTimeZone.js b/src/services/sheet/getTimeZone.js
index ffe2f89b..cd37b983 100644
--- a/src/services/sheet/getTimeZone.js
+++ b/src/services/sheet/getTimeZone.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Get the sheet time zone data.
*
@@ -5,12 +7,14 @@
* @returns The sheet time zone
*/
export default async function getTimeZone(sheetId) {
- const propertiesResponse = await window.gapi.client.sheets.spreadsheets.get({
+ const thenable = window.gapi.client.sheets.spreadsheets.get({
spreadsheetId: sheetId,
fields: 'properties.timeZone'
})
- const timeZoneName = propertiesResponse.result.properties.timeZone
+ const response = await promisify(thenable)
+
+ const timeZoneName = response.result.properties.timeZone
const offset = parseInt(
new Date()
.toLocaleString('en-US', {
diff --git a/src/services/sheet/insertBook.js b/src/services/sheet/insertBook.js
index c744d65f..8ad6b90f 100644
--- a/src/services/sheet/insertBook.js
+++ b/src/services/sheet/insertBook.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Insert a book in the sheet.
*
@@ -8,7 +10,7 @@
export default async function insertBook(sheetId, book) {
const bookFormatted = book.toArray()
- await window.gapi.client.sheets.spreadsheets.values.append({
+ const thenable = window.gapi.client.sheets.spreadsheets.values.append({
spreadsheetId: sheetId,
range: 'Collection!B5',
valueInputOption: 'USER_ENTERED',
@@ -16,5 +18,7 @@ export default async function insertBook(sheetId, book) {
values: [bookFormatted]
})
+ await promisify(thenable)
+
return bookFormatted[1]
}
diff --git a/src/services/sheet/updateBook.js b/src/services/sheet/updateBook.js
index e74dfa4e..7d090e97 100644
--- a/src/services/sheet/updateBook.js
+++ b/src/services/sheet/updateBook.js
@@ -1,3 +1,5 @@
+import { promisify } from '@/util/gapi'
+
/**
* Update a book in the sheet.
*
@@ -7,10 +9,12 @@
export default async function updateBook(sheetId, book) {
const bookFormatted = book.toArray()
- await window.gapi.client.sheets.spreadsheets.values.update({
+ const thenable = window.gapi.client.sheets.spreadsheets.values.update({
spreadsheetId: sheetId,
range: book.sheetLocation,
valueInputOption: 'USER_ENTERED',
values: [bookFormatted]
})
+
+ await promisify(thenable)
}
diff --git a/src/stores/auth.js b/src/stores/auth.js
index 1eba084a..268969c1 100644
--- a/src/stores/auth.js
+++ b/src/stores/auth.js
@@ -1,13 +1,7 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
-import i18n from '@/i18n'
-
import jwtDecode from 'jwt-decode'
-const { t } = i18n.global
-
-const GoogleApiErrors = {
- COOKIES_DISABLED: 'Cookies are not enabled in current environment.'
-}
+import { loadApiAsync, promisify, whenAvailable } from '@/util/gapi'
const DISCOVERY_DOCS = [
'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest',
@@ -19,17 +13,6 @@ const SCOPES = [
'https://www.googleapis.com/auth/drive.metadata.readonly'
]
-async function whenAvailable(name, intervalMs = 100) {
- return new Promise((resolve) => {
- const interval = setInterval(() => {
- if (window[name]) {
- clearInterval(interval)
- resolve()
- }
- }, intervalMs)
- })
-}
-
export const useAuthStore = defineStore('auth', {
state: () => ({
accessToken: null,
@@ -73,6 +56,15 @@ export const useAuthStore = defineStore('auth', {
this.clear()
},
+ /**
+ * Checks if the token is expired.
+ *
+ * @returns {boolean} If the token is expired.
+ */
+ expired() {
+ return new Date().getTime >= this.expiresIn
+ },
+
/**
* Try to grant the missing permissions.
*/
@@ -100,26 +92,13 @@ export const useAuthStore = defineStore('auth', {
*/
async initGoogleApiClient() {
await whenAvailable('gapi')
+ await loadApiAsync('client')
- return new Promise((resolve, reject) => {
- window.gapi.load('client', () => {
- window.gapi.client.init({ discoveryDocs: DISCOVERY_DOCS }).then(
- () => resolve(),
- (error) => {
- const errorMessage =
- error.details === GoogleApiErrors.COOKIES_DISABLED
- ? t('errors.cookiesDisabled')
- : t('errors.authStartedFailed')
-
- reject(
- new Error(errorMessage, {
- cause: { ...error, refresh: true }
- })
- )
- }
- )
- })
+ const initThenable = window.gapi.client.init({
+ discoveryDocs: DISCOVERY_DOCS
})
+
+ await promisify(initThenable)
},
/**
@@ -153,7 +132,7 @@ export const useAuthStore = defineStore('auth', {
if (tokenResponse?.access_token) {
this.$patch({
accessToken: tokenResponse.access_token,
- expiresIn: new Date().getTime() + tokenResponse.expires_in,
+ expiresIn: new Date().getTime() + tokenResponse.expires_in * 1000,
hasGrantedScopes:
window.google.accounts.oauth2.hasGrantedAllScopes(
tokenResponse,
@@ -172,17 +151,47 @@ export const useAuthStore = defineStore('auth', {
* Refresh the token if needed.
*/
refreshToken() {
- if (new Date().getTime() > this.expiresIn && !this.refreshing) {
+ if (this.expired() && !this.refreshing) {
this.refreshing = true
this.grantPermissions()
}
},
+ /**
+ * Refresh the token if needed, asynchronously.
+ *
+ * If the refreshing is already happening, the Promise
+ * will be blocked to wait until it gets finished.
+ *
+ * @returns {Promise} An promise that will resolve when refreshed.
+ */
+ async refreshTokenAsync() {
+ return new Promise((resolve) => {
+ if (!this.expired()) {
+ resolve()
+ }
+
+ const removeWatcher = this.$subscribe(
+ (_, state) => {
+ if (state.refreshing == false) {
+ removeWatcher()
+ resolve()
+ }
+ },
+ { detached: true }
+ )
+
+ if (!this.refreshing) {
+ this.refreshing = true
+ this.grantPermissions()
+ }
+ })
+ },
+
/**
* Attempt to sign out the user.
*/
signOut() {
- // window.google.accounts.oauth2.revoke(this.accessToken)
window.google.accounts.id.disableAutoSelect()
this.clear()
diff --git a/src/util/gapi.js b/src/util/gapi.js
new file mode 100644
index 00000000..a4ada4c9
--- /dev/null
+++ b/src/util/gapi.js
@@ -0,0 +1,81 @@
+import { useAuthStore } from '@/stores/auth'
+
+/**
+ * Google GAPI returns a Thenable instead of ES6 Promises.
+ *
+ * This function converts a Thenable to a {@link Promise}.
+ *
+ * @param {gapi.client.Request} thenable The original thenable.
+ * @returns {Promise>} An ES6 Promise.
+ * @template T
+ */
+export function promisify(thenable) {
+ return new Promise((resolve, reject) => {
+ thenable.then(
+ (response) => resolve(response),
+ (reason) =>
+ reject({
+ message: reason.result.error.message,
+ status: reason.status,
+ reason
+ })
+ )
+ })
+}
+
+/**
+ * An ES6 {@link Promise} version of {@link gapi.load}.
+ *
+ * @param {string} api The API to load.
+ * @returns {Promise}
+ */
+export function loadApiAsync(api) {
+ return new Promise((resolve) => {
+ window.gapi.load(api, () => resolve())
+ })
+}
+
+/**
+ * Fetch a {@link Promise} and refresh the token if it fails
+ * and it's expired (i.e. returns 401).
+ *
+ * @param {Promise} promise The original promise.
+ * @returns {Promise} A new promise
+ * @template T
+ */
+export async function fetch(promise) {
+ const authStore = useAuthStore()
+
+ try {
+ if (authStore.expired()) {
+ await authStore.refreshTokenAsync()
+ }
+
+ return await promise
+ } catch (error) {
+ if (error.status === 401) {
+ await authStore.refreshTokenAsync()
+ return await promise
+ }
+
+ throw error
+ }
+}
+
+/**
+ * Wait until a key gets available in {@link Window}.
+ *
+ * @param {string} name The key in {@link Window}
+ * @param {number} intervalMs The interval time
+ * @returns {Promise} An promise that will resolve when available.
+ */
+export async function whenAvailable(name, intervalMs = 100) {
+ return new Promise((resolve) => {
+ const interval = setInterval(() => {
+ if (window[name]) {
+ clearInterval(interval)
+ resolve()
+ }
+ }, intervalMs)
+ })
+}
diff --git a/src/views/SignIn.vue b/src/views/SignIn.vue
index a4d8edca..0a7e77a2 100644
--- a/src/views/SignIn.vue
+++ b/src/views/SignIn.vue
@@ -85,7 +85,10 @@ function handleNotification(notification) {
-
+
diff --git a/yarn.lock b/yarn.lock
index b16cfb24..b8d27552 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1597,7 +1597,7 @@
dependencies:
"@maxim_mazurok/gapi.client.sheets" latest
-"@types/gapi.client@*":
+"@types/gapi.client@*", "@types/gapi.client@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/gapi.client/-/gapi.client-1.0.5.tgz#a6eb97e664fe51656c5b52258bd0afef28c76308"
integrity sha512-OTpbBMuzfC4lkvaomxqskI/iWRGW3zOZbDXZLNSyiuswTiSSGgILRLkg0POuZ4EgzEdaYaTlXpnXiCp07ri/Yw==
@@ -1875,24 +1875,24 @@
dependencies:
vue-demi "^0.12.0"
-"@vueuse/core@^8.0.1":
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-8.0.1.tgz#113e3704ce1e11ea204cc3586edb255753260f0c"
- integrity sha512-LQ5pyxALWMZikNWzW2CTs/RfTyT0O3oYDl7jJhfckY0q/dLe2SCy5vX4+f6c2LFjG9M+sXRugne+W4t1I/wXUA==
+"@vueuse/core@^8.1.1":
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-8.1.1.tgz#09dd6def4d5efad81a1a665155fb1959e03d7246"
+ integrity sha512-NmpizOcQXW0OHV0qa6YwFtw5hQBe0RxMuxQoIIbCa6jRT16MTweFPecIM6VgA1e5vZJtAO7p//TgLSpzi3joZQ==
dependencies:
- "@vueuse/metadata" "8.0.1"
- "@vueuse/shared" "8.0.1"
+ "@vueuse/metadata" "8.1.1"
+ "@vueuse/shared" "8.1.1"
vue-demi "*"
-"@vueuse/metadata@8.0.1":
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-8.0.1.tgz#672b987619d6dc161501b06cada0798c90b827a1"
- integrity sha512-+Y2TSsgliQMzqcRMcuX28XHrxojrZYGiBKzW05/mkPmlYGlUSOauF9pvyyslH0V4HfzCsg3CDKJhCnBLWKqN0w==
+"@vueuse/metadata@8.1.1":
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-8.1.1.tgz#6e6ba14cb3225ce4c252e5140da1fe572b1ef2e9"
+ integrity sha512-TSmA3UaSmBcV8pJ7fnHAL7NjSE1RQZlpJklg6YysFxwxDb0MZlo+Q8j9U1hLBZT1fnAjwZMaoxilanHN8ZwugQ==
-"@vueuse/shared@8.0.1":
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-8.0.1.tgz#1ca157241b4e702dac63e0786ce459a865658ed8"
- integrity sha512-PZf84+DHWUnIKNhvIxL335uWjjfIeEhYjRFilDqEWB/t4yHfNbZxDpT+4OgiLtgdCcGG1JKryNI7nkY9TyEu6w==
+"@vueuse/shared@8.1.1":
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-8.1.1.tgz#beb84b4fd7cc09577d9d30dbedfdabf47740e9d0"
+ integrity sha512-lWRcK8W9+q1t1XMxW9JIljXFQLIViInTOEF7wUHcISQNAYwHtHgr0z+U5SRM8tA4eJd/dVs/2BV1/z2DG7aKEg==
dependencies:
vue-demi "*"
@@ -2078,14 +2078,14 @@ at-least-node@^1.0.0:
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
-autoprefixer@^10.4.2:
- version "10.4.2"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
- integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
+autoprefixer@^10.4.4:
+ version "10.4.4"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e"
+ integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==
dependencies:
- browserslist "^4.19.1"
- caniuse-lite "^1.0.30001297"
- fraction.js "^4.1.2"
+ browserslist "^4.20.2"
+ caniuse-lite "^1.0.30001317"
+ fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
@@ -2249,6 +2249,17 @@ browserslist@^4.17.5, browserslist@^4.19.1:
node-releases "^2.0.1"
picocolors "^1.0.0"
+browserslist@^4.20.2:
+ version "4.20.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
+ integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
+ dependencies:
+ caniuse-lite "^1.0.30001317"
+ electron-to-chromium "^1.4.84"
+ escalade "^3.1.1"
+ node-releases "^2.0.2"
+ picocolors "^1.0.0"
+
bser@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
@@ -2294,11 +2305,16 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297:
+caniuse-lite@^1.0.30001286:
version "1.0.30001300"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz#11ab6c57d3eb6f964cba950401fd00a146786468"
integrity sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==
+caniuse-lite@^1.0.30001317:
+ version "1.0.30001317"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz#0548fb28fd5bc259a70b8c1ffdbe598037666a1b"
+ integrity sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==
+
chalk@^2.0.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -2641,6 +2657,11 @@ electron-to-chromium@^1.4.17:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.48.tgz#1948b5227aa0ca1ed690945eae1adbe9e7904575"
integrity sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA==
+electron-to-chromium@^1.4.84:
+ version "1.4.86"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz#90fe4a9787f48d6522957213408e08a8126b2ebc"
+ integrity sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==
+
emittery@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
@@ -3254,10 +3275,10 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
-fraction.js@^4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8"
- integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==
+fraction.js@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
+ integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fs-extra@^9.0.1:
version "9.1.0"
@@ -4633,6 +4654,11 @@ node-releases@^2.0.1:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
+node-releases@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
+ integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
+
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@@ -4924,6 +4950,15 @@ postcss@^8.1.10:
nanoid "^3.1.23"
source-map-js "^0.6.2"
+postcss@^8.4.12:
+ version "8.4.12"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
+ integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
+ dependencies:
+ nanoid "^3.3.1"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
postcss@^8.4.6:
version "8.4.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
@@ -4933,15 +4968,6 @@ postcss@^8.4.6:
picocolors "^1.0.0"
source-map-js "^1.0.2"
-postcss@^8.4.8:
- version "8.4.8"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
- integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
- dependencies:
- nanoid "^3.3.1"
- picocolors "^1.0.0"
- source-map-js "^1.0.2"
-
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -4959,10 +4985,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@^2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
- integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
+prettier@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4"
+ integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==
pretty-bytes@^5.3.0, pretty-bytes@^5.6.0:
version "5.6.0"
@@ -5884,10 +5910,10 @@ vue-i18n@^9.1.9:
"@intlify/vue-devtools" "9.1.9"
"@vue/devtools-api" "^6.0.0-beta.7"
-vue-query@^1.19.2:
- version "1.19.2"
- resolved "https://registry.yarnpkg.com/vue-query/-/vue-query-1.19.2.tgz#fe169399e1668208981ce5232e13dd53a7e0b970"
- integrity sha512-aEi1ICUISAeyMPPdJIABVat+RkaL9yK1QRMBC47p7wYhvx1xl5QVVsyP7KgJje0JTId2ak1XnrerHtLCAZvT1Q==
+vue-query@^1.19.3:
+ version "1.19.3"
+ resolved "https://registry.yarnpkg.com/vue-query/-/vue-query-1.19.3.tgz#ce0d0ec8a86c129ecb60cd2eaa213650ab335e9a"
+ integrity sha512-aQnVU+shMC5LlK2NWrx+zq/w1kOvmHix/zo4Ok5xmJgxOagm/TPSfrMVqDqcIZfQx+dthimXQaHCuu6p8zkXag==
dependencies:
match-sorter "^6.3.1"
react-query "^3.34.9"