Skip to content

Commit

Permalink
Merge pull request #4184 from traPtitech/fix/setting_browser_cache
Browse files Browse the repository at this point in the history
設定画面のブラウザタブにあるキャッシュ項目の改修
  • Loading branch information
reiroop authored Sep 28, 2024
2 parents 5c6a0ce + fd73488 commit d55e4bc
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 136 deletions.
2 changes: 1 addition & 1 deletion src/components/Modal/Common/ModalFrame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { useModalStore } from '/@/store/ui/modal'
withDefaults(
defineProps<{
iconMdi?: boolean
iconName: string
iconName?: string
title: string
subtitle?: string
returnButton?: boolean
Expand Down
19 changes: 14 additions & 5 deletions src/components/Modal/Common/ModalHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
<modal-return-button v-if="returnButton" :class="$style.returnButton" />
<div :class="$style.content">
<h1 :class="$style.title">
<a-icon :class="$style.icon" :name="iconName" :mdi="iconMdi" />{{
title
}}
<a-icon
v-if="iconName"
:class="$style.icon"
:name="iconName"
:mdi="iconMdi"
/>
{{ title }}
</h1>
<h2 :class="$style.subtitle"><slot name="subtitle" /></h2>
<h2 :class="$style.subtitle" :data-has-icon="$boolAttr(!iconName)">
<slot name="subtitle" />
</h2>
</div>
</div>
</template>
Expand All @@ -19,7 +25,7 @@ import ModalReturnButton from './ModalReturnButton.vue'
withDefaults(
defineProps<{
iconMdi?: boolean
iconName: string
iconName?: string
title: string
subtitle?: string
returnButton?: boolean
Expand Down Expand Up @@ -69,6 +75,9 @@ withDefaults(
weight: 500;
size: 0.875rem;
}
&[data-has-icon] {
padding-left: 0;
}
}
.icon {
margin-right: 16px;
Expand Down
5 changes: 3 additions & 2 deletions src/components/Modal/GroupCreateModal/GroupCreateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
<form-checkbox
v-model="addMember"
:class="[$style.item, $style.memberCheckbox]"
label="自分自身をメンバーに追加する"
/>
>
自分自身をメンバーに追加する
</form-checkbox>
<div :class="$style.createButtonWrapper">
<form-button label="作成" @click="create" />
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Modal/ModalContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import GroupCreateModal from './GroupCreateModal/GroupCreateModal.vue'
import GroupMemberEditModal from './GroupMemberEditModal/GroupMemberEditModal.vue'
import GroupAdminAddModal from './GroupAdminAddModal/GroupAdminAddModal.vue'
import GroupMemberAddModal from './GroupMemberAddModal/GroupMemberAddModal.vue'
import SettingsCacheClearModal from './SettingsCacheClearModal/SettingsCacheClearModal.vue'
import SettingsThemeEditModal from './SettingsThemeEditModal/SettingsThemeEditModal.vue'
import type { ModalStateType } from '/@/store/ui/modal/states'
Expand All @@ -83,6 +84,7 @@ const components: Record<ModalStateType, Component> = {
'group-member-edit': GroupMemberEditModal,
'group-admin-add': GroupAdminAddModal,
'group-member-add': GroupMemberAddModal,
'settings-cache-clear': SettingsCacheClearModal,
'settings-theme-edit': SettingsThemeEditModal
}
const component = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<template>
<modal-frame
title="キャッシュの削除"
subtitle="キャッシュを削除する項目を選んで下さい。"
>
<div :class="$style.content">
<div :class="$style.checkboxContainer">
<form-checkbox
v-for="name in cacheCategories"
:key="name"
v-model="cacheCategoryToIsSelected[name]"
:class="$style.checkbox"
>
<div :class="$style.label">
{{ cacheLabel(name) }}
<!-- TODO: キャッシュサイズを表示する -->
</div>
</form-checkbox>
</div>
<div :class="$style.buttonContainer">
<form-button label="キャンセル" type="tertiary" @click="clearModal" />
<form-button
label="削除する"
type="secondary"
:disabled="!anyCacheSelected"
is-danger
@click="clearCache"
/>
</div>
</div>
</modal-frame>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useToastStore } from '/@/store/ui/toast'
import { wait } from '/@/lib/basic/timer'
import ModalFrame from '../Common/ModalFrame.vue'
import FormButton from '/@/components/UI/FormButton.vue'
import { useModalStore } from '/@/store/ui/modal'
import FormCheckbox from '/@/components/UI/FormCheckbox.vue'
declare global {
interface StorageEstimate {
usageDetails: Record<CacheCategory, number>
}
}
const confirmClear = () => window.confirm('本当に削除しますか?')
/* CacheStorageのnameはsw.jsを参照 */
const clearCacheStorage = (cacheCategory: string) =>
window.caches.delete(cacheCategory)
const { addSuccessToast } = useToastStore()
const showToast = (extraMessage?: string) => {
addSuccessToast(
`削除に成功しました${extraMessage ? `: ${extraMessage}` : ''}`
)
}
const cacheCategories = [
'traQ_S-precache',
'files-cache',
'thumbnail-cache'
] as const
type CacheCategory = (typeof cacheCategories)[number]
const cacheCategoryToIsSelected = ref(
Object.fromEntries(cacheCategories.map(name => [name, false]))
)
const anyCacheSelected = computed(() => {
return Object.values(cacheCategoryToIsSelected).includes(true)
})
const cacheNames = async (category: CacheCategory) => {
if (!(category === 'traQ_S-precache')) {
return [category]
}
const allNames = await window.caches.keys()
return allNames.filter(name => name.startsWith(category))
}
const cacheLabel = (cacheCategory: CacheCategory) => {
switch (cacheCategory) {
case 'traQ_S-precache':
return 'traQ本体'
case 'files-cache':
return 'ファイルの本体一覧'
case 'thumbnail-cache':
return 'ファイルのサムネイル一覧'
default:
throw new Error(
`Unknown cache name: ${cacheCategory satisfies CacheCategory}`
)
}
}
const { clearModal } = useModalStore()
const isClearingCache = ref(false)
const clearMainCachePromises = async () => {
const names = await cacheNames('traQ_S-precache')
return names.map(name => {
clearCacheStorage(name)
})
}
const clearCache = async () => {
if (isClearingCache.value || !confirmClear()) return
isClearingCache.value = true
const promises = []
if (cacheCategoryToIsSelected.value['traQ_S-precache']) {
promises.push(clearMainCachePromises())
}
if (cacheCategoryToIsSelected.value['files-cache']) {
promises.push(clearCacheStorage('files-cache'))
}
if (cacheCategoryToIsSelected.value['thumbnail-cache']) {
promises.push(clearCacheStorage('thumbnail-cache'))
}
await Promise.all(promises.flat())
if (!cacheCategoryToIsSelected.value['traQ_S-precache']) {
isClearingCache.value = false
clearModal()
showToast()
return
}
const registration = await navigator.serviceWorker?.getRegistration()
if (!registration) {
isClearingCache.value = false
clearModal()
showToast()
return
}
registration.unregister()
isClearingCache.value = false
showToast('1秒後にリロードします')
await wait(1000)
clearModal()
window.location.reload()
}
</script>

<style lang="scss" module>
.content {
display: flex;
flex-direction: column;
gap: 32px;
}
.checkboxContainer {
display: flex;
flex-direction: column;
gap: 8px;
}
.checkbox {
@include color-ui-secondary;
&:has(:checked) {
@include color-ui-primary;
}
display: flex;
justify-content: space-between;
gap: 4px;
padding: 8px;
align-items: center;
cursor: pointer;
border: solid 2px transparent;
border-radius: 4px;
&:focus-within {
border-color: $theme-accent-focus-default;
}
}
.label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 8px 16px;
}
.buttonContainer {
display: flex;
justify-content: flex-end;
gap: 16px;
}
</style>
90 changes: 13 additions & 77 deletions src/components/Settings/BrowserTab/CacheManager.vue
Original file line number Diff line number Diff line change
@@ -1,120 +1,56 @@
<template>
<div>
<h3 :class="$style.header">キャッシュの削除</h3>
<div :class="$style.content">
<h3 :class="$style.header">キャッシュ</h3>
<div>
<p v-if="cacheData && cacheData.usage" :class="$style.usage">
使用量:
<template v-if="cacheData.usageDetails">
<span v-for="(usage, key) in cacheData.usageDetails" :key="key">
{{ prettifyFileSize(usage) }} ({{ key }})
</span>
</template>
<template v-else>
{{ prettifyFileSize(cacheData.usage) }}
</template>
<span>{{ prettifyFileSize(cacheData.usage) }}</span>
</p>
<form-button
:class="$style.button"
label="traQ本体"
@click="clearMainCache"
/>
<form-button
:class="$style.button"
label="ファイルの本体一覧"
@click="clearFileCache"
/>
<form-button
:class="$style.button"
label="ファイルのサムネイル一覧"
@click="clearThumbnailCache"
label="削除する"
type="secondary"
is-danger
@click="openClearCacheModal"
/>
</div>
</div>
</template>

<script lang="ts">
import { onMounted, ref } from 'vue'
import { useToastStore } from '/@/store/ui/toast'
import { wait } from '/@/lib/basic/timer'
import { checkStorageManagerSupport } from '/@/lib/dom/browser'
import { prettifyFileSize } from '/@/lib/basic/file'
import { useStampsStore } from '/@/store/entities/stamps'
declare global {
interface StorageEstimate {
usageDetails: Record<string, number>
}
}
const isStorageManagerSupported = checkStorageManagerSupport()
const getStorageUsage = async () => {
if (!isStorageManagerSupported) return null
return navigator.storage.estimate()
}
const confirmClear = () => window.confirm('本当に削除しますか?')
/* CacheStorageのnameはsw.jsを参照 */
const clearCacheStorage = (cacheName: string) => window.caches.delete(cacheName)
</script>

<script lang="ts" setup>
import FormButton from '/@/components/UI/FormButton.vue'
const { fetchStamps } = useStampsStore()
const { addSuccessToast } = useToastStore()
const showToast = (extraMesage?: string) => {
addSuccessToast(`削除に成功しました${extraMesage ? `: ${extraMesage}` : ''}`)
}
import { useModalStore } from '/@/store/ui/modal'
const cacheData = ref<StorageEstimate | null>(null)
const setCacheData = async () => {
cacheData.value = await getStorageUsage()
}
onMounted(setCacheData)
const clearMainCache = async () => {
if (!confirmClear()) return
const names = await window.caches.keys()
await Promise.all(
names
.filter(name => name.startsWith('traQ_S-precache'))
.map(name => clearCacheStorage(name))
)
const registration = await navigator.serviceWorker?.getRegistration()
if (registration) {
registration.unregister()
showToast('1秒後にリロードします')
setCacheData()
await wait(1000)
window.location.reload()
} else {
showToast()
}
}
const clearFileCache = async () => {
if (!confirmClear()) return
await clearCacheStorage('files-cache')
setCacheData()
showToast()
}
const clearThumbnailCache = async () => {
if (!confirmClear()) return
await clearCacheStorage('thumbnail-cache')
setCacheData()
showToast()
const { pushModal } = useModalStore()
const openClearCacheModal = async () => {
pushModal({
type: 'settings-cache-clear'
})
}
</script>

<style lang="scss" module>
.header {
margin-bottom: 8px;
}
.content {
margin-left: 12px;
}
.usage {
margin-bottom: 8px;
}
Expand Down
Loading

0 comments on commit d55e4bc

Please sign in to comment.