navigation-bar.svelte 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <script lang="ts">
  2. import { goto } from '$app/navigation';
  3. import { page } from '$app/stores';
  4. import { clickOutside } from '$lib/utils/click-outside';
  5. import { createEventDispatcher } from 'svelte';
  6. import { fade, fly } from 'svelte/transition';
  7. import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
  8. import { api, UserResponseDto } from '@api';
  9. import ThemeButton from '../theme-button.svelte';
  10. import { AppRoute } from '../../../constants';
  11. import AccountInfoPanel from './account-info-panel.svelte';
  12. import ImmichLogo from '../immich-logo.svelte';
  13. import SearchBar from '../search-bar/search-bar.svelte';
  14. import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
  15. import Magnify from 'svelte-material-icons/Magnify.svelte';
  16. import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
  17. import Cog from 'svelte-material-icons/Cog.svelte';
  18. export let user: UserResponseDto;
  19. export let shouldShowUploadButton = true;
  20. let shouldShowAccountInfo = false;
  21. let shouldShowAccountInfoPanel = false;
  22. // Show fallback while loading profile picture and hide when image loads.
  23. let showProfilePictureFallback = true;
  24. const dispatch = createEventDispatcher();
  25. const getFirstLetter = (text?: string) => {
  26. return text?.charAt(0).toUpperCase();
  27. };
  28. const logOut = async () => {
  29. const { data } = await api.authenticationApi.logout();
  30. await fetch('/auth/logout', { method: 'POST' });
  31. goto(data.redirectUri || '/auth/login?autoLaunch=0');
  32. };
  33. </script>
  34. <section id="dashboard-navbar" class="fixed h-[4.25rem] w-screen z-[900] text-sm">
  35. <div
  36. class="grid md:grid-cols-[250px_auto] grid-cols-[70px_auto] border-b dark:border-b-immich-dark-gray items-center py-2 bg-immich-bg dark:bg-immich-dark-bg"
  37. >
  38. <a
  39. data-sveltekit-preload-data="hover"
  40. class="flex gap-2 md:mx-6 mx-4 place-items-center"
  41. href={AppRoute.PHOTOS}
  42. >
  43. <ImmichLogo height="35" width="35" />
  44. <h1
  45. class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary md:block hidden"
  46. >
  47. IMMICH
  48. </h1>
  49. </a>
  50. <div class="flex justify-between gap-16 pr-6">
  51. <div class="w-full max-w-5xl flex-1 pl-4 sm:block hidden">
  52. <SearchBar grayTheme={true} />
  53. </div>
  54. <section class="flex gap-4 place-items-center justify-end max-sm:w-full">
  55. <a href={AppRoute.SEARCH} id="search-button" class="sm:hidden pl-4">
  56. <IconButton title="Search">
  57. <div class="flex gap-2">
  58. <Magnify size="1.5em" />
  59. </div>
  60. </IconButton>
  61. </a>
  62. <ThemeButton />
  63. {#if !$page.url.pathname.includes('/admin') && shouldShowUploadButton}
  64. <div in:fly={{ x: 50, duration: 250 }}>
  65. <LinkButton on:click={() => dispatch('uploadClicked')}>
  66. <div class="flex gap-2">
  67. <TrayArrowUp size="20" />
  68. <span class="md:block hidden">Upload</span>
  69. </div>
  70. </LinkButton>
  71. </div>
  72. {/if}
  73. {#if user.isAdmin}
  74. <a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
  75. <div class="sm:block hidden">
  76. <LinkButton>
  77. <span
  78. class={$page.url.pathname.includes('/admin')
  79. ? 'text-immich-primary dark:text-immich-dark-primary underline item'
  80. : ''}
  81. >
  82. Administration
  83. </span>
  84. </LinkButton>
  85. </div>
  86. <div class="sm:hidden block">
  87. <IconButton title="Administration">
  88. <Cog
  89. size="1.5em"
  90. class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
  91. ? 'text-immich-primary dark:text-immich-dark-primary'
  92. : ''}"
  93. />
  94. </IconButton>
  95. <hr
  96. class={$page.url.pathname.includes('/admin')
  97. ? 'block border-1 w-2/3 mx-auto border-immich-primary dark:border-immich-dark-primary'
  98. : 'hidden'}
  99. />
  100. </div>
  101. </a>
  102. {/if}
  103. <div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
  104. <button
  105. class="flex place-items-center place-content-center rounded-full bg-immich-primary hover:bg-immich-primary/80 h-12 w-12 text-gray-100 dark:text-immich-dark-bg dark:bg-immich-dark-primary"
  106. on:mouseover={() => (shouldShowAccountInfo = true)}
  107. on:focus={() => (shouldShowAccountInfo = true)}
  108. on:blur={() => (shouldShowAccountInfo = false)}
  109. on:mouseleave={() => (shouldShowAccountInfo = false)}
  110. on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
  111. >
  112. {#if user.profileImagePath}
  113. <img
  114. transition:fade={{ duration: 100 }}
  115. class:hidden={showProfilePictureFallback}
  116. src={`${$page.url.origin}/api/user/profile-image/${user.id}`}
  117. alt="profile-img"
  118. class="inline rounded-full h-12 w-12 object-cover shadow-md border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-all"
  119. draggable="false"
  120. on:load={() => (showProfilePictureFallback = false)}
  121. />
  122. {/if}
  123. {#if showProfilePictureFallback}
  124. {getFirstLetter(user.firstName)}{getFirstLetter(user.lastName)}
  125. {/if}
  126. </button>
  127. {#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}
  128. <div
  129. in:fade={{ delay: 500, duration: 150 }}
  130. out:fade={{ delay: 200, duration: 150 }}
  131. class="absolute -bottom-12 right-5 border bg-gray-500 dark:bg-immich-dark-gray text-[12px] text-gray-100 p-2 rounded-md shadow-md dark:border-immich-dark-gray"
  132. >
  133. <p>{user.firstName} {user.lastName}</p>
  134. <p>{user.email}</p>
  135. </div>
  136. {/if}
  137. {#if shouldShowAccountInfoPanel}
  138. <AccountInfoPanel {user} on:logout={logOut} />
  139. {/if}
  140. </div>
  141. </section>
  142. </div>
  143. </div>
  144. </section>