mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
refactor: admin sidebar (#18276)
This commit is contained in:
parent
4efc41d5d9
commit
4445288758
10
web/package-lock.json
generated
10
web/package-lock.json
generated
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.19.1",
|
||||
"@immich/ui": "^0.20.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
@ -88,7 +88,7 @@
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/node": "^22.15.16",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
@ -1337,9 +1337,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@immich/ui": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.19.1.tgz",
|
||||
"integrity": "sha512-PyJ+OAEgBu1HTScMMui2KpBjMYkCw3nhVloYorOaB5lKOlNh7mqz5xBCNo/UVwxLXyAOFuBLU05lv3hWNveSKQ==",
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.20.0.tgz",
|
||||
"integrity": "sha512-euK3N0AhQLB28qFteorRKyDUdet3UpA9MEAd8eBLbTtTFZKvZismBGa4J7pHbQrSkuOlbmJD5LJuM575q8zigQ==",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
|
@ -27,7 +27,7 @@
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.19.1",
|
||||
"@immich/ui": "^0.20.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
|
@ -31,7 +31,6 @@
|
||||
--immich-ui-danger: 200 60 60;
|
||||
--immich-ui-warning: 216 143 64;
|
||||
--immich-ui-info: 8 111 230;
|
||||
--immich-ui-default-border: 209 213 219;
|
||||
--immich-ui-gray: 246 246 246;
|
||||
}
|
||||
|
||||
@ -44,7 +43,6 @@
|
||||
--immich-ui-success: 72 237 152;
|
||||
--immich-ui-warning: 254 197 132;
|
||||
--immich-ui-info: 121 183 254;
|
||||
--immich-ui-default-border: 55 65 81;
|
||||
--immich-ui-gray: 33 33 33;
|
||||
}
|
||||
}
|
||||
|
43
web/src/lib/components/layouts/AdminPageLayout.svelte
Normal file
43
web/src/lib/components/layouts/AdminPageLayout.svelte
Normal file
@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import PageContent from '$lib/components/layouts/PageContent.svelte';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||
import { AppShell, AppShellHeader, AppShellSidebar, NavbarItem, type Size } from '@immich/ui';
|
||||
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync } from '@mdi/js';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
size?: Size | 'full';
|
||||
buttons?: Snippet;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { title, size, buttons, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<AppShell>
|
||||
<AppShellHeader>
|
||||
<NavigationBar showUploadButton={false} noBorder />
|
||||
</AppShellHeader>
|
||||
<AppShellSidebar bind:open={sidebarStore.isOpen}>
|
||||
<div class="h-full flex flex-col justify-between gap-2">
|
||||
<div class="flex flex-col pt-8 pe-4 gap-1">
|
||||
<NavbarItem title={$t('users')} href={AppRoute.ADMIN_USERS} icon={mdiAccountMultipleOutline} />
|
||||
<NavbarItem title={$t('jobs')} href={AppRoute.ADMIN_JOBS} icon={mdiSync} />
|
||||
<NavbarItem title={$t('settings')} href={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
||||
<NavbarItem title={$t('external_libraries')} href={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
|
||||
<NavbarItem title={$t('server_stats')} href={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2 me-4">
|
||||
<BottomInfo />
|
||||
</div>
|
||||
</div>
|
||||
</AppShellSidebar>
|
||||
|
||||
<PageContent {title} {size} {buttons} {children} />
|
||||
</AppShell>
|
26
web/src/lib/components/layouts/PageContent.svelte
Normal file
26
web/src/lib/components/layouts/PageContent.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Container, Scrollable, type Size } from '@immich/ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
title?: string;
|
||||
size?: Size | 'full';
|
||||
buttons?: Snippet;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { title, size, buttons, children, id }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="flex h-16 w-full place-items-center justify-between border-b p-2">
|
||||
<div class="font-medium outline-none" tabindex="-1" {id}>{title}</div>
|
||||
{@render buttons?.()}
|
||||
</div>
|
||||
<Scrollable class="grow">
|
||||
<Container {size} class="flex flex-col p-4">
|
||||
{@render children?.()}
|
||||
</Container>
|
||||
</Scrollable>
|
||||
</div>
|
@ -5,7 +5,6 @@
|
||||
<script lang="ts">
|
||||
import { useActions, type ActionArray } from '$lib/actions/use-actions';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||
import AdminSideBar from '$lib/components/shared-components/side-bar/admin-side-bar.svelte';
|
||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import type { Snippet } from 'svelte';
|
||||
@ -16,7 +15,6 @@
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
scrollbar?: boolean;
|
||||
admin?: boolean;
|
||||
use?: ActionArray;
|
||||
header?: Snippet;
|
||||
sidebar?: Snippet;
|
||||
@ -30,7 +28,6 @@
|
||||
title = undefined,
|
||||
description = undefined,
|
||||
scrollbar = true,
|
||||
admin = false,
|
||||
use = [],
|
||||
header,
|
||||
sidebar,
|
||||
@ -58,8 +55,6 @@
|
||||
>
|
||||
{#if sidebar}
|
||||
{@render sidebar()}
|
||||
{:else if admin}
|
||||
<AdminSideBar />
|
||||
{:else}
|
||||
<SideBar />
|
||||
{/if}
|
||||
|
@ -30,10 +30,12 @@
|
||||
|
||||
interface Props {
|
||||
showUploadButton?: boolean;
|
||||
onUploadClick: () => void;
|
||||
onUploadClick?: () => void;
|
||||
// TODO: remove once this is only used in <AppShellHeader>
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
let { showUploadButton = true, onUploadClick }: Props = $props();
|
||||
let { showUploadButton = true, onUploadClick, noBorder = false }: Props = $props();
|
||||
|
||||
let shouldShowAccountInfoPanel = $state(false);
|
||||
let shouldShowNotificationPanel = $state(false);
|
||||
@ -55,7 +57,9 @@
|
||||
>
|
||||
<SkipLink text={$t('skip_to_content')} />
|
||||
<div
|
||||
class="grid h-full grid-cols-[theme(spacing.32)_auto] items-center border-b py-2 dark:border-b-immich-dark-gray sidebar:grid-cols-[theme(spacing.64)_auto]"
|
||||
class="grid h-full grid-cols-[theme(spacing.32)_auto] items-center py-2 dark:border-b-immich-dark-gray sidebar:grid-cols-[theme(spacing.64)_auto] {noBorder
|
||||
? ''
|
||||
: 'border-b'}"
|
||||
>
|
||||
<div class="flex flex-row gap-1 mx-4 items-center">
|
||||
<IconButton
|
||||
@ -103,7 +107,7 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !page.url.pathname.includes('/admin') && showUploadButton}
|
||||
{#if !page.url.pathname.includes('/admin') && showUploadButton && onUploadClick}
|
||||
<Button
|
||||
leadingIcon={mdiTrayArrowUp}
|
||||
onclick={onUploadClick}
|
||||
|
@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
||||
import SideBarLink from '$lib/components/shared-components/side-bar/side-bar-link.svelte';
|
||||
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SideBarSection ariaLabel={$t('primary')}>
|
||||
<SideBarLink title={$t('users')} routeId={AppRoute.ADMIN_USERS} icon={mdiAccountMultipleOutline} />
|
||||
<SideBarLink title={$t('jobs')} routeId={AppRoute.ADMIN_JOBS} icon={mdiSync} />
|
||||
<SideBarLink title={$t('settings')} routeId={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
||||
<SideBarLink title={$t('external_libraries')} routeId={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
|
||||
<SideBarLink title={$t('server_stats')} routeId={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
||||
|
||||
<BottomInfo />
|
||||
</SideBarSection>
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import JobCreateModal from '$lib/modals/JobCreateModal.svelte';
|
||||
@ -34,7 +34,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<HStack gap={0}>
|
||||
<Button
|
||||
@ -64,4 +64,4 @@
|
||||
{/if}
|
||||
</section>
|
||||
</section>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
@ -4,7 +4,7 @@
|
||||
import LibraryRenameForm from '$lib/components/forms/library-rename-form.svelte';
|
||||
import LibraryScanSettingsForm from '$lib/components/forms/library-scan-settings-form.svelte';
|
||||
import LibraryUserPickerForm from '$lib/components/forms/library-user-picker-form.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
@ -240,7 +240,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<div class="flex justify-end gap-2">
|
||||
{#if libraries.length > 0}
|
||||
@ -363,7 +363,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
||||
{#if renameLibrary !== undefined}
|
||||
<LibraryRenameForm
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import { asyncTimeout } from '$lib/utils';
|
||||
import { getServerStatistics } from '@immich/sdk';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { asyncTimeout } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@ -26,10 +26,10 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
||||
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
|
||||
<ServerStatsPanel stats={data.stats} />
|
||||
</section>
|
||||
</section>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
@ -19,7 +19,7 @@
|
||||
import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte';
|
||||
import UserSettings from '$lib/components/admin-page/settings/user-settings/user-settings.svelte';
|
||||
import SearchBar from '$lib/components/elements/search-bar.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import SettingAccordionState from '$lib/components/shared-components/settings/setting-accordion-state.svelte';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
|
||||
import { QueryParameter } from '$lib/constants';
|
||||
@ -241,7 +241,7 @@
|
||||
|
||||
<input bind:this={inputElement} type="file" accept=".json" style="display: none" onchange={uploadConfig} />
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<HStack gap={1}>
|
||||
<div class="hidden lg:block">
|
||||
@ -301,4 +301,4 @@
|
||||
</section>
|
||||
{/snippet}
|
||||
</AdminSettings>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import {
|
||||
NotificationType,
|
||||
notificationController,
|
||||
@ -85,7 +85,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<HStack gap={1}>
|
||||
<Button leadingIcon={mdiPlusBoxOutline} onclick={handleCreate} size="small" variant="ghost" color="secondary">
|
||||
@ -172,4 +172,4 @@
|
||||
</table>
|
||||
</section>
|
||||
</section>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import StatsCard from '$lib/components/admin-page/server-stats/stats-card.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType,
|
||||
@ -166,7 +166,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} admin>
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<HStack gap={0}>
|
||||
{#if canResetPassword}
|
||||
@ -365,4 +365,4 @@
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</UserPageLayout>
|
||||
</AdminPageLayout>
|
||||
|
@ -40,7 +40,7 @@ export default {
|
||||
},
|
||||
borderColor: ({ theme }) => ({
|
||||
...theme('colors'),
|
||||
DEFAULT: 'rgb(var(--immich-ui-default-border) / <alpha-value>)',
|
||||
DEFAULT: 'rgb(var(--immich-ui-gray) / <alpha-value>)',
|
||||
}),
|
||||
fontFamily: {
|
||||
'immich-mono': ['Overpass Mono', 'monospace'],
|
||||
|
Loading…
x
Reference in New Issue
Block a user