chore(web): prettier (#2821)

Co-authored-by: Thomas Way <thomas@6f.io>
This commit is contained in:
Jason Rasmussen 2023-07-01 00:50:47 -04:00 committed by GitHub
parent 7c2f7d6c51
commit f55b3add80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
242 changed files with 12794 additions and 13426 deletions

View file

@ -1,80 +1,78 @@
<script lang="ts">
import {
notificationController,
NotificationType
} from '$lib/components/shared-components/notification/notification';
import { api, ApiError } from '@api';
import { fade } from 'svelte/transition';
import SettingInputField, {
SettingInputFieldType
} from '../admin-page/settings/setting-input-field.svelte';
import Button from '../elements/buttons/button.svelte';
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { api, ApiError } from '@api';
import { fade } from 'svelte/transition';
import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
import Button from '../elements/buttons/button.svelte';
let password = '';
let newPassword = '';
let confirmPassword = '';
let password = '';
let newPassword = '';
let confirmPassword = '';
const handleChangePassword = async () => {
try {
await api.authenticationApi.changePassword({
changePasswordDto: {
password,
newPassword
}
});
const handleChangePassword = async () => {
try {
await api.authenticationApi.changePassword({
changePasswordDto: {
password,
newPassword,
},
});
notificationController.show({
message: 'Updated password',
type: NotificationType.Info
});
notificationController.show({
message: 'Updated password',
type: NotificationType.Info,
});
password = '';
newPassword = '';
confirmPassword = '';
} catch (error) {
console.error('Error [user-profile] [changePassword]', error);
notificationController.show({
message: (error as ApiError)?.response?.data?.message || 'Unable to change password',
type: NotificationType.Error
});
}
};
password = '';
newPassword = '';
confirmPassword = '';
} catch (error) {
console.error('Error [user-profile] [changePassword]', error);
notificationController.show({
message: (error as ApiError)?.response?.data?.message || 'Unable to change password',
type: NotificationType.Error,
});
}
};
</script>
<section class="my-4">
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4 ml-4 mt-4">
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="PASSWORD"
bind:value={password}
required={true}
/>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4 ml-4 mt-4">
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="PASSWORD"
bind:value={password}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="NEW PASSWORD"
bind:value={newPassword}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="NEW PASSWORD"
bind:value={newPassword}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="CONFIRM PASSWORD"
bind:value={confirmPassword}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.PASSWORD}
label="CONFIRM PASSWORD"
bind:value={confirmPassword}
required={true}
/>
<div class="flex justify-end">
<Button
type="submit"
size="sm"
disabled={!(password && newPassword && newPassword === confirmPassword)}
on:click={() => handleChangePassword()}>Save</Button
>
</div>
</div>
</form>
</div>
<div class="flex justify-end">
<Button
type="submit"
size="sm"
disabled={!(password && newPassword && newPassword === confirmPassword)}
on:click={() => handleChangePassword()}>Save</Button
>
</div>
</div>
</form>
</div>
</section>

View file

@ -1,72 +1,70 @@
<script lang="ts">
import { locale } from '$lib/stores/preferences.store';
import type { AuthDeviceResponseDto } from '@api';
import { DateTime, ToRelativeCalendarOptions } from 'luxon';
import { createEventDispatcher } from 'svelte';
import Android from 'svelte-material-icons/Android.svelte';
import Apple from 'svelte-material-icons/Apple.svelte';
import AppleSafari from 'svelte-material-icons/AppleSafari.svelte';
import GoogleChrome from 'svelte-material-icons/GoogleChrome.svelte';
import Help from 'svelte-material-icons/Help.svelte';
import Linux from 'svelte-material-icons/Linux.svelte';
import MicrosoftWindows from 'svelte-material-icons/MicrosoftWindows.svelte';
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
import { locale } from '$lib/stores/preferences.store';
import type { AuthDeviceResponseDto } from '@api';
import { DateTime, ToRelativeCalendarOptions } from 'luxon';
import { createEventDispatcher } from 'svelte';
import Android from 'svelte-material-icons/Android.svelte';
import Apple from 'svelte-material-icons/Apple.svelte';
import AppleSafari from 'svelte-material-icons/AppleSafari.svelte';
import GoogleChrome from 'svelte-material-icons/GoogleChrome.svelte';
import Help from 'svelte-material-icons/Help.svelte';
import Linux from 'svelte-material-icons/Linux.svelte';
import MicrosoftWindows from 'svelte-material-icons/MicrosoftWindows.svelte';
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
export let device: AuthDeviceResponseDto;
export let device: AuthDeviceResponseDto;
const dispatcher = createEventDispatcher();
const dispatcher = createEventDispatcher();
const options: ToRelativeCalendarOptions = {
unit: 'days',
locale: $locale
};
const options: ToRelativeCalendarOptions = {
unit: 'days',
locale: $locale,
};
</script>
<div class="flex flex-row w-full">
<!-- TODO: Device Image -->
<div
class="hidden sm:flex pr-2 justify-center items-center text-immich-primary dark:text-immich-dark-primary"
>
{#if device.deviceOS === 'Android'}
<Android size="40" />
{:else if device.deviceOS === 'iOS' || device.deviceOS === 'Mac OS'}
<Apple size="40" />
{:else if device.deviceOS.indexOf('Safari') !== -1}
<AppleSafari size="40" />
{:else if device.deviceOS.indexOf('Windows') !== -1}
<MicrosoftWindows size="40" />
{:else if device.deviceOS === 'Linux'}
<Linux size="40" />
{:else if device.deviceOS === 'Chromium OS' || device.deviceType === 'Chrome' || device.deviceType === 'Chromium'}
<GoogleChrome size="40" />
{:else}
<Help size="40" />
{/if}
</div>
<div class="pl-4 sm:pl-0 flex flex-row grow justify-between gap-1">
<div class="flex flex-col gap-1 justify-center dark:text-white">
<span class="text-sm">
{#if device.deviceType || device.deviceOS}
<span>{device.deviceOS || 'Unknown'}{device.deviceType || 'Unknown'}</span>
{:else}
<span>Unknown</span>
{/if}
</span>
<div class="text-sm">
<span class="">Last seen</span>
<span>{DateTime.fromISO(device.updatedAt).toRelativeCalendar(options)}</span>
</div>
</div>
{#if !device.current}
<div class="text-sm flex flex-col justify-center">
<button
on:click={() => dispatcher('delete')}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
title="Log out"
>
<TrashCanOutline size="16" />
</button>
</div>
{/if}
</div>
<!-- TODO: Device Image -->
<div class="hidden sm:flex pr-2 justify-center items-center text-immich-primary dark:text-immich-dark-primary">
{#if device.deviceOS === 'Android'}
<Android size="40" />
{:else if device.deviceOS === 'iOS' || device.deviceOS === 'Mac OS'}
<Apple size="40" />
{:else if device.deviceOS.indexOf('Safari') !== -1}
<AppleSafari size="40" />
{:else if device.deviceOS.indexOf('Windows') !== -1}
<MicrosoftWindows size="40" />
{:else if device.deviceOS === 'Linux'}
<Linux size="40" />
{:else if device.deviceOS === 'Chromium OS' || device.deviceType === 'Chrome' || device.deviceType === 'Chromium'}
<GoogleChrome size="40" />
{:else}
<Help size="40" />
{/if}
</div>
<div class="pl-4 sm:pl-0 flex flex-row grow justify-between gap-1">
<div class="flex flex-col gap-1 justify-center dark:text-white">
<span class="text-sm">
{#if device.deviceType || device.deviceOS}
<span>{device.deviceOS || 'Unknown'}{device.deviceType || 'Unknown'}</span>
{:else}
<span>Unknown</span>
{/if}
</span>
<div class="text-sm">
<span class="">Last seen</span>
<span>{DateTime.fromISO(device.updatedAt).toRelativeCalendar(options)}</span>
</div>
</div>
{#if !device.current}
<div class="text-sm flex flex-col justify-center">
<button
on:click={() => dispatcher('delete')}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
title="Log out"
>
<TrashCanOutline size="16" />
</button>
</div>
{/if}
</div>
</div>

View file

@ -1,102 +1,93 @@
<script lang="ts">
import { api, AuthDeviceResponseDto } from '@api';
import { onMount } from 'svelte';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import {
notificationController,
NotificationType
} from '../shared-components/notification/notification';
import DeviceCard from './device-card.svelte';
import { api, AuthDeviceResponseDto } from '@api';
import { onMount } from 'svelte';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
import DeviceCard from './device-card.svelte';
let devices: AuthDeviceResponseDto[] = [];
let deleteDevice: AuthDeviceResponseDto | null = null;
let deleteAll = false;
let devices: AuthDeviceResponseDto[] = [];
let deleteDevice: AuthDeviceResponseDto | null = null;
let deleteAll = false;
const refresh = () => api.authenticationApi.getAuthDevices().then(({ data }) => (devices = data));
const refresh = () => api.authenticationApi.getAuthDevices().then(({ data }) => (devices = data));
onMount(() => {
refresh();
});
onMount(() => {
refresh();
});
$: currentDevice = devices.find((device) => device.current);
$: otherDevices = devices.filter((device) => !device.current);
$: currentDevice = devices.find((device) => device.current);
$: otherDevices = devices.filter((device) => !device.current);
const handleDelete = async () => {
if (!deleteDevice) {
return;
}
const handleDelete = async () => {
if (!deleteDevice) {
return;
}
try {
await api.authenticationApi.logoutAuthDevice({ id: deleteDevice.id });
notificationController.show({ message: `Logged out device`, type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to log out device');
} finally {
await refresh();
deleteDevice = null;
}
};
try {
await api.authenticationApi.logoutAuthDevice({ id: deleteDevice.id });
notificationController.show({ message: `Logged out device`, type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to log out device');
} finally {
await refresh();
deleteDevice = null;
}
};
const handleDeleteAll = async () => {
try {
await api.authenticationApi.logoutAuthDevices();
notificationController.show({
message: `Logged out all devices`,
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to log out all devices');
} finally {
await refresh();
deleteAll = false;
}
};
const handleDeleteAll = async () => {
try {
await api.authenticationApi.logoutAuthDevices();
notificationController.show({
message: `Logged out all devices`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to log out all devices');
} finally {
await refresh();
deleteAll = false;
}
};
</script>
{#if deleteDevice}
<ConfirmDialogue
prompt="Are you sure you want to log out this device?"
on:confirm={() => handleDelete()}
on:cancel={() => (deleteDevice = null)}
/>
<ConfirmDialogue
prompt="Are you sure you want to log out this device?"
on:confirm={() => handleDelete()}
on:cancel={() => (deleteDevice = null)}
/>
{/if}
{#if deleteAll}
<ConfirmDialogue
prompt="Are you sure you want to log out all devices?"
on:confirm={() => handleDeleteAll()}
on:cancel={() => (deleteAll = false)}
/>
<ConfirmDialogue
prompt="Are you sure you want to log out all devices?"
on:confirm={() => handleDeleteAll()}
on:cancel={() => (deleteAll = false)}
/>
{/if}
<section class="my-4">
{#if currentDevice}
<div class="mb-6">
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
CURRENT DEVICE
</h3>
<DeviceCard device={currentDevice} />
</div>
{/if}
{#if otherDevices.length > 0}
<div class="mb-6">
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
OTHER DEVICES
</h3>
{#each otherDevices as device, i}
<DeviceCard {device} on:delete={() => (deleteDevice = device)} />
{#if i !== otherDevices.length - 1}
<hr class="my-3" />
{/if}
{/each}
</div>
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
LOG OUT ALL DEVICES
</h3>
<div class="flex justify-end">
<Button color="red" size="sm" on:click={() => (deleteAll = true)}>Log Out All Devices</Button>
</div>
{/if}
{#if currentDevice}
<div class="mb-6">
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">CURRENT DEVICE</h3>
<DeviceCard device={currentDevice} />
</div>
{/if}
{#if otherDevices.length > 0}
<div class="mb-6">
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3>
{#each otherDevices as device, i}
<DeviceCard {device} on:delete={() => (deleteDevice = device)} />
{#if i !== otherDevices.length - 1}
<hr class="my-3" />
{/if}
{/each}
</div>
<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">LOG OUT ALL DEVICES</h3>
<div class="flex justify-end">
<Button color="red" size="sm" on:click={() => (deleteAll = true)}>Log Out All Devices</Button>
</div>
{/if}
</section>

View file

@ -1,80 +1,77 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { oauth, OAuthConfigResponseDto, UserResponseDto } from '@api';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import {
notificationController,
NotificationType
} from '../shared-components/notification/notification';
import Button from '../elements/buttons/button.svelte';
import { goto } from '$app/navigation';
import { oauth, OAuthConfigResponseDto, UserResponseDto } from '@api';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
import Button from '../elements/buttons/button.svelte';
export let user: UserResponseDto;
export let user: UserResponseDto;
let config: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: true };
let loading = true;
let config: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: true };
let loading = true;
onMount(async () => {
if (oauth.isCallback(window.location)) {
try {
loading = true;
onMount(async () => {
if (oauth.isCallback(window.location)) {
try {
loading = true;
const { data } = await oauth.link(window.location);
user = data;
const { data } = await oauth.link(window.location);
user = data;
notificationController.show({
message: 'Linked OAuth account',
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to link OAuth account');
} finally {
goto('?open=oauth');
}
}
notificationController.show({
message: 'Linked OAuth account',
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to link OAuth account');
} finally {
goto('?open=oauth');
}
}
try {
const { data } = await oauth.getConfig(window.location);
config = data;
} catch (error) {
handleError(error, 'Unable to load OAuth config');
}
try {
const { data } = await oauth.getConfig(window.location);
config = data;
} catch (error) {
handleError(error, 'Unable to load OAuth config');
}
loading = false;
});
loading = false;
});
const handleUnlink = async () => {
try {
const { data } = await oauth.unlink();
user = data;
notificationController.show({
message: 'Unlinked OAuth account',
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to unlink account');
}
};
const handleUnlink = async () => {
try {
const { data } = await oauth.unlink();
user = data;
notificationController.show({
message: 'Unlinked OAuth account',
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to unlink account');
}
};
</script>
<section class="my-4">
<div in:fade={{ duration: 500 }}>
<div class="flex justify-end">
{#if loading}
<div class="flex place-items-center place-content-center">
<LoadingSpinner />
</div>
{:else if config.enabled}
{#if user.oauthId}
<Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
{:else}
<a href={config.url}>
<Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button>
</a>
{/if}
{/if}
</div>
</div>
<div in:fade={{ duration: 500 }}>
<div class="flex justify-end">
{#if loading}
<div class="flex place-items-center place-content-center">
<LoadingSpinner />
</div>
{:else if config.enabled}
{#if user.oauthId}
<Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
{:else}
<a href={config.url}>
<Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button>
</a>
{/if}
{/if}
</div>
</div>
</section>

View file

@ -1,87 +1,85 @@
<script lang="ts">
import { api, UserResponseDto } from '@api';
import BaseModal from '../shared-components/base-modal.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import Button from '../elements/buttons/button.svelte';
import { createEventDispatcher, onMount } from 'svelte';
import { api, UserResponseDto } from '@api';
import BaseModal from '../shared-components/base-modal.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import Button from '../elements/buttons/button.svelte';
import { createEventDispatcher, onMount } from 'svelte';
export let user: UserResponseDto;
export let user: UserResponseDto;
let availableUsers: UserResponseDto[] = [];
let selectedUsers: UserResponseDto[] = [];
let availableUsers: UserResponseDto[] = [];
let selectedUsers: UserResponseDto[] = [];
const dispatch = createEventDispatcher<{ close: void; 'add-users': UserResponseDto[] }>();
const dispatch = createEventDispatcher<{ close: void; 'add-users': UserResponseDto[] }>();
onMount(async () => {
// TODO: update endpoint to have a query param for deleted users
let { data: users } = await api.userApi.getAllUsers({ isAll: false });
onMount(async () => {
// TODO: update endpoint to have a query param for deleted users
let { data: users } = await api.userApi.getAllUsers({ isAll: false });
// remove invalid users
users = users.filter((_user) => !(_user.deletedAt || _user.id === user.id));
// remove invalid users
users = users.filter((_user) => !(_user.deletedAt || _user.id === user.id));
// exclude partners from the list of users available for selection
const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-by' });
const partnerIds = partners.map((partner) => partner.id);
availableUsers = users.filter((user) => !partnerIds.includes(user.id));
});
// exclude partners from the list of users available for selection
const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-by' });
const partnerIds = partners.map((partner) => partner.id);
availableUsers = users.filter((user) => !partnerIds.includes(user.id));
});
const selectUser = (user: UserResponseDto) => {
if (selectedUsers.includes(user)) {
selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
} else {
selectedUsers = [...selectedUsers, user];
}
};
const selectUser = (user: UserResponseDto) => {
if (selectedUsers.includes(user)) {
selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
} else {
selectedUsers = [...selectedUsers, user];
}
};
</script>
<BaseModal on:close={() => dispatch('close')}>
<svelte:fragment slot="title">
<span class="flex gap-2 place-items-center">
<ImmichLogo width={24} />
<p class="font-medium">Add partner</p>
</span>
</svelte:fragment>
<svelte:fragment slot="title">
<span class="flex gap-2 place-items-center">
<ImmichLogo width={24} />
<p class="font-medium">Add partner</p>
</span>
</svelte:fragment>
<div class="max-h-[300px] overflow-y-auto immich-scrollbar">
{#if availableUsers.length > 0}
{#each availableUsers as user}
<button
on:click={() => selectUser(user)}
class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
>
{#if selectedUsers.includes(user)}
<span
class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
>✓</span
>
{:else}
<UserAvatar {user} size="md" autoColor />
{/if}
<div class="max-h-[300px] overflow-y-auto immich-scrollbar">
{#if availableUsers.length > 0}
{#each availableUsers as user}
<button
on:click={() => selectUser(user)}
class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
>
{#if selectedUsers.includes(user)}
<span
class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
>✓</span
>
{:else}
<UserAvatar {user} size="md" autoColor />
{/if}
<div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg">
{user.firstName}
{user.lastName}
</p>
<p class="text-xs">
{user.email}
</p>
</div>
</button>
{/each}
{:else}
<p class="text-sm p-5">
Looks like you shared your photos with all users or you don't have any user to share with.
</p>
{/if}
<div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg">
{user.firstName}
{user.lastName}
</p>
<p class="text-xs">
{user.email}
</p>
</div>
</button>
{/each}
{:else}
<p class="text-sm p-5">
Looks like you shared your photos with all users or you don't have any user to share with.
</p>
{/if}
{#if selectedUsers.length > 0}
<div class="flex place-content-end p-5">
<Button size="sm" rounded="lg" on:click={() => dispatch('add-users', selectedUsers)}>
Add
</Button>
</div>
{/if}
</div>
{#if selectedUsers.length > 0}
<div class="flex place-content-end p-5">
<Button size="sm" rounded="lg" on:click={() => dispatch('add-users', selectedUsers)}>Add</Button>
</div>
{/if}
</div>
</BaseModal>

View file

@ -1,100 +1,100 @@
<script lang="ts">
import { UserResponseDto, api } from '@api';
import UserAvatar from '../shared-components/user-avatar.svelte';
import Close from 'svelte-material-icons/Close.svelte';
import Button from '../elements/buttons/button.svelte';
import PartnerSelectionModal from './partner-selection-modal.svelte';
import { handleError } from '../../utils/handle-error';
import { onMount } from 'svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { UserResponseDto, api } from '@api';
import UserAvatar from '../shared-components/user-avatar.svelte';
import Close from 'svelte-material-icons/Close.svelte';
import Button from '../elements/buttons/button.svelte';
import PartnerSelectionModal from './partner-selection-modal.svelte';
import { handleError } from '../../utils/handle-error';
import { onMount } from 'svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
export let user: UserResponseDto;
export let user: UserResponseDto;
let partners: UserResponseDto[] = [];
let createPartner = false;
let removePartner: UserResponseDto | null = null;
let partners: UserResponseDto[] = [];
let createPartner = false;
let removePartner: UserResponseDto | null = null;
const refreshPartners = async () => {
const { data } = await api.partnerApi.getPartners({ direction: 'shared-by' });
partners = data;
};
const refreshPartners = async () => {
const { data } = await api.partnerApi.getPartners({ direction: 'shared-by' });
partners = data;
};
const handleRemovePartner = async () => {
if (!removePartner) {
return;
}
const handleRemovePartner = async () => {
if (!removePartner) {
return;
}
try {
await api.partnerApi.removePartner({ id: removePartner.id });
removePartner = null;
await refreshPartners();
} catch (error) {
handleError(error, 'Unable to remove partner');
}
};
try {
await api.partnerApi.removePartner({ id: removePartner.id });
removePartner = null;
await refreshPartners();
} catch (error) {
handleError(error, 'Unable to remove partner');
}
};
const handleCreatePartners = async (users: UserResponseDto[]) => {
try {
for (const user of users) {
await api.partnerApi.createPartner({ id: user.id });
}
const handleCreatePartners = async (users: UserResponseDto[]) => {
try {
for (const user of users) {
await api.partnerApi.createPartner({ id: user.id });
}
await refreshPartners();
createPartner = false;
} catch (error) {
handleError(error, 'Unable to add partners');
}
};
await refreshPartners();
createPartner = false;
} catch (error) {
handleError(error, 'Unable to add partners');
}
};
onMount(async () => {
await refreshPartners();
});
onMount(async () => {
await refreshPartners();
});
</script>
<section class="my-4">
{#if partners.length > 0}
<div class="flex flex-row gap-4">
{#each partners as partner (partner.id)}
<div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
<UserAvatar user={partner} size="md" autoColor />
<div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg">
{partner.firstName}
{partner.lastName}
</p>
<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
{partner.email}
</p>
</div>
<CircleIconButton
on:click={() => (removePartner = partner)}
logo={Close}
size={'16'}
title="Remove partner"
/>
</div>
{/each}
</div>
{/if}
<div class="flex justify-end">
<Button size="sm" on:click={() => (createPartner = true)}>Add partner</Button>
</div>
{#if partners.length > 0}
<div class="flex flex-row gap-4">
{#each partners as partner (partner.id)}
<div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
<UserAvatar user={partner} size="md" autoColor />
<div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg">
{partner.firstName}
{partner.lastName}
</p>
<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
{partner.email}
</p>
</div>
<CircleIconButton
on:click={() => (removePartner = partner)}
logo={Close}
size={'16'}
title="Remove partner"
/>
</div>
{/each}
</div>
{/if}
<div class="flex justify-end">
<Button size="sm" on:click={() => (createPartner = true)}>Add partner</Button>
</div>
</section>
{#if createPartner}
<PartnerSelectionModal
{user}
on:close={() => (createPartner = false)}
on:add-users={(event) => handleCreatePartners(event.detail)}
/>
<PartnerSelectionModal
{user}
on:close={() => (createPartner = false)}
on:add-users={(event) => handleCreatePartners(event.detail)}
/>
{/if}
{#if removePartner}
<ConfirmDialogue
title="Stop sharing your photos?"
prompt="{removePartner.firstName} will no longer be able to access your photos."
on:cancel={() => (removePartner = null)}
on:confirm={() => handleRemovePartner()}
/>
<ConfirmDialogue
title="Stop sharing your photos?"
prompt="{removePartner.firstName} will no longer be able to access your photos."
on:cancel={() => (removePartner = null)}
on:confirm={() => handleRemovePartner()}
/>
{/if}

View file

@ -1,177 +1,167 @@
<script lang="ts">
import { api, APIKeyResponseDto } from '@api';
import { onMount } from 'svelte';
import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import APIKeyForm from '../forms/api-key-form.svelte';
import APIKeySecret from '../forms/api-key-secret.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import {
notificationController,
NotificationType
} from '../shared-components/notification/notification';
import { locale } from '$lib/stores/preferences.store';
import Button from '../elements/buttons/button.svelte';
import { api, APIKeyResponseDto } from '@api';
import { onMount } from 'svelte';
import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import APIKeyForm from '../forms/api-key-form.svelte';
import APIKeySecret from '../forms/api-key-secret.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
import { locale } from '$lib/stores/preferences.store';
import Button from '../elements/buttons/button.svelte';
let keys: APIKeyResponseDto[] = [];
let keys: APIKeyResponseDto[] = [];
let newKey: Partial<APIKeyResponseDto> | null = null;
let editKey: APIKeyResponseDto | null = null;
let deleteKey: APIKeyResponseDto | null = null;
let secret = '';
let newKey: Partial<APIKeyResponseDto> | null = null;
let editKey: APIKeyResponseDto | null = null;
let deleteKey: APIKeyResponseDto | null = null;
let secret = '';
const format: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric'
};
const format: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
onMount(() => {
refreshKeys();
});
onMount(() => {
refreshKeys();
});
async function refreshKeys() {
const { data } = await api.keyApi.getKeys();
keys = data;
}
async function refreshKeys() {
const { data } = await api.keyApi.getKeys();
keys = data;
}
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
try {
const dto = event.detail;
const { data } = await api.keyApi.createKey({ aPIKeyCreateDto: dto });
secret = data.secret;
} catch (error) {
handleError(error, 'Unable to create a new API Key');
} finally {
await refreshKeys();
newKey = null;
}
};
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
try {
const dto = event.detail;
const { data } = await api.keyApi.createKey({ aPIKeyCreateDto: dto });
secret = data.secret;
} catch (error) {
handleError(error, 'Unable to create a new API Key');
} finally {
await refreshKeys();
newKey = null;
}
};
const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
if (!editKey) {
return;
}
const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
if (!editKey) {
return;
}
const dto = event.detail;
const dto = event.detail;
try {
await api.keyApi.updateKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
notificationController.show({
message: `Saved API Key`,
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to save API Key');
} finally {
await refreshKeys();
editKey = null;
}
};
try {
await api.keyApi.updateKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
notificationController.show({
message: `Saved API Key`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to save API Key');
} finally {
await refreshKeys();
editKey = null;
}
};
const handleDelete = async () => {
if (!deleteKey) {
return;
}
const handleDelete = async () => {
if (!deleteKey) {
return;
}
try {
await api.keyApi.deleteKey({ id: deleteKey.id });
notificationController.show({
message: `Removed API Key: ${deleteKey.name}`,
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to remove API Key');
} finally {
await refreshKeys();
deleteKey = null;
}
};
try {
await api.keyApi.deleteKey({ id: deleteKey.id });
notificationController.show({
message: `Removed API Key: ${deleteKey.name}`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to remove API Key');
} finally {
await refreshKeys();
deleteKey = null;
}
};
</script>
{#if newKey}
<APIKeyForm
title="New API Key"
submitText="Create"
apiKey={newKey}
on:submit={handleCreate}
on:cancel={() => (newKey = null)}
/>
<APIKeyForm
title="New API Key"
submitText="Create"
apiKey={newKey}
on:submit={handleCreate}
on:cancel={() => (newKey = null)}
/>
{/if}
{#if secret}
<APIKeySecret {secret} on:done={() => (secret = '')} />
<APIKeySecret {secret} on:done={() => (secret = '')} />
{/if}
{#if editKey}
<APIKeyForm
submitText="Save"
apiKey={editKey}
on:submit={handleUpdate}
on:cancel={() => (editKey = null)}
/>
<APIKeyForm submitText="Save" apiKey={editKey} on:submit={handleUpdate} on:cancel={() => (editKey = null)} />
{/if}
{#if deleteKey}
<ConfirmDialogue
prompt="Are you sure you want to delete this API Key?"
on:confirm={() => handleDelete()}
on:cancel={() => (deleteKey = null)}
/>
<ConfirmDialogue
prompt="Are you sure you want to delete this API Key?"
on:confirm={() => handleDelete()}
on:cancel={() => (deleteKey = null)}
/>
{/if}
<section class="my-4">
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
<div class="flex justify-end mb-2">
<Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
</div>
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
<div class="flex justify-end mb-2">
<Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
</div>
{#if keys.length > 0}
<table class="text-left w-full">
<thead
class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
>
<tr class="flex w-full place-items-center">
<th class="text-center w-1/3 font-medium text-sm">Name</th>
<th class="text-center w-1/3 font-medium text-sm">Created</th>
<th class="text-center w-1/3 font-medium text-sm">Action</th>
</tr>
</thead>
<tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
{#each keys as key, i}
{#key key.id}
<tr
class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
i % 2 == 0
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`}
>
<td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
<td class="text-sm px-4 w-1/3 text-ellipsis"
>{new Date(key.createdAt).toLocaleDateString($locale, format)}
</td>
<td class="text-sm px-4 w-1/3 text-ellipsis">
<button
on:click={() => (editKey = key)}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
>
<PencilOutline size="16" />
</button>
<button
on:click={() => (deleteKey = key)}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
>
<TrashCanOutline size="16" />
</button>
</td>
</tr>
{/key}
{/each}
</tbody>
</table>
{/if}
</div>
{#if keys.length > 0}
<table class="text-left w-full">
<thead
class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
>
<tr class="flex w-full place-items-center">
<th class="text-center w-1/3 font-medium text-sm">Name</th>
<th class="text-center w-1/3 font-medium text-sm">Created</th>
<th class="text-center w-1/3 font-medium text-sm">Action</th>
</tr>
</thead>
<tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
{#each keys as key, i}
{#key key.id}
<tr
class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
i % 2 == 0 ? 'bg-immich-gray dark:bg-immich-dark-gray/75' : 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`}
>
<td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
<td class="text-sm px-4 w-1/3 text-ellipsis"
>{new Date(key.createdAt).toLocaleDateString($locale, format)}
</td>
<td class="text-sm px-4 w-1/3 text-ellipsis">
<button
on:click={() => (editKey = key)}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
>
<PencilOutline size="16" />
</button>
<button
on:click={() => (deleteKey = key)}
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
>
<TrashCanOutline size="16" />
</button>
</td>
</tr>
{/key}
{/each}
</tbody>
</table>
{/if}
</div>
</section>

View file

@ -1,92 +1,86 @@
<script lang="ts">
import {
notificationController,
NotificationType
} from '$lib/components/shared-components/notification/notification';
import { api, UserResponseDto } from '@api';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import SettingInputField, {
SettingInputFieldType
} from '../admin-page/settings/setting-input-field.svelte';
import Button from '../elements/buttons/button.svelte';
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { api, UserResponseDto } from '@api';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
import Button from '../elements/buttons/button.svelte';
export let user: UserResponseDto;
export let user: UserResponseDto;
const handleSaveProfile = async () => {
try {
const { data } = await api.userApi.updateUser({
updateUserDto: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName
}
});
const handleSaveProfile = async () => {
try {
const { data } = await api.userApi.updateUser({
updateUserDto: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
},
});
Object.assign(user, data);
Object.assign(user, data);
notificationController.show({
message: 'Saved profile',
type: NotificationType.Info
});
} catch (error) {
handleError(error, 'Unable to save profile');
}
};
notificationController.show({
message: 'Saved profile',
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to save profile');
}
};
</script>
<section class="my-4">
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4 ml-4 mt-4">
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="USER ID"
bind:value={user.id}
disabled={true}
/>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4 ml-4 mt-4">
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="USER ID"
bind:value={user.id}
disabled={true}
/>
<SettingInputField
inputType={SettingInputFieldType.EMAIL}
label="EMAIL"
bind:value={user.email}
/>
<SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={user.email} />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="FIRST NAME"
bind:value={user.firstName}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="FIRST NAME"
bind:value={user.firstName}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="LAST NAME"
bind:value={user.lastName}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="LAST NAME"
bind:value={user.lastName}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE LABEL"
disabled={true}
value={user.storageLabel || ''}
required={false}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE LABEL"
disabled={true}
value={user.storageLabel || ''}
required={false}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="EXTERNAL PATH"
disabled={true}
value={user.externalPath || ''}
required={false}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="EXTERNAL PATH"
disabled={true}
value={user.externalPath || ''}
required={false}
/>
<div class="flex justify-end">
<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
</div>
</div>
</form>
</div>
<div class="flex justify-end">
<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
</div>
</div>
</form>
</div>
</section>

View file

@ -1,58 +1,58 @@
<script lang="ts">
import { page } from '$app/stores';
import { oauth, UserResponseDto } from '@api';
import { onMount } from 'svelte';
import SettingAccordion from '../admin-page/settings/setting-accordion.svelte';
import ChangePasswordSettings from './change-password-settings.svelte';
import OAuthSettings from './oauth-settings.svelte';
import UserAPIKeyList from './user-api-key-list.svelte';
import DeviceList from './device-list.svelte';
import PartnerSettings from './partner-settings.svelte';
import UserProfileSettings from './user-profile-settings.svelte';
import { page } from '$app/stores';
import { oauth, UserResponseDto } from '@api';
import { onMount } from 'svelte';
import SettingAccordion from '../admin-page/settings/setting-accordion.svelte';
import ChangePasswordSettings from './change-password-settings.svelte';
import OAuthSettings from './oauth-settings.svelte';
import UserAPIKeyList from './user-api-key-list.svelte';
import DeviceList from './device-list.svelte';
import PartnerSettings from './partner-settings.svelte';
import UserProfileSettings from './user-profile-settings.svelte';
export let user: UserResponseDto;
export let user: UserResponseDto;
let oauthEnabled = false;
let oauthOpen = false;
let oauthEnabled = false;
let oauthOpen = false;
onMount(async () => {
oauthOpen = oauth.isCallback(window.location);
onMount(async () => {
oauthOpen = oauth.isCallback(window.location);
try {
const { data } = await oauth.getConfig(window.location);
oauthEnabled = data.enabled;
} catch {
// noop
}
});
try {
const { data } = await oauth.getConfig(window.location);
oauthEnabled = data.enabled;
} catch {
// noop
}
});
</script>
<SettingAccordion title="Account" subtitle="Manage your account">
<UserProfileSettings {user} />
<UserProfileSettings {user} />
</SettingAccordion>
<SettingAccordion title="API Keys" subtitle="Manage your API keys">
<UserAPIKeyList />
<UserAPIKeyList />
</SettingAccordion>
<SettingAccordion title="Authorized Devices" subtitle="Manage your logged-in devices">
<DeviceList />
<DeviceList />
</SettingAccordion>
{#if oauthEnabled}
<SettingAccordion
title="OAuth"
subtitle="Manage your OAuth connection"
isOpen={oauthOpen || $page.url.searchParams.get('open') === 'oauth'}
>
<OAuthSettings {user} />
</SettingAccordion>
<SettingAccordion
title="OAuth"
subtitle="Manage your OAuth connection"
isOpen={oauthOpen || $page.url.searchParams.get('open') === 'oauth'}
>
<OAuthSettings {user} />
</SettingAccordion>
{/if}
<SettingAccordion title="Password" subtitle="Change your password">
<ChangePasswordSettings />
<ChangePasswordSettings />
</SettingAccordion>
<SettingAccordion title="Sharing" subtitle="Manage sharing with partners">
<PartnerSettings {user} />
<PartnerSettings {user} />
</SettingAccordion>