refactor: allow banner service to hold multiple banners and display the banner with the highest score (#3556)
This commit is contained in:
committed by
GitHub
parent
a3aa9b68fc
commit
24ae090916
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
<span class="text-white">
|
<span class="text-white">
|
||||||
<span v-if="banner.alternateText" class="md:hidden">
|
<span v-if="banner.alternateText" class="md:hidden">
|
||||||
{{ banner.alternateText }}
|
{{ banner.alternateText(t) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="<md:hidden">
|
<span :class="banner.alternateText ? '<md:hidden' : ''">
|
||||||
{{ banner.text }}
|
{{ banner.text(t) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
|
|
||||||
import { BannerContent, BannerType } from "~/services/banner.service"
|
import { BannerContent, BannerType } from "~/services/banner.service"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
|
||||||
import IconAlertCircle from "~icons/lucide/alert-circle"
|
import IconAlertCircle from "~icons/lucide/alert-circle"
|
||||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
@@ -30,6 +30,8 @@ const props = defineProps<{
|
|||||||
banner: BannerContent
|
banner: BannerContent
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
const ariaRoles: Record<BannerType, string> = {
|
const ariaRoles: Record<BannerType, string> = {
|
||||||
error: "alert",
|
error: "alert",
|
||||||
warning: "status",
|
warning: "status",
|
||||||
|
|||||||
@@ -217,7 +217,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<AppBanner v-if="banner" :banner="banner" />
|
<AppBanner v-if="bannerContent" :banner="bannerContent" />
|
||||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||||
<TeamsInvite
|
<TeamsInvite
|
||||||
v-if="workspace.type === 'team' && workspace.teamID"
|
v-if="workspace.type === 'team' && workspace.teamID"
|
||||||
@@ -266,7 +266,11 @@ import IconUsers from "~icons/lucide/users"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
import { BannerService } from "~/services/banner.service"
|
import {
|
||||||
|
BannerService,
|
||||||
|
BannerContent,
|
||||||
|
BANNER_PRIORITY_HIGH,
|
||||||
|
} from "~/services/banner.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -284,18 +288,29 @@ const showTeamsModal = ref(false)
|
|||||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
const mdAndLarger = breakpoints.greater("md")
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
|
|
||||||
const { content: banner } = useService(BannerService)
|
const banner = useService(BannerService)
|
||||||
const network = reactive(useNetwork())
|
const bannerContent = computed(() => banner.content.value?.content)
|
||||||
|
let bannerID: number | null = null
|
||||||
|
|
||||||
watch(network, () => {
|
const offlineBanner: BannerContent = {
|
||||||
if (network.isOnline) {
|
type: "info",
|
||||||
banner.value = null
|
text: (t) => t("helpers.offline"),
|
||||||
|
alternateText: (t) => t("helpers.offline_short"),
|
||||||
|
score: BANNER_PRIORITY_HIGH,
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = reactive(useNetwork())
|
||||||
|
const isOnline = computed(() => network.isOnline)
|
||||||
|
|
||||||
|
// Show the offline banner if the user is offline
|
||||||
|
watch(isOnline, () => {
|
||||||
|
if (!isOnline.value) {
|
||||||
|
bannerID = banner.showBanner(offlineBanner)
|
||||||
return
|
return
|
||||||
}
|
} else {
|
||||||
banner.value = {
|
if (banner.content && bannerID) {
|
||||||
type: "info",
|
banner.removeBanner(bannerID)
|
||||||
text: t("helpers.offline"),
|
}
|
||||||
alternateText: t("helpers.offline_short"),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,53 @@
|
|||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
import { BannerContent, BannerService } from "../banner.service"
|
|
||||||
import { TestContainer } from "dioc/testing"
|
import { TestContainer } from "dioc/testing"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import {
|
||||||
|
BannerService,
|
||||||
|
BANNER_PRIORITY_LOW,
|
||||||
|
BANNER_PRIORITY_HIGH,
|
||||||
|
BannerContent,
|
||||||
|
} from "../banner.service"
|
||||||
|
|
||||||
describe("BannerService", () => {
|
describe("BannerService", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const service = container.bind(BannerService)
|
const banner = container.bind(BannerService)
|
||||||
|
|
||||||
it("initally there are no banners defined", () => {
|
it("should be able to show and remove a banner", () => {
|
||||||
expect(service.content.value).toEqual(null)
|
const bannerContent: BannerContent = {
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to set and retrieve banner content", () => {
|
|
||||||
const sampleBanner: BannerContent = {
|
|
||||||
type: "info",
|
type: "info",
|
||||||
text: "Info Banner",
|
text: (t: ReturnType<typeof getI18n>) => t("Info Banner"),
|
||||||
|
score: BANNER_PRIORITY_LOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
const banner = service.content
|
const bannerId = banner.showBanner(bannerContent)
|
||||||
banner.value = sampleBanner
|
expect(banner.content.value).toEqual({
|
||||||
const retrievedBanner = service.content.value
|
id: bannerId,
|
||||||
|
content: bannerContent,
|
||||||
|
})
|
||||||
|
|
||||||
expect(retrievedBanner).toEqual(sampleBanner)
|
banner.removeBanner(bannerId)
|
||||||
|
expect(banner.content.value).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to update the banner content", () => {
|
it("should show the banner with the highest score", () => {
|
||||||
const updatedBanner: BannerContent = {
|
const lowPriorityBanner: BannerContent = {
|
||||||
type: "warning",
|
type: "info",
|
||||||
text: "Updated Banner Content",
|
text: (t: ReturnType<typeof getI18n>) => t("Low Priority Banner"),
|
||||||
alternateText: "Updated Banner",
|
score: BANNER_PRIORITY_LOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
service.content.value = updatedBanner
|
const highPriorityBanner: BannerContent = {
|
||||||
const retrievedBanner = service.content.value
|
type: "warning",
|
||||||
|
text: (t: ReturnType<typeof getI18n>) => t("High Priority Banner"),
|
||||||
|
score: BANNER_PRIORITY_HIGH,
|
||||||
|
}
|
||||||
|
|
||||||
expect(retrievedBanner).toEqual(updatedBanner)
|
banner.showBanner(lowPriorityBanner)
|
||||||
|
const highPriorityBannerID = banner.showBanner(highPriorityBanner)
|
||||||
|
|
||||||
|
expect(banner.content.value).toEqual({
|
||||||
|
id: highPriorityBannerID,
|
||||||
|
content: highPriorityBanner,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import { ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
|
||||||
|
export const BANNER_PRIORITY_LOW = 1
|
||||||
|
export const BANNER_PRIORITY_MEDIUM = 3
|
||||||
|
export const BANNER_PRIORITY_HIGH = 5
|
||||||
|
|
||||||
/**
|
|
||||||
* The different types of banners that can be used.
|
|
||||||
*/
|
|
||||||
export type BannerType = "info" | "warning" | "error"
|
export type BannerType = "info" | "warning" | "error"
|
||||||
|
|
||||||
export type BannerContent = {
|
export type BannerContent = {
|
||||||
type: BannerType
|
type: BannerType
|
||||||
text: string
|
text: (t: ReturnType<typeof getI18n>) => string
|
||||||
// Can be used to display an alternate text when display size is small
|
// Can be used to display an alternate text when display size is small
|
||||||
alternateText?: string
|
alternateText?: (t: ReturnType<typeof getI18n>) => string
|
||||||
|
// Used to determine which banner should be displayed when multiple banners are present
|
||||||
|
score: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Banner = {
|
||||||
|
id: number
|
||||||
|
content: BannerContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the banner with the highest score
|
||||||
|
const getBannerWithHighestScore = (list: Banner[]) => {
|
||||||
|
if (list.length === 0) return null
|
||||||
|
else if (list.length === 1) return list[0]
|
||||||
|
else {
|
||||||
|
const highestScore = Math.max(...list.map((banner) => banner.content.score))
|
||||||
|
return list.find((banner) => banner.content.score === highestScore)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,9 +39,22 @@ export type BannerContent = {
|
|||||||
export class BannerService extends Service {
|
export class BannerService extends Service {
|
||||||
public static readonly ID = "BANNER_SERVICE"
|
public static readonly ID = "BANNER_SERVICE"
|
||||||
|
|
||||||
/**
|
private bannerID = 0
|
||||||
* This is a reactive variable that can be used to set the contents of the banner
|
private bannerList = ref<Banner[]>([])
|
||||||
* and use it to render the banner on components.
|
|
||||||
*/
|
public content = computed(() =>
|
||||||
public content = ref<BannerContent | null>(null)
|
getBannerWithHighestScore(this.bannerList.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
public showBanner(banner: BannerContent) {
|
||||||
|
this.bannerID = this.bannerID + 1
|
||||||
|
this.bannerList.value.push({ id: this.bannerID, content: banner })
|
||||||
|
return this.bannerID
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeBanner(id: number) {
|
||||||
|
this.bannerList.value = this.bannerList.value.filter(
|
||||||
|
(banner) => id !== banner.id
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user