123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- import { tryOnScopeDispose } from '@vueuse/shared'
- import { isEqual, keyBy } from 'lodash-es'
- import { acceptHMRUpdate, defineStore } from 'pinia'
- import { computed, ref, watch } from 'vue'
- import { useRouter } from 'vue-router'
- import {
- EnumTaskbarApp,
- EnumTaskbarEntity,
- type UserCurrentTaskbarItemListQuery,
- type UserCurrentTaskbarItemListUpdatesSubscription,
- type UserCurrentTaskbarItemListUpdatesSubscriptionVariables,
- type UserCurrentTaskbarItemUpdatesSubscription,
- type UserCurrentTaskbarItemUpdatesSubscriptionVariables,
- } from '#shared/graphql/types.ts'
- import { convertToGraphQLId } from '#shared/graphql/utils.ts'
- import { getApolloClient } from '#shared/server/apollo/client.ts'
- import {
- MutationHandler,
- QueryHandler,
- } from '#shared/server/apollo/handler/index.ts'
- import { useApplicationStore } from '#shared/stores/application.ts'
- import { useSessionStore } from '#shared/stores/session.ts'
- import type { ObjectWithId } from '#shared/types/utils.ts'
- import log from '#shared/utils/log.ts'
- import { userTaskbarTabPluginByType } from '#desktop/components/UserTaskbarTabs/plugins/index.ts'
- import type {
- BackRoute,
- UserTaskbarTab,
- } from '#desktop/components/UserTaskbarTabs/types.ts'
- import { useUserCurrentTaskbarItemAddMutation } from '../graphql/mutations/userCurrentTaskbarItemAdd.api.ts'
- import { useUserCurrentTaskbarItemDeleteMutation } from '../graphql/mutations/userCurrentTaskbarItemDelete.api.ts'
- import { useUserCurrentTaskbarItemTouchLastContactMutation } from '../graphql/mutations/userCurrentTaskbarItemTouchLastContact.api.ts'
- import { useUserCurrentTaskbarItemUpdateMutation } from '../graphql/mutations/userCurrentTaskbarItemUpdate.api.ts'
- import {
- UserCurrentTaskbarItemListDocument,
- useUserCurrentTaskbarItemListQuery,
- } from '../graphql/queries/userCurrentTaskbarItemList.api.ts'
- import { UserCurrentTaskbarItemListUpdatesDocument } from '../graphql/subscriptions/userCurrentTaskbarItemListUpdates.api.ts'
- import { UserCurrentTaskbarItemUpdatesDocument } from '../graphql/subscriptions/userCurrentTaskbarItemUpdates.api.ts'
- import type { TaskbarTabContext } from '../types.ts'
- export const useUserCurrentTaskbarTabsStore = defineStore(
- 'userCurrentTaskbarTabs',
- () => {
- const application = useApplicationStore()
- const session = useSessionStore()
- const router = useRouter()
- const taskbarTabContexts = ref<Record<string, TaskbarTabContext>>({})
- const activeTaskbarTabEntityKey = ref<string>()
- const taskbarTabsInCreation = ref<UserTaskbarTab[]>([])
- const taskbarTabIDsInDeletion = ref<ID[]>([])
- const getTaskbarTabTypePlugin = (tabEntityType: EnumTaskbarEntity) =>
- userTaskbarTabPluginByType[tabEntityType]
- const taskbarTabsQuery = new QueryHandler(
- useUserCurrentTaskbarItemListQuery({ app: EnumTaskbarApp.Desktop }),
- )
- const taskbarTabsRaw = taskbarTabsQuery.result()
- const taskbarTabsLoading = taskbarTabsQuery.loading()
- const taskbarTabList = computed<UserTaskbarTab[]>(
- (currentTaskbarTabList) => {
- if (!taskbarTabsRaw.value?.userCurrentTaskbarItemList) return []
- const taskbarTabs: UserTaskbarTab[] =
- taskbarTabsRaw.value.userCurrentTaskbarItemList
- .filter(
- (taskbarTab) =>
- !taskbarTabIDsInDeletion.value.includes(taskbarTab.id),
- )
- .flatMap((taskbarTab) => {
- const type = taskbarTab.callback
- if (!userTaskbarTabPluginByType[type]) {
- log.warn(`Unknown taskbar tab type: ${type}.`)
- return []
- }
- return {
- type,
- entity: taskbarTab.entity,
- entityAccess: taskbarTab.entityAccess,
- tabEntityKey: taskbarTab.key,
- taskbarTabId: taskbarTab.id,
- order: taskbarTab.prio,
- formId: taskbarTab.formId,
- formNewArticlePresent: taskbarTab.formNewArticlePresent,
- changed: taskbarTab.changed,
- dirty: taskbarTab.dirty,
- notify: taskbarTab.notify,
- updatedAt: taskbarTab.updatedAt,
- }
- })
- const existingTabEntityKeys = new Set(
- taskbarTabs.map((taskbarTab) => taskbarTab.tabEntityKey),
- )
- const newTaskbarTabList = taskbarTabs
- .concat(
- taskbarTabsInCreation.value.filter(
- (taskbarTab) =>
- !existingTabEntityKeys.has(taskbarTab.tabEntityKey),
- ),
- )
- .sort((a, b) => a.order - b.order)
- if (
- currentTaskbarTabList &&
- isEqual(currentTaskbarTabList, newTaskbarTabList)
- )
- return currentTaskbarTabList
- return newTaskbarTabList
- },
- )
- const activeTaskbarTab = computed<UserTaskbarTab | undefined>(
- (currentActiveTaskbarTab) => {
- if (!activeTaskbarTabEntityKey.value) return
- const newActiveTaskbarTab = taskbarTabList.value.find(
- (taskbarTab) =>
- taskbarTab.tabEntityKey === activeTaskbarTabEntityKey.value,
- )
- if (
- currentActiveTaskbarTab &&
- isEqual(newActiveTaskbarTab, currentActiveTaskbarTab)
- )
- return currentActiveTaskbarTab
- return newActiveTaskbarTab
- },
- )
- const activeTaskbarTabId = computed(
- () => activeTaskbarTab.value?.taskbarTabId,
- )
- const activeTaskbarTabEntityAccess = computed(
- () => activeTaskbarTab.value?.entityAccess,
- )
- const hasTaskbarTabs = computed(() => taskbarTabList.value?.length > 0)
- const taskbarTabListByTabEntityKey = computed(() =>
- keyBy(taskbarTabList.value, 'tabEntityKey'),
- )
- const taskbarTabListOrder = computed(() =>
- taskbarTabList.value.reduce((taskbarTabListOrder, taskbarTab) => {
- taskbarTabListOrder.push(taskbarTab.tabEntityKey)
- return taskbarTabListOrder
- }, [] as string[]),
- )
- const taskbarLookupByTypeAndTabEntityKey = computed(() => {
- return taskbarTabList.value.reduce(
- (lookup, tab) => {
- if (!tab.taskbarTabId) return lookup
- lookup[tab.type] = lookup[tab.type] || {}
- lookup[tab.type][tab.tabEntityKey] = tab.taskbarTabId
- return lookup
- },
- {} as Record<EnumTaskbarEntity, Record<ID, ID>>,
- )
- })
- const taskbarTabExists = (type: EnumTaskbarEntity, tabEntityKey: string) =>
- Boolean(taskbarLookupByTypeAndTabEntityKey.value[type]?.[tabEntityKey])
- const previousRoutes = ref<BackRoute[]>([])
- // Keep track of previously visited routes and if they are taskbar tab routes.
- router.afterEach((_, from) => {
- // Clear all previous routes whenever a non-taskbar tab route is visited.
- if (!from.meta?.taskbarTabEntityKey) previousRoutes.value.length = 0
- previousRoutes.value.push({
- path: from.fullPath,
- taskbarTabEntityKey: from.meta?.taskbarTabEntityKey,
- })
- })
- const backRoutes = computed(() => [...previousRoutes.value].reverse())
- const redirectToLastHistoricalRoute = () => {
- // In case of taskbar tab routes, make sure the tab is still present in the list.
- // We can do this by comparing the historical taskbar tab entity key against the current tab list.
- const nextRoute = backRoutes.value.find((backRoute) => {
- // Return a non-taskbar tab route immediately.
- if (!backRoute.taskbarTabEntityKey) return true
- // Ignore the current tab, we will be closing it shortly.
- if (backRoute.taskbarTabEntityKey === activeTaskbarTabEntityKey.value)
- return false
- // Check if the taskbar tab route is part of the current taskbar.
- return !!taskbarTabListByTabEntityKey.value[
- backRoute.taskbarTabEntityKey
- ]
- })
- // If identified, redirect to the historical route.
- if (nextRoute) {
- router.push(nextRoute.path)
- return
- }
- // Otherwise, redirect to the fallback route.
- // TODO: Adjust the following redirect fallback to Overviews page instead, when ready.
- router.push('/')
- }
- const handleActiveTaskbarTabRemoval = (
- taskbarTabList: UserCurrentTaskbarItemListQuery['userCurrentTaskbarItemList'],
- removedItemId: string,
- ) => {
- const removedItem = taskbarTabList?.find(
- (tab) => tab.id === removedItemId,
- )
- if (!removedItem) return
- if (removedItem.key !== activeTaskbarTabEntityKey.value) return
- // If the active taskbar tab was removed, redirect to the last historical route.
- redirectToLastHistoricalRoute()
- }
- taskbarTabsQuery.subscribeToMore<
- UserCurrentTaskbarItemUpdatesSubscriptionVariables,
- UserCurrentTaskbarItemUpdatesSubscription
- >({
- document: UserCurrentTaskbarItemUpdatesDocument,
- variables: {
- app: EnumTaskbarApp.Desktop,
- userId: session.userId,
- },
- updateQuery(previous, { subscriptionData }) {
- const updates = subscriptionData.data.userCurrentTaskbarItemUpdates
- if (!updates.addItem && !updates.updateItem && !updates.removeItem)
- return null as unknown as UserCurrentTaskbarItemListQuery
- if (!previous.userCurrentTaskbarItemList || updates.updateItem)
- return previous
- const previousTaskbarTabList = previous.userCurrentTaskbarItemList
- if (updates.removeItem) {
- const newTaskbarTabList = previousTaskbarTabList.filter(
- (tab) => tab.id !== updates.removeItem,
- )
- handleActiveTaskbarTabRemoval(
- previousTaskbarTabList,
- updates.removeItem,
- )
- return {
- userCurrentTaskbarItemList: newTaskbarTabList,
- }
- }
- if (updates.addItem) {
- const newIdPresent = previousTaskbarTabList.find((taskbarTab) => {
- return taskbarTab.id === updates.addItem?.id
- })
- if (newIdPresent) return previous
- return {
- userCurrentTaskbarItemList: [
- ...previousTaskbarTabList,
- updates.addItem,
- ],
- }
- }
- return previous
- },
- })
- taskbarTabsQuery.subscribeToMore<
- UserCurrentTaskbarItemListUpdatesSubscriptionVariables,
- UserCurrentTaskbarItemListUpdatesSubscription
- >({
- document: UserCurrentTaskbarItemListUpdatesDocument,
- variables: {
- userId: session.userId,
- app: EnumTaskbarApp.Desktop,
- },
- })
- const taskbarAddMutation = new MutationHandler(
- useUserCurrentTaskbarItemAddMutation({
- update: (cache, { data }) => {
- if (!data) return
- const { userCurrentTaskbarItemAdd } = data
- if (!userCurrentTaskbarItemAdd?.taskbarItem) return
- const newIdPresent = taskbarTabList.value.find((taskbarTab) => {
- return (
- taskbarTab.taskbarTabId ===
- userCurrentTaskbarItemAdd.taskbarItem?.id
- )
- })
- if (newIdPresent) return
- let existingTaskbarItemList =
- cache.readQuery<UserCurrentTaskbarItemListQuery>({
- query: UserCurrentTaskbarItemListDocument,
- })
- existingTaskbarItemList = {
- ...existingTaskbarItemList,
- userCurrentTaskbarItemList: [
- ...(existingTaskbarItemList?.userCurrentTaskbarItemList || []),
- userCurrentTaskbarItemAdd.taskbarItem,
- ],
- }
- cache.writeQuery({
- query: UserCurrentTaskbarItemListDocument,
- data: existingTaskbarItemList,
- })
- },
- }),
- )
- const addTaskbarTab = async (
- taskbarTabEntity: EnumTaskbarEntity,
- tabEntityKey: string,
- tabEntityInternalId: string,
- ) => {
- const { buildTaskbarTabParams, entityType, entityDocument } =
- getTaskbarTabTypePlugin(taskbarTabEntity)
- const order = hasTaskbarTabs.value
- ? taskbarTabList.value[taskbarTabList.value.length - 1].order + 1
- : 1
- // Add temporary in creation taskbar tab item when we have already an existing entity from the cache.
- if (entityType && entityDocument) {
- const cachedEntity = getApolloClient().cache.readFragment<ObjectWithId>(
- {
- id: `${entityType}:${convertToGraphQLId(
- entityType,
- tabEntityInternalId,
- )}`,
- fragment: entityDocument,
- },
- )
- if (cachedEntity) {
- taskbarTabsInCreation.value.push({
- type: taskbarTabEntity,
- entity: cachedEntity,
- tabEntityKey,
- order,
- })
- }
- }
- await taskbarAddMutation
- .send({
- input: {
- app: EnumTaskbarApp.Desktop,
- callback: taskbarTabEntity,
- key: tabEntityKey,
- notify: false,
- params: buildTaskbarTabParams(tabEntityInternalId),
- prio: order,
- },
- })
- .finally(() => {
- // Remove temporary in creation taskar tab again.
- taskbarTabsInCreation.value = taskbarTabsInCreation.value.filter(
- (tab) => tab.tabEntityKey !== tabEntityKey,
- )
- })
- }
- const taskbarUpdateMutation = new MutationHandler(
- useUserCurrentTaskbarItemUpdateMutation(),
- )
- const updateTaskbarTab = (taskbarTabId: ID, taskbarTab: UserTaskbarTab) => {
- taskbarUpdateMutation.send({
- id: taskbarTabId,
- input: {
- app: EnumTaskbarApp.Desktop,
- callback: taskbarTab.type,
- key: taskbarTab.tabEntityKey,
- notify: !!taskbarTab.notify,
- prio: taskbarTab.order,
- dirty: taskbarTab.dirty,
- },
- })
- }
- const taskbarTouchMutation = new MutationHandler(
- useUserCurrentTaskbarItemTouchLastContactMutation(),
- )
- const touchTaskbarTab = async (taskbarTabId: ID) => {
- await taskbarTouchMutation.send({
- id: taskbarTabId,
- })
- }
- const upsertTaskbarTab = async (
- taskbarTabEntity: EnumTaskbarEntity,
- tabEntityKey: string,
- tabEntityInternalId: string,
- ) => {
- activeTaskbarTabEntityKey.value = tabEntityKey
- if (!taskbarTabExists(taskbarTabEntity, tabEntityKey)) {
- await addTaskbarTab(taskbarTabEntity, tabEntityKey, tabEntityInternalId)
- }
- const taskbarTab = taskbarTabListByTabEntityKey.value[tabEntityKey]
- if (!taskbarTab || !taskbarTab.taskbarTabId) return
- await touchTaskbarTab(taskbarTab.taskbarTabId)
- }
- const resetActiveTaskbarTab = () => {
- if (!activeTaskbarTabEntityKey.value) return
- activeTaskbarTabEntityKey.value = undefined
- }
- let silenceTaskbarDeleteError = false
- const taskbarDeleteMutation = new MutationHandler(
- useUserCurrentTaskbarItemDeleteMutation(),
- {
- errorCallback: () => {
- if (silenceTaskbarDeleteError) return false
- },
- },
- )
- const deleteTaskbarTab = (taskbarTabId: ID, silenceError?: boolean) => {
- taskbarTabIDsInDeletion.value.push(taskbarTabId)
- if (silenceError) silenceTaskbarDeleteError = true
- taskbarDeleteMutation
- .send({
- id: taskbarTabId,
- })
- .catch(() => {
- taskbarTabIDsInDeletion.value = taskbarTabIDsInDeletion.value.filter(
- (inDeletionTaskbarTabId) => inDeletionTaskbarTabId !== taskbarTabId,
- )
- })
- .finally(() => {
- if (silenceError) silenceTaskbarDeleteError = false
- })
- }
- watch(taskbarTabList, (newTaskbarTabList) => {
- if (
- !newTaskbarTabList ||
- newTaskbarTabList.length <=
- application.config.ui_task_mananger_max_task_count
- )
- return
- const sortedTaskbarTabList = newTaskbarTabList
- .filter(
- (taskbarTab) =>
- taskbarTab.taskbarTabId !== activeTaskbarTab.value?.taskbarTabId &&
- taskbarTab.updatedAt &&
- !taskbarTab.changed &&
- !taskbarTab.dirty,
- )
- .sort(
- (a, b) =>
- new Date(a.updatedAt!).getTime() - new Date(b.updatedAt!).getTime(),
- )
- if (!sortedTaskbarTabList.length) return
- const oldestTaskbarTab = sortedTaskbarTabList.at(0)
- if (!oldestTaskbarTab?.taskbarTabId) return
- log.info(
- `More than the allowed maximum number of tasks are open (${application.config.ui_task_mananger_max_task_count}), closing the oldest untouched task now.`,
- oldestTaskbarTab.tabEntityKey,
- )
- deleteTaskbarTab(oldestTaskbarTab.taskbarTabId, true)
- })
- const waitForTaskbarListLoaded = () => {
- return new Promise<void>((resolve) => {
- const interval = setInterval(() => {
- if (taskbarTabsLoading.value) return
- clearInterval(interval)
- resolve()
- })
- })
- }
- tryOnScopeDispose(() => {
- taskbarTabsQuery.stop()
- })
- return {
- taskbarTabIDsInDeletion,
- activeTaskbarTabId,
- activeTaskbarTab,
- activeTaskbarTabEntityKey,
- activeTaskbarTabEntityAccess,
- taskbarTabContexts,
- taskbarTabsInCreation,
- taskbarTabsRaw,
- taskbarTabList,
- taskbarTabListByTabEntityKey,
- taskbarLookupByTypeAndTabEntityKey,
- taskbarTabListOrder,
- taskbarTabExists,
- getTaskbarTabTypePlugin,
- addTaskbarTab,
- updateTaskbarTab,
- upsertTaskbarTab,
- deleteTaskbarTab,
- resetActiveTaskbarTab,
- waitForTaskbarListLoaded,
- loading: taskbarTabsLoading,
- hasTaskbarTabs,
- }
- },
- )
- if (import.meta.hot) {
- import.meta.hot.accept(
- acceptHMRUpdate(useUserCurrentTaskbarTabsStore, import.meta.hot),
- )
- }
|