1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

fix(web): user management responsive design (#5698)

* fix: user management tailwind

* use top instead of inset-y-0

* add types to createEventDispatcher
This commit is contained in:
martin 2023-12-14 17:55:15 +01:00 committed by GitHub
parent 8e39d389b5
commit f2270ad757
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 257 additions and 135 deletions

View File

@ -22,6 +22,7 @@
import SettingAccordion from '../setting-accordion.svelte';
import { mdiHelpCircleOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
export let disabled = false;
@ -63,6 +64,14 @@
}
}
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
@ -354,9 +363,8 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -9,6 +9,7 @@
import { handleError } from '../../../../utils/handle-error';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let jobConfig: SystemConfigJobDto; // this is the config that is being edited
export let disabled = false;
@ -29,6 +30,14 @@
JobName.Migration,
];
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.job),
@ -101,9 +110,8 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -11,6 +11,7 @@
import { fade } from 'svelte/transition';
import { handleError } from '../../../../utils/handle-error';
import SettingAccordion from '../setting-accordion.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let libraryConfig: SystemConfigLibraryDto; // this is the config that is being edited
export let disabled = false;
@ -25,6 +26,14 @@
let savedConfig: SystemConfigLibraryDto;
let defaultConfig: SystemConfigLibraryDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.library),
@ -131,9 +140,8 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -12,6 +12,7 @@
import SettingSwitch from '../setting-switch.svelte';
import SettingAccordion from '../setting-accordion.svelte';
import SettingSelect from '../setting-select.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let machineLearningConfig: SystemConfigMachineLearningDto; // this is the config that is being edited
export let disabled = false;
@ -33,6 +34,14 @@
notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info });
}
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function saveSetting() {
try {
const { data: current } = await api.systemConfigApi.getConfig();
@ -212,9 +221,8 @@
</SettingAccordion>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -11,6 +11,7 @@
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let config: SystemConfigDto; // this is the config that is being edited
export let disabled = false;
@ -18,6 +19,14 @@
let savedConfig: SystemConfigDto;
let defaultConfig: SystemConfigDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function refreshConfig() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data),
@ -133,9 +142,8 @@
>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(
{ ...savedConfig.map, ...savedConfig.reverseGeocoding },
{ ...defaultConfig.map, ...defaultConfig.reverseGeocoding },

View File

@ -9,6 +9,7 @@
import { fade } from 'svelte/transition';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let newVersionCheckConfig: SystemConfigNewVersionCheckDto; // this is the config that is being edited
@ -22,6 +23,14 @@
]);
}
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function saveSetting() {
try {
const { data: configs } = await api.systemConfigApi.getConfig();
@ -79,9 +88,8 @@
bind:checked={newVersionCheckConfig.enabled}
/>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
/>
</div>

View File

@ -11,6 +11,7 @@
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let oauthConfig: SystemConfigOAuthDto;
export let disabled = false;
@ -18,6 +19,14 @@
let savedConfig: SystemConfigOAuthDto;
let defaultConfig: SystemConfigOAuthDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
const handleToggleOverride = () => {
// click runs before bind
const previouslyEnabled = oauthConfig.mobileOverrideEnabled;
@ -209,9 +218,8 @@
{/if}
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -10,6 +10,7 @@
import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited
export let disabled = false;
@ -17,6 +18,14 @@
let savedConfig: SystemConfigPasswordLoginDto;
let defaultConfig: SystemConfigPasswordLoginDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin),
@ -107,9 +116,8 @@
/>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -1,8 +1,12 @@
<script lang="ts">
import Button from '$lib/components/elements/buttons/button.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
reset: ResetOptions;
save: void;
}>();
export let showResetToDefault = true;
export let disabled = false;
@ -12,7 +16,7 @@
<div class="left">
{#if showResetToDefault}
<button
on:click={() => dispatch('reset-to-default')}
on:click={() => dispatch('reset', { default: true })}
class="bg-none text-sm font-medium text-immich-primary hover:text-immich-primary/75 dark:text-immich-dark-primary hover:dark:text-immich-dark-primary/75"
>
Reset to default
@ -21,7 +25,7 @@
</div>
<div class="right">
<Button {disabled} size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
<Button {disabled} size="sm" color="gray" on:click={() => dispatch('reset', { default: false })}>Reset</Button>
<Button {disabled} size="sm" on:click={() => dispatch('save')}>Save</Button>
</div>
</div>

View File

@ -14,6 +14,7 @@
} from '$lib/components/shared-components/notification/notification';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import { user } from '$lib/stores/user.store';
import type { ResetOptions } from '$lib/utils/dipatch';
export let storageConfig: SystemConfigStorageTemplateDto;
export let disabled = false;
@ -23,6 +24,14 @@
let templateOptions: SystemConfigTemplateStorageOptionDto;
let selectedPreset = '';
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig, templateOptions] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
@ -232,9 +241,8 @@
</div>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -9,6 +9,7 @@
import { fade } from 'svelte/transition';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingTextarea from '../setting-textarea.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let themeConfig: SystemConfigThemeDto; // this is the config that is being edited
export let disabled = false;
@ -16,6 +17,14 @@
let savedConfig: SystemConfigThemeDto;
let defaultConfig: SystemConfigThemeDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.theme),
@ -84,9 +93,8 @@
/>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -10,6 +10,7 @@
} from '$lib/components/shared-components/notification/notification';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited
export let disabled = false;
@ -17,6 +18,14 @@
let savedConfig: SystemConfigThumbnailDto;
let defaultConfig: SystemConfigThumbnailDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.thumbnail),
@ -133,9 +142,8 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -10,6 +10,7 @@
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let trashConfig: SystemConfigTrashDto; // this is the config that is being edited
export let disabled = false;
@ -17,6 +18,14 @@
let savedConfig: SystemConfigTrashDto;
let defaultConfig: SystemConfigTrashDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.trash),
@ -90,9 +99,8 @@
/>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View File

@ -50,7 +50,12 @@
let message = '';
let isSendingMessage = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
deleteComment: void;
deleteLike: void;
addComment: void;
close: void;
}>();
$: showDeleteReaction = Array(reactions.length).fill(false);
$: {

View File

@ -725,9 +725,9 @@
albumId={album?.id}
albums={appearsInAlbums}
on:close={() => ($isShowDetail = false)}
on:close-viewer={handleCloseViewer}
on:description-focus-in={disableKeyDownEvent}
on:description-focus-out={enableKeyDownEvent}
on:closeViewer={handleCloseViewer}
on:descriptionFocusIn={disableKeyDownEvent}
on:descriptionFocusOut={enableKeyDownEvent}
/>
</div>
{/if}

View File

@ -87,7 +87,13 @@
unsubscribe();
});
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
descriptionFocusIn: void;
descriptionFocusOut: void;
click: AlbumResponseDto;
closeViewer: void;
}>();
const getMegapixel = (width: number, height: number): number | undefined => {
const megapixel = Math.round((height * width) / 1_000_000);
@ -114,11 +120,11 @@
};
const handleFocusIn = () => {
dispatch('description-focus-in');
dispatch('descriptionFocusIn');
};
const handleFocusOut = async () => {
dispatch('description-focus-out');
dispatch('descriptionFocusOut');
try {
await api.assetApi.updateAsset({
id: asset.id,
@ -241,7 +247,7 @@
href="{AppRoute.PEOPLE}/{person.id}?previousRoute={albumId
? `${AppRoute.ALBUMS}/${albumId}`
: AppRoute.PHOTOS}"
on:click={() => dispatch('close-viewer')}
on:click={() => dispatch('closeViewer')}
>
<div class="relative">
<ImageThumbnail

View File

@ -11,7 +11,13 @@
let intersecting = false;
let container: HTMLDivElement;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
hidden: HTMLDivElement;
intersected: {
container: HTMLDivElement;
position: BucketPosition;
};
}>();
onMount(() => {
if (typeof IntersectionObserver !== 'undefined') {

View File

@ -8,7 +8,10 @@
export let album: AlbumResponseDto;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
editSuccess: void;
cancel: void;
}>();
const editUser = async () => {
try {
@ -21,7 +24,7 @@
});
if (status === 200) {
dispatch('edit-success');
dispatch('editSuccess');
}
} catch (error) {
handleError(error, 'Unable to update user');

View File

@ -6,8 +6,9 @@
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { handleError } from '../../utils/handle-error';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiAccountEditOutline } from '@mdi/js';
import { mdiAccountEditOutline, mdiClose } from '@mdi/js';
import { AppRoute } from '$lib/constants';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
export let user: UserResponseDto;
export let canResetPassword = true;
@ -17,7 +18,11 @@
let isShowResetPasswordConfirmation = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
resetPasswordSuccess: void;
editSuccess: void;
}>();
const editUser = async () => {
try {
@ -33,7 +38,7 @@
});
if (status === 200) {
dispatch('edit-success');
dispatch('editSuccess');
}
} catch (error) {
handleError(error, 'Unable to update user');
@ -53,7 +58,7 @@
});
if (status == 200) {
dispatch('reset-password-success');
dispatch('resetPasswordSuccess');
}
} catch (e) {
console.error('Error reseting user password', e);
@ -68,8 +73,12 @@
</script>
<div
class="max-h-screen w-[500px] max-w-[95vw] overflow-y-auto rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
class="relative max-h-screen w-[500px] max-w-[95vw] overflow-y-auto rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div class="absolute top-0 right-0 px-2 py-2 h-fit">
<CircleIconButton icon={mdiClose} on:click={() => dispatch('close')} />
</div>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>

View File

@ -16,7 +16,10 @@
let loading = false;
let oauthLoading = true;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
success: void;
firstLogin: void;
}>();
onMount(async () => {
if (!$featureFlags.oauth) {
@ -62,7 +65,7 @@
});
if (!data.isAdmin && data.shouldChangePassword) {
dispatch('first-login');
dispatch('firstLogin');
return;
}

View File

@ -6,7 +6,9 @@
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
let showModal = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
escape: void;
}>();
const { getAssets } = getAssetControlContext();
const escape = () => {
dispatch('escape');

View File

@ -19,7 +19,9 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
escape: void;
}>();
let isShowConfirmation = false;
let loading = false;

View File

@ -12,7 +12,18 @@
let loading = true;
let search = '';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
newAlbum: {
albumName: string;
};
album: {
album: AlbumResponseDto;
};
newSharedAlbum: {
albumName: string;
};
close: void;
}>();
export let shared: boolean;

View File

@ -7,7 +7,10 @@
import { clickOutside } from '$lib/utils/click-outside';
import { mdiClose } from '@mdi/js';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
escape: void;
close: void;
}>();
export let zIndex = 9999;
export let ignoreClickOutside = false;

View File

@ -29,7 +29,10 @@
let canCopyImagesToClipboard = true;
let enablePassword = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
escape: void;
}>();
const expiredDateOption: ImmichDropDownOption = {
default: 'Never',

View File

@ -11,7 +11,9 @@
let viewWidth: number;
$: thumbnailSize = getThumbnailSize(assets.length, viewWidth);
let dispatch = createEventDispatcher();
let dispatch = createEventDispatcher<{
select: { asset: AssetResponseDto; selectedAssets: Set<AssetResponseDto> };
}>();
const selectAssetHandler = (event: CustomEvent) => {
const { asset }: { asset: AssetResponseDto } = event.detail;

View File

@ -16,7 +16,10 @@
let isShowSelectAvatar = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
logout: void;
close: void;
}>();
const handleSaveProfile = async (color: UserAvatarColor) => {
try {

View File

@ -8,7 +8,10 @@
export let user: UserResponseDto;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
choose: UserAvatarColor;
}>();
const colors: UserAvatarColor[] = Object.values(UserAvatarColor);
</script>

View File

@ -23,7 +23,9 @@
let shouldShowAccountInfo = false;
let shouldShowAccountInfoPanel = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
uploadClicked: void;
}>();
const logOut = async () => {
const { data } = await api.authenticationApi.logout();

View File

@ -10,7 +10,9 @@
export let asset: AssetResponseDto;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
}>();
let imgElement: HTMLDivElement;
onMount(() => {

View File

@ -19,7 +19,9 @@
{ key: ['Del'], action: 'Delete Asset' },
],
};
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
}>();
</script>
<FullScreenModal on:clickOutside={() => dispatch('close')} on:escape={() => dispatch('close')}>

View File

@ -11,7 +11,9 @@
let showMoreInformation = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
selected: void;
}>();
const onButtonClicked = () => dispatch('selected');
</script>

View File

@ -17,7 +17,9 @@
export let device: AuthDeviceResponseDto;
const dispatcher = createEventDispatcher();
const dispatcher = createEventDispatcher<{
delete: void;
}>();
const options: ToRelativeCalendarOptions = {
unit: 'days',

View File

@ -0,0 +1,3 @@
export interface ResetOptions {
default?: boolean;
}

View File

@ -239,7 +239,7 @@
<FullScreenModal on:clickOutside={() => (shouldShowEditUserForm = false)}>
<EditAlbumForm
album={selectedAlbum}
on:edit-success={() => successModifyAlbum()}
on:editSuccess={() => successModifyAlbum()}
on:cancel={() => (shouldShowEditUserForm = false)}
/>
</FullScreenModal>

View File

@ -107,7 +107,7 @@
<UserPageLayout title={data.meta.title} admin>
<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]">
<section class="w-full pb-28 lg:w-[850px]">
{#if shouldShowCreateUserForm}
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
<CreateUserForm on:user-created={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} />
@ -119,8 +119,9 @@
<EditUserForm
user={selectedUser}
canResetPassword={selectedUser?.id !== $user.id}
on:edit-success={onEditUserSuccess}
on:editSuccess={onEditUserSuccess}
on:reset-password-success={onEditPasswordSuccess}
on:close={() => (shouldShowEditUserForm = false)}
/>
</FullScreenModal>
{/if}
@ -163,32 +164,34 @@
</FullScreenModal>
{/if}
<table class="my-5 hidden w-full text-left sm:block">
<table class="my-5 w-full text-left">
<thead
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
>
<tr class="flex w-full place-items-center">
<th class="w-4/12 text-center text-sm font-medium">Email</th>
<th class="w-2/12 text-center text-sm font-medium">Name</th>
<th class="w-2/12 text-center text-sm font-medium">Can import</th>
<th class="w-2/12 text-center text-sm font-medium">Action</th>
<th class="w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-center text-sm font-medium">Email</th>
<th class="hidden sm:block w-3/12 text-center text-sm font-medium">Name</th>
<th class="hidden xl:block w-3/12 2xl:w-2/12 text-center text-sm font-medium">Can import</th>
<th class="w-4/12 lg:w-3/12 xl:w-2/12 text-center text-sm font-medium">Action</th>
</tr>
</thead>
<tbody class="block max-h-[320px] w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#if allUsers}
{#each allUsers as immichUser, i}
<tr
class={`flex h-[80px] w-full place-items-center text-center dark:text-immich-dark-fg ${
isDeleted(immichUser)
? 'bg-red-300 dark:bg-red-900'
: i % 2 == 0
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`}
class="flex h-[80px] overflow-hidden w-full place-items-center text-center dark:text-immich-dark-fg {isDeleted(
immichUser,
)
? 'bg-red-300 dark:bg-red-900'
: i % 2 == 0
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
: 'bg-immich-bg dark:bg-immich-dark-gray/50'}"
>
<td class="w-4/12 text-ellipsis break-all px-2 text-sm">{immichUser.email}</td>
<td class="w-2/12 text-ellipsis break-all px-2 text-sm">{immichUser.name}</td>
<td class="w-2/12 text-ellipsis break-all px-2 text-sm">
<td class="w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-ellipsis break-all px-2 text-sm"
>{immichUser.email}</td
>
<td class="hidden sm:block w-3/12 text-ellipsis break-all px-2 text-sm">{immichUser.name}</td>
<td class="hidden xl:block w-3/12 2xl:w-2/12 text-ellipsis break-all px-2 text-sm">
<div class="container mx-auto flex flex-wrap justify-center">
{#if immichUser.externalPath}
<Icon path={mdiCheck} size="16" />
@ -197,18 +200,18 @@
{/if}
</div>
</td>
<td class="w-2/12 text-ellipsis break-all px-4 text-sm">
<td class="w-4/12 lg:w-3/12 xl:w-2/12 text-ellipsis break-all px-4 text-sm">
{#if !isDeleted(immichUser)}
<button
on:click={() => editUserHandler(immichUser)}
class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
class="rounded-full bg-immich-primary p-2 sm:p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700 max-sm:mb-1"
>
<Icon path={mdiPencilOutline} size="16" />
</button>
{#if immichUser.id !== $user.id}
<button
on:click={() => deleteUserHandler(immichUser)}
class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
class="rounded-full bg-immich-primary p-2 sm:p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
>
<Icon path={mdiTrashCanOutline} size="16" />
</button>
@ -218,62 +221,7 @@
<button
on:click={() => restoreUserHandler(immichUser)}
class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
title={`scheduled removal on ${getDeleteDate(immichUser)}`}
>
<Icon path={mdiDeleteRestore} size="16" />
</button>
{/if}
</td>
</tr>
{/each}
{/if}
</tbody>
</table>
<table class="my-5 block w-full text-left sm:hidden">
<thead
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
>
<tr class="flex w-full place-items-center">
<th class="w-1/4 text-center text-sm font-medium">Name</th>
<th class="w-1/2 text-center text-sm font-medium">Email</th>
<th class="w-1/4 text-center text-sm font-medium">Action</th>
</tr>
</thead>
<tbody class="block max-h-[320px] w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#if allUsers}
{#each allUsers as user, i}
<tr
class={`flex h-[80px] w-full place-items-center text-center dark:text-immich-dark-fg ${
isDeleted(user)
? 'bg-red-300 dark:bg-red-900'
: i % 2 == 0
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`}
>
<td class="w-1/4 text-ellipsis break-words px-2 text-sm">{user.name}</td>
<td class="w-1/2 text-ellipsis break-all px-2 text-sm">{user.email}</td>
<td class="w-1/4 text-ellipsis px-2 text-sm">
{#if !isDeleted(user)}
<button
on:click={() => editUserHandler(user)}
class="rounded-full bg-immich-primary p-2 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700 max-sm:mb-1 sm:p-3"
>
<Icon path={mdiPencilOutline} size="16" />
</button>
<button
on:click={() => deleteUserHandler(user)}
class="rounded-full bg-immich-primary p-2 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700 sm:p-3"
>
<Icon path={mdiTrashCanOutline} size="16" />
</button>
{/if}
{#if isDeleted(user)}
<button
on:click={() => restoreUserHandler(user)}
class="rounded-full bg-immich-primary p-2 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700 sm:p-3"
title={`scheduled removal on ${getDeleteDate(user)}`}
title="scheduled removal on {getDeleteDate(immichUser)}"
>
<Icon path={mdiDeleteRestore} size="16" />
</button>