LayoutPage.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { storeToRefs } from 'pinia'
  4. import { ref, computed } from 'vue'
  5. import { useRoute } from 'vue-router'
  6. import { EnumTaskbarEntityAccess } from '#shared/graphql/types.ts'
  7. import type { ErrorOptions } from '#shared/router/error.ts'
  8. import { useSessionStore } from '#shared/stores/session.ts'
  9. import { ErrorStatusCodes } from '#shared/types/error.ts'
  10. import CommonError from '#desktop/components/CommonError/CommonError.vue'
  11. import LayoutMain from '#desktop/components/layout/LayoutMain.vue'
  12. import LeftSidebarFooterMenu from '#desktop/components/layout/LayoutSidebar/LeftSidebar/LeftSidebarFooterMenu.vue'
  13. import LeftSidebarHeader from '#desktop/components/layout/LayoutSidebar/LeftSidebar/LeftSidebarHeader.vue'
  14. import LayoutSidebar from '#desktop/components/layout/LayoutSidebar.vue'
  15. import PageNavigation from '#desktop/components/PageNavigation/PageNavigation.vue'
  16. import UserTaskbarTabs from '#desktop/components/UserTaskbarTabs/UserTaskbarTabs.vue'
  17. import { useResizeGridColumns } from '#desktop/composables/useResizeGridColumns.ts'
  18. import { useUserCurrentTaskbarTabsStore } from '#desktop/entities/user/current/stores/taskbarTabs.ts'
  19. const { activeTaskbarTabEntityAccess, activeTaskbarTabEntityKey } = storeToRefs(
  20. useUserCurrentTaskbarTabsStore(),
  21. )
  22. const route = useRoute()
  23. const entityAccess = computed<Maybe<EnumTaskbarEntityAccess> | undefined>(
  24. (currentValue) => {
  25. return route.meta.taskbarTabEntityKey === activeTaskbarTabEntityKey.value
  26. ? activeTaskbarTabEntityAccess.value
  27. : currentValue
  28. },
  29. )
  30. // NB: Flag in the route metadata data does not seem to trigger an update all the time.
  31. // Due to this limitation, we need a way to force the re-computation in certain situations.
  32. const pageError = computed(() => {
  33. if (!route.meta.taskbarTabEntity) return null
  34. // Check first for page errors, when the entity access is not undefined.
  35. if (entityAccess.value === undefined) return undefined
  36. switch (entityAccess.value) {
  37. case EnumTaskbarEntityAccess.Forbidden:
  38. return {
  39. statusCode: ErrorStatusCodes.Forbidden,
  40. title: __('Forbidden'),
  41. message:
  42. (route.meta.messageForbidden as string) ??
  43. __('You have insufficient rights to view this object.'),
  44. } as ErrorOptions
  45. case EnumTaskbarEntityAccess.NotFound:
  46. return {
  47. statusCode: ErrorStatusCodes.NotFound,
  48. title: __('Not Found'),
  49. message:
  50. (route.meta.messageNotFound as string) ??
  51. __(
  52. 'Object with specified ID was not found. Try checking the URL for errors.',
  53. ),
  54. } as ErrorOptions
  55. case EnumTaskbarEntityAccess.Granted:
  56. default:
  57. return null
  58. }
  59. })
  60. const noTransition = ref(false)
  61. const { userId } = useSessionStore()
  62. const storageKeyId = `${userId}-left`
  63. const {
  64. currentSidebarWidth,
  65. maxSidebarWidth,
  66. minSidebarWidth,
  67. gridColumns,
  68. collapseSidebar,
  69. resizeSidebar,
  70. expandSidebar,
  71. resetSidebarWidth,
  72. } = useResizeGridColumns(storageKeyId)
  73. </script>
  74. <template>
  75. <div
  76. class="grid h-full max-h-full overflow-y-clip duration-100"
  77. :class="{ 'transition-none': noTransition }"
  78. :style="gridColumns"
  79. >
  80. <LayoutSidebar
  81. id="main-sidebar"
  82. :name="storageKeyId"
  83. :aria-label="$t('Main sidebar')"
  84. data-theme="dark"
  85. :style="{ colorScheme: 'dark' }"
  86. :current-width="currentSidebarWidth"
  87. :max-width="maxSidebarWidth"
  88. :min-width="minSidebarWidth"
  89. collapsible
  90. resizable
  91. no-scroll
  92. @collapse="collapseSidebar"
  93. @expand="expandSidebar"
  94. @resize-horizontal="resizeSidebar"
  95. @resize-horizontal-start="noTransition = true"
  96. @resize-horizontal-end="noTransition = false"
  97. @reset-width="resetSidebarWidth"
  98. >
  99. <template #default="{ isCollapsed }">
  100. <LeftSidebarHeader class="mb-2" :collapsed="isCollapsed" />
  101. <PageNavigation :collapsed="isCollapsed" />
  102. <UserTaskbarTabs :collapsed="isCollapsed" />
  103. <LeftSidebarFooterMenu :collapsed="isCollapsed" class="mt-auto" />
  104. </template>
  105. </LayoutSidebar>
  106. <div class="relative">
  107. <LayoutMain
  108. v-if="pageError"
  109. class="flex grow flex-col items-center justify-center gap-4 bg-blue-50 dark:bg-gray-800"
  110. >
  111. <CommonError :options="pageError" authenticated />
  112. </LayoutMain>
  113. <slot v-else-if="pageError !== undefined"><RouterView /></slot>
  114. </div>
  115. </div>
  116. </template>