navigation-bar.svelte 4.5 KB

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