diff options
author | Rémy Raes <contact@remyraes.com> | 2023-10-16 16:17:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-16 16:17:37 +0200 |
commit | 07ca1acee9a5f4227d18766b49b3fe6c8690e9a1 (patch) | |
tree | 824f2e659536dc9a5eb10f6e99bfb16da3d65647 | |
parent | 4ddf57e0024dcd70a39473b15bbe1a0e1f69db88 (diff) | |
download | FlightCore-07ca1acee9a5f4227d18766b49b3fe6c8690e9a1.tar.gz FlightCore-07ca1acee9a5f4227d18766b49b3fe6c8690e9a1.zip |
feat: Notification menu (#615)
Introduces a notification menu, which hosts notifications fired while the app was not focused.
Notifications can be closed by the user.
-rw-r--r-- | src-vue/src/App.vue | 21 | ||||
-rw-r--r-- | src-vue/src/components/NotificationButton.vue | 77 | ||||
-rw-r--r-- | src-vue/src/i18n/lang/en.json | 5 | ||||
-rw-r--r-- | src-vue/src/i18n/lang/fr.json | 5 | ||||
-rw-r--r-- | src-vue/src/plugins/modules/notifications.ts | 31 | ||||
-rw-r--r-- | src-vue/src/plugins/store.ts | 2 | ||||
-rw-r--r-- | src-vue/src/style.css | 3 | ||||
-rw-r--r-- | src-vue/src/utils/ui.ts | 6 |
8 files changed, 146 insertions, 4 deletions
diff --git a/src-vue/src/App.vue b/src-vue/src/App.vue index 30e5e683..44e2c3e6 100644 --- a/src-vue/src/App.vue +++ b/src-vue/src/App.vue @@ -8,9 +8,11 @@ import { appWindow } from '@tauri-apps/api/window'; import { store } from './plugins/store'; import { Store } from 'tauri-plugin-store-api'; import { invoke } from "@tauri-apps/api"; +import NotificationButton from "./components/NotificationButton.vue"; export default { components: { + NotificationButton, ChangelogView, DeveloperView, PlayView, @@ -73,6 +75,7 @@ export default { <!-- Window controls --> <div id="fc_window__controls"> + <NotificationButton /> <el-button color="white" icon="SemiSelect" @click="minimize" circle /> <el-button color="white" icon="CloseBold" @click="close" circle /> </div> @@ -119,7 +122,7 @@ export default { height: 100%; background-color: transparent; float: left; - width: calc(100% - 148px); /* window controls container width */ + width: calc(100% - 168px); /* window controls container width */ } #fc__menu_items .el-menu-item, #fc__menu_items .el-sub-menu__title { @@ -167,7 +170,9 @@ export default { height: 100%; } -#fc_window__controls > button { +#fc_window__controls > button, +#fc_window__controls > .el-dropdown > button, +#fc_window__controls > .el-dropdown > .el-badge > button { color: white; font-size: 20px; margin: auto 5px; @@ -176,11 +181,14 @@ export default { height: 100%; } -#fc_window__controls > button:hover { +#fc_window__controls > button:hover, +#fc_window__controls > .el-dropdown > button:hover, +#fc_window__controls > .el-dropdown > .el-badge > button:hover { color: #c6c9ce; } -#fc_window__controls > button:active { +#fc_window__controls > button:active, +#fc_window__controls > .el-dropdown > button:active { color: #56585a; } @@ -188,4 +196,9 @@ export default { margin-right: 15px; } +sup { + transform: translate(-10px, 5px) !important; + border: none !important; +} + </style> diff --git a/src-vue/src/components/NotificationButton.vue b/src-vue/src/components/NotificationButton.vue new file mode 100644 index 00000000..4945f8c7 --- /dev/null +++ b/src-vue/src/components/NotificationButton.vue @@ -0,0 +1,77 @@ +<template> + <el-dropdown trigger="click" placement="bottom-end" max-height="280" popper-class="fc_popper"> + <el-badge v-if="notifications.length != 0" :value="notifications.length" :max="9" class="item" type="primary"> + <el-button color="white" icon="BellFilled" circle /> + </el-badge> + <el-button v-else color="white" icon="BellFilled" circle /> + <template #dropdown> + <el-dropdown-menu :key="counter"> + <el-alert + v-if="notifications.length != 0" + v-for="(notification, i) in notifications" + :key="i" + :title="notification.title" + :description="notification.text" + :type="notification.type" + show-icon + style="width: 300px" + @close.stop="removeNotification(i)" + /> + <el-result + v-else + icon="success" + :title="i18n.global.tc('notification.no_new.title')" + :sub-title="i18n.global.tc('notification.no_new.text')" + > + <template #icon> + </template> + </el-result> + </el-dropdown-menu> + </template> + </el-dropdown> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import {Notification} from '../plugins/modules/notifications'; +import {i18n} from "../main"; + +export default defineComponent({ + name: 'NotificationButton', + data: () => ({ + // This variable is used as a key for the dropdown menu, so we can force it to refresh when a item is deleted. + counter: 0 + }), + computed: { + i18n() { + return i18n + }, + notifications(): Notification[] { + return this.$store.state.notifications.notifications; + } + }, + methods: { + removeNotification(index: number) { + this.$store.commit('removeNotification', index); + // By refreshing the notifications list, we ensure the first notification get the index 0, ensuring this list + // is synchronized with the store list. + this.counter += 1; + } + } +}) +</script> + +<style scoped> +.el-dropdown { + height: 100%; + max-height: 300px; +} + +.el-button { + margin: auto 25px auto 0 !important; +} + +.el-alert { + margin: 5px 10px 5px 5px; +} +</style> diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json index 2bf18c2e..6fa6a965 100644 --- a/src-vue/src/i18n/lang/en.json +++ b/src-vue/src/i18n/lang/en.json @@ -146,6 +146,11 @@ }, "notification": { + "no_new": { + "title": "Up-to-date", + "text": "Nothing to see here!" + }, + "game_folder": { "new": { "title": "New game folder", diff --git a/src-vue/src/i18n/lang/fr.json b/src-vue/src/i18n/lang/fr.json index dbb34e80..c1ad181f 100644 --- a/src-vue/src/i18n/lang/fr.json +++ b/src-vue/src/i18n/lang/fr.json @@ -127,6 +127,11 @@ } }, "notification": { + "no_new": { + "title": "Vous êtes à jour", + "text": "Rien à voir par ici !" + }, + "game_folder": { "new": { "title": "Nouveau dossier", diff --git a/src-vue/src/plugins/modules/notifications.ts b/src-vue/src/plugins/modules/notifications.ts new file mode 100644 index 00000000..ed57f8af --- /dev/null +++ b/src-vue/src/plugins/modules/notifications.ts @@ -0,0 +1,31 @@ +type NotificationType = 'success' | 'warning' | 'info' | 'error'; + +export interface Notification { + title: string; + text: string; + type: NotificationType; +} + +interface NotificationsStoreState { + notifications: Notification[]; +} + + +/** + * This notification module is meant to host the list of notifications that have been fired while the application was + * not focused. + * This list is then used by the [NotificationButton] component to display notifications to user. + **/ +export const notificationsModule = { + state: () => ({ + notifications: [] + }) as NotificationsStoreState, + mutations: { + addNotification(state: NotificationsStoreState, payload: Notification) { + state.notifications.push(payload); + }, + removeNotification(state: NotificationsStoreState, index: number): void { + state.notifications.splice(index, 1); + } + } + } diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index 854cd19e..56bf37e9 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -19,6 +19,7 @@ import { searchModule } from './modules/search'; import { i18n } from '../main'; import { pullRequestModule } from './modules/pull_requests'; import { showErrorNotification, showNotification } from '../utils/ui'; +import { notificationsModule } from './modules/notifications'; const persistentStore = new Store('flight-core-settings.json'); @@ -57,6 +58,7 @@ export const store = createStore<FlightCoreStore>({ modules: { search: searchModule, pullrequests: pullRequestModule, + notifications: notificationsModule }, state(): FlightCoreStore { return { diff --git a/src-vue/src/style.css b/src-vue/src/style.css index dfe682bf..e5c66328 100644 --- a/src-vue/src/style.css +++ b/src-vue/src/style.css @@ -50,6 +50,9 @@ body { .el-popper { width: 200px !important; } +.fc_popper { + width: auto !important; +} .el-popconfirm { word-break: break-word; diff --git a/src-vue/src/utils/ui.ts b/src-vue/src/utils/ui.ts index b84d7666..4c735a9c 100644 --- a/src-vue/src/utils/ui.ts +++ b/src-vue/src/utils/ui.ts @@ -1,8 +1,10 @@ import { ElNotification, NotificationHandle } from "element-plus"; import { i18n } from "../main"; +import { store } from "../plugins/store"; /** * Displays content to the user in the form of a notification appearing on screen bottom right. + * If the app is not focused when this is invoked, a notification is added to the notifications menu. **/ function showNotification( title: string, @@ -10,6 +12,10 @@ function showNotification( type: 'success' | 'warning' | 'error' | 'info' = 'success', duration: number = 4500 ): NotificationHandle { + if (!document.hasFocus()) { + store.commit('addNotification', {title, text: message, type}); + } + return ElNotification({ title, message, type, duration, position: 'bottom-right', |