navigation-bar.svelte 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. import UserAvatar from '../user-avatar.svelte';
  19. export let user: UserResponseDto;
  20. export let showUploadButton = true;
  21. let shouldShowAccountInfo = false;
  22. let shouldShowAccountInfoPanel = false;
  23. const dispatch = createEventDispatcher();
  24. const logOut = async () => {
  25. const { data } = await api.authenticationApi.logout();
  26. await fetch('/auth/logout', { method: 'POST' });
  27. goto(data.redirectUri || '/auth/login?autoLaunch=0');
  28. };
  29. </script>
  30. <section id="dashboard-navbar" class="fixed z-[900] h-[var(--navbar-height)] w-screen text-sm">
  31. <div
  32. class="grid h-full grid-cols-[theme(spacing.18)_auto] items-center border-b bg-immich-bg py-2 dark:border-b-immich-dark-gray dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
  33. >
  34. <a data-sveltekit-preload-data="hover" class="mx-4 flex place-items-center gap-2 md:mx-6" href={AppRoute.PHOTOS}>
  35. <ImmichLogo height="35" width="35" />
  36. <h1 class="hidden font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary md:block">
  37. IMMICH
  38. </h1>
  39. </a>
  40. <div class="flex justify-between gap-16 pr-6">
  41. <div class="hidden w-full max-w-5xl flex-1 pl-4 sm:block">
  42. <SearchBar grayTheme={true} />
  43. </div>
  44. <section class="flex place-items-center justify-end gap-4 max-sm:w-full">
  45. <a href={AppRoute.SEARCH} id="search-button" class="pl-4 sm:hidden">
  46. <IconButton title="Search">
  47. <div class="flex gap-2">
  48. <Magnify size="1.5em" />
  49. </div>
  50. </IconButton>
  51. </a>
  52. <ThemeButton />
  53. {#if !$page.url.pathname.includes('/admin') && showUploadButton}
  54. <div in:fly={{ x: 50, duration: 250 }}>
  55. <LinkButton on:click={() => dispatch('uploadClicked')}>
  56. <div class="flex gap-2">
  57. <TrayArrowUp size="1.5em" />
  58. <span class="hidden md:block">Upload</span>
  59. </div>
  60. </LinkButton>
  61. </div>
  62. {/if}
  63. {#if user.isAdmin}
  64. <a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
  65. <div class="hidden sm:block">
  66. <LinkButton>
  67. <span
  68. class={$page.url.pathname.includes('/admin')
  69. ? 'item text-immich-primary underline dark:text-immich-dark-primary'
  70. : ''}
  71. >
  72. Administration
  73. </span>
  74. </LinkButton>
  75. </div>
  76. <div class="block sm:hidden">
  77. <IconButton title="Administration">
  78. <Cog
  79. size="1.5em"
  80. class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
  81. ? 'text-immich-primary dark:text-immich-dark-primary'
  82. : ''}"
  83. />
  84. </IconButton>
  85. <hr
  86. class={$page.url.pathname.includes('/admin')
  87. ? 'border-1 mx-auto block w-2/3 border-immich-primary dark:border-immich-dark-primary'
  88. : 'hidden'}
  89. />
  90. </div>
  91. </a>
  92. {/if}
  93. <div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
  94. <button
  95. class="flex"
  96. on:mouseover={() => (shouldShowAccountInfo = true)}
  97. on:focus={() => (shouldShowAccountInfo = true)}
  98. on:blur={() => (shouldShowAccountInfo = false)}
  99. on:mouseleave={() => (shouldShowAccountInfo = false)}
  100. on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
  101. >
  102. <UserAvatar {user} size="md" showTitle={false} interactive />
  103. </button>
  104. {#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}
  105. <div
  106. in:fade={{ delay: 500, duration: 150 }}
  107. out:fade={{ delay: 200, duration: 150 }}
  108. class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
  109. >
  110. <p>{user.firstName} {user.lastName}</p>
  111. <p>{user.email}</p>
  112. </div>
  113. {/if}
  114. {#if shouldShowAccountInfoPanel}
  115. <AccountInfoPanel {user} on:logout={logOut} />
  116. {/if}
  117. </div>
  118. </section>
  119. </div>
  120. </div>
  121. </section>