feat: add message to add to home screen on mobile
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
169feaaf0f
commit
3c9083b90d
|
@ -12,6 +12,7 @@
|
||||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
|
<AddToHomeScreen/>
|
||||||
<UpdateNotification/>
|
<UpdateNotification/>
|
||||||
<Notification/>
|
<Notification/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
@ -43,6 +44,7 @@ import {useBaseStore} from '@/stores/base'
|
||||||
|
|
||||||
import {useColorScheme} from '@/composables/useColorScheme'
|
import {useColorScheme} from '@/composables/useColorScheme'
|
||||||
import {useBodyClass} from '@/composables/useBodyClass'
|
import {useBodyClass} from '@/composables/useBodyClass'
|
||||||
|
import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
|
||||||
|
|
||||||
const baseStore = useBaseStore()
|
const baseStore = useBaseStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
80
src/components/home/AddToHomeScreen.vue
Normal file
80
src/components/home/AddToHomeScreen.vue
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="shouldShowMessage"
|
||||||
|
class="add-to-home-screen"
|
||||||
|
:class="{'has-update-available': hasUpdateAvailable}"
|
||||||
|
>
|
||||||
|
<icon icon="arrow-up-from-bracket" class="add-icon"/>
|
||||||
|
<p>
|
||||||
|
{{ $t('home.addToHomeScreen') }}
|
||||||
|
</p>
|
||||||
|
<BaseButton @click="() => hideMessage = true" class="hide-button">
|
||||||
|
<icon icon="x"/>
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import {useLocalStorage} from '@vueuse/core'
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useBaseStore} from '@/stores/base'
|
||||||
|
|
||||||
|
const baseStore = useBaseStore()
|
||||||
|
|
||||||
|
const hideMessage = useLocalStorage('hideAddToHomeScreenMessage', false)
|
||||||
|
const hasUpdateAvailable = computed(() => baseStore.updateAvailable)
|
||||||
|
|
||||||
|
const shouldShowMessage = computed(() => {
|
||||||
|
if (hideMessage.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && window.matchMedia('(display-mode: standalone)').matches) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.add-to-home-screen {
|
||||||
|
position: fixed;
|
||||||
|
// FIXME: We should prevent usage of z-index or
|
||||||
|
// at least define it centrally
|
||||||
|
// the highest z-index of a modal is .hint-modal with 4500
|
||||||
|
z-index: 5000;
|
||||||
|
bottom: 1rem;
|
||||||
|
inset-inline: 1rem;
|
||||||
|
max-width: max-content;
|
||||||
|
margin-inline: auto;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
background: var(--grey-900);
|
||||||
|
border-radius: $radius;
|
||||||
|
font-size: .9rem;
|
||||||
|
color: var(--grey-200);
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-update-available {
|
||||||
|
bottom: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon {
|
||||||
|
color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-button {
|
||||||
|
padding: .25rem .5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -12,9 +12,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
|
import {useBaseStore} from '@/stores/base'
|
||||||
|
|
||||||
const updateAvailable = ref(false)
|
const baseStore = useBaseStore()
|
||||||
|
|
||||||
|
const updateAvailable = computed(() => baseStore.updateAvailable)
|
||||||
const registration = ref(null)
|
const registration = ref(null)
|
||||||
const refreshing = ref(false)
|
const refreshing = ref(false)
|
||||||
|
|
||||||
|
@ -31,11 +34,11 @@ navigator?.serviceWorker?.addEventListener(
|
||||||
function showRefreshUI(e: Event) {
|
function showRefreshUI(e: Event) {
|
||||||
console.log('recieved refresh event', e)
|
console.log('recieved refresh event', e)
|
||||||
registration.value = e.detail
|
registration.value = e.detail
|
||||||
updateAvailable.value = true
|
baseStore.setUpdateAvailable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshApp() {
|
function refreshApp() {
|
||||||
updateAvailable.value = false
|
baseStore.setUpdateAvailable(false)
|
||||||
if (!registration.value || !registration.value.waiting) {
|
if (!registration.value || !registration.value.waiting) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,6 @@ function refreshApp() {
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
color: var(--grey-900);
|
color: var(--grey-900);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-notification__message {
|
.update-notification__message {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
faAngleRight,
|
faAngleRight,
|
||||||
faArchive,
|
faArchive,
|
||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
|
faArrowUpFromBracket,
|
||||||
faBars,
|
faBars,
|
||||||
faBell,
|
faBell,
|
||||||
faCalendar,
|
faCalendar,
|
||||||
|
@ -56,7 +57,7 @@ import {
|
||||||
faTimes,
|
faTimes,
|
||||||
faTrashAlt,
|
faTrashAlt,
|
||||||
faUser,
|
faUser,
|
||||||
faUsers,
|
faUsers, faX,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {
|
import {
|
||||||
faBellSlash,
|
faBellSlash,
|
||||||
|
@ -71,7 +72,7 @@ import {
|
||||||
} from '@fortawesome/free-regular-svg-icons'
|
} from '@fortawesome/free-regular-svg-icons'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
import type { FontAwesomeIcon as FontAwesomeIconFixedTypes } from '@/types/vue-fontawesome'
|
import type {FontAwesomeIcon as FontAwesomeIconFixedTypes} from '@/types/vue-fontawesome'
|
||||||
|
|
||||||
library.add(faAlignLeft)
|
library.add(faAlignLeft)
|
||||||
library.add(faAngleRight)
|
library.add(faAngleRight)
|
||||||
|
@ -139,6 +140,8 @@ library.add(faTimesCircle)
|
||||||
library.add(faTrashAlt)
|
library.add(faTrashAlt)
|
||||||
library.add(faUser)
|
library.add(faUser)
|
||||||
library.add(faUsers)
|
library.add(faUsers)
|
||||||
|
library.add(faArrowUpFromBracket)
|
||||||
|
library.add(faX)
|
||||||
|
|
||||||
// overwriting the wrong types
|
// overwriting the wrong types
|
||||||
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
|
@ -5,6 +5,7 @@
|
||||||
"welcomeDay": "Hi {username}!",
|
"welcomeDay": "Hi {username}!",
|
||||||
"welcomeEvening": "Good Evening {username}!",
|
"welcomeEvening": "Good Evening {username}!",
|
||||||
"lastViewed": "Last viewed",
|
"lastViewed": "Last viewed",
|
||||||
|
"addToHomeScreen": "Add this app to your home screen for faster access and improved experience.",
|
||||||
"project": {
|
"project": {
|
||||||
"importText": "Import your projects and tasks from other services into Vikunja:",
|
"importText": "Import your projects and tasks from other services into Vikunja:",
|
||||||
"import": "Import your data into Vikunja"
|
"import": "Import your data into Vikunja"
|
||||||
|
|
|
@ -28,6 +28,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||||
const keyboardShortcutsActive = ref(false)
|
const keyboardShortcutsActive = ref(false)
|
||||||
const quickActionsActive = ref(false)
|
const quickActionsActive = ref(false)
|
||||||
const logoVisible = ref(true)
|
const logoVisible = ref(true)
|
||||||
|
const updateAvailable = ref(false)
|
||||||
|
|
||||||
function setLoading(newLoading: boolean) {
|
function setLoading(newLoading: boolean) {
|
||||||
loading.value = newLoading
|
loading.value = newLoading
|
||||||
|
@ -77,6 +78,10 @@ export const useBaseStore = defineStore('base', () => {
|
||||||
function setReady(value: boolean) {
|
function setReady(value: boolean) {
|
||||||
ready.value = value
|
ready.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUpdateAvailable(value: boolean) {
|
||||||
|
updateAvailable.value = value
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSetCurrentProject(
|
async function handleSetCurrentProject(
|
||||||
{project, forceUpdate = false}: {project: IProject | null, forceUpdate?: boolean},
|
{project, forceUpdate = false}: {project: IProject | null, forceUpdate?: boolean},
|
||||||
|
@ -135,6 +140,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||||
keyboardShortcutsActive: readonly(keyboardShortcutsActive),
|
keyboardShortcutsActive: readonly(keyboardShortcutsActive),
|
||||||
quickActionsActive: readonly(quickActionsActive),
|
quickActionsActive: readonly(quickActionsActive),
|
||||||
logoVisible: readonly(logoVisible),
|
logoVisible: readonly(logoVisible),
|
||||||
|
updateAvailable: readonly(updateAvailable),
|
||||||
|
|
||||||
setLoading,
|
setLoading,
|
||||||
setReady,
|
setReady,
|
||||||
|
@ -145,6 +151,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||||
setBackground,
|
setBackground,
|
||||||
setBlurHash,
|
setBlurHash,
|
||||||
setLogoVisible,
|
setLogoVisible,
|
||||||
|
setUpdateAvailable,
|
||||||
|
|
||||||
handleSetCurrentProject,
|
handleSetCurrentProject,
|
||||||
loadApp,
|
loadApp,
|
||||||
|
|
Reference in New Issue
Block a user