mirror of
https://github.com/immich-app/immich.git
synced 2025-01-16 00:36:47 +01:00
merge main
This commit is contained in:
commit
690b5e7f06
24 changed files with 160 additions and 135 deletions
|
@ -7,11 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
|
||||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
|
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run api:generate # Run from the `server/` directory
|
make open-api
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find the generated client SDK in the `web/src/api` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||||
|
|
||||||
:::tip
|
|
||||||
This can also be run via `make open-api` from the project root directory (not in the `server` folder)
|
|
||||||
:::
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.93.1"
|
version = "1.93.2"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -62,7 +62,7 @@ fi
|
||||||
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||||
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
|
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
|
||||||
npm --prefix server version $SERVER_PUMP
|
npm --prefix server version $SERVER_PUMP
|
||||||
npm --prefix server run api:generate
|
make open-api
|
||||||
poetry --directory machine-learning version $SERVER_PUMP
|
poetry --directory machine-learning version $SERVER_PUMP
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ platform :android do
|
||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 118,
|
"android.injected.version.code" => 119,
|
||||||
"android.injected.version.name" => "1.93.1",
|
"android.injected.version.name" => "1.93.2",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|
|
@ -19,7 +19,7 @@ platform :ios do
|
||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.93.1"
|
version_number: "1.93.2"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|
|
@ -197,7 +197,7 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
label: "control_bottom_app_bar_edit_location".tr(),
|
label: "control_bottom_app_bar_edit_location".tr(),
|
||||||
onPressed: enabled ? onEditLocation : null,
|
onPressed: enabled ? onEditLocation : null,
|
||||||
),
|
),
|
||||||
if (!hasLocal &&
|
if (!selectionAssetState.hasLocal &&
|
||||||
selectionAssetState.selectedCount > 1 &&
|
selectionAssetState.selectedCount > 1 &&
|
||||||
onStack != null)
|
onStack != null)
|
||||||
ControlBoxButton(
|
ControlBoxButton(
|
||||||
|
@ -211,7 +211,7 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
label: 'album_viewer_appbar_share_remove'.tr(),
|
label: 'album_viewer_appbar_share_remove'.tr(),
|
||||||
onPressed: enabled ? onRemoveFromAlbum : null,
|
onPressed: enabled ? onRemoveFromAlbum : null,
|
||||||
),
|
),
|
||||||
if (hasLocal)
|
if (selectionAssetState.hasLocal)
|
||||||
ControlBoxButton(
|
ControlBoxButton(
|
||||||
iconData: Icons.backup_outlined,
|
iconData: Icons.backup_outlined,
|
||||||
label: "Upload",
|
label: "Upload",
|
||||||
|
|
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
|
@ -2,7 +2,7 @@ name: immich_mobile
|
||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 1.93.1+118
|
version: 1.93.2+119
|
||||||
isar_version: &isar_version 3.1.0+1
|
isar_version: &isar_version 3.1.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -6221,7 +6221,7 @@
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.93.1",
|
"version": "1.93.2",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|
2
open-api/typescript-sdk/client/api.ts
generated
2
open-api/typescript-sdk/client/api.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.93.0
|
* The version of the OpenAPI document: 1.93.2
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
open-api/typescript-sdk/client/base.ts
generated
2
open-api/typescript-sdk/client/base.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.93.0
|
* The version of the OpenAPI document: 1.93.2
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
open-api/typescript-sdk/client/common.ts
generated
2
open-api/typescript-sdk/client/common.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.93.0
|
* The version of the OpenAPI document: 1.93.2
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
open-api/typescript-sdk/client/configuration.ts
generated
2
open-api/typescript-sdk/client/configuration.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.93.0
|
* The version of the OpenAPI document: 1.93.2
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
open-api/typescript-sdk/client/index.ts
generated
2
open-api/typescript-sdk/client/index.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.93.0
|
* The version of the OpenAPI document: 1.93.2
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
4
server/package-lock.json
generated
4
server/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.93.1",
|
"version": "1.93.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.93.1",
|
"version": "1.93.2",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.22.11",
|
"@babel/runtime": "^7.22.11",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.93.1",
|
"version": "1.93.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -1014,8 +1014,6 @@ describe(PersonService.name, () => {
|
||||||
oldPersonId: personStub.mergePerson.id,
|
oldPersonId: personStub.mergePerson.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(personMock.update).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1040,7 +1038,6 @@ describe(PersonService.name, () => {
|
||||||
name: personStub.primaryPerson.name,
|
name: personStub.primaryPerson.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(personMock.delete).toHaveBeenCalledWith([personStub.primaryPerson]);
|
|
||||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Slider from '$lib/components/elements/slider.svelte';
|
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let subtitle = '';
|
export let subtitle = '';
|
||||||
|
@ -11,6 +10,7 @@
|
||||||
export let isEdited = false;
|
export let isEdited = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ toggle: boolean }>();
|
const dispatch = createEventDispatcher<{ toggle: boolean }>();
|
||||||
|
const onToggle = (event: Event) => dispatch('toggle', (event.target as HTMLInputElement).checked);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex place-items-center justify-between">
|
<div class="flex place-items-center justify-between">
|
||||||
|
@ -31,5 +31,67 @@
|
||||||
|
|
||||||
<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
|
<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<Slider bind:checked {disabled} on:toggle={() => dispatch('toggle', checked)} />
|
|
||||||
|
<label class="relative inline-block h-[10px] w-[36px] flex-none">
|
||||||
|
<input
|
||||||
|
class="disabled::cursor-not-allowed h-0 w-0 opacity-0"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked
|
||||||
|
on:click={onToggle}
|
||||||
|
{disabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if disabled}
|
||||||
|
<span class="slider slider-disabled cursor-not-allowed" />
|
||||||
|
{:else}
|
||||||
|
<span class="slider slider-enabled cursor-pointer" />
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: -4px;
|
||||||
|
background-color: gray;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
-webkit-transform: translateX(18px);
|
||||||
|
-ms-transform: translateX(18px);
|
||||||
|
transform: translateX(18px);
|
||||||
|
background-color: #4250af;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider-disabled {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider-enabled {
|
||||||
|
background-color: #adcbfa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { mdiLink, mdiShareCircle } from '@mdi/js';
|
import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
|
@ -60,28 +60,25 @@
|
||||||
</span>
|
</span>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<div class="immich-scrollbar max-h-[300px] overflow-y-auto">
|
{#if selectedUsers.length > 0}
|
||||||
{#if selectedUsers.length > 0}
|
<div class="mb-2 flex flex-wrap place-items-center gap-4 overflow-x-auto px-5 py-2 sticky">
|
||||||
<div class="mb-2 flex place-items-center gap-4 overflow-x-auto px-5 py-2">
|
<p class="font-medium">To</p>
|
||||||
<p class="font-medium">To</p>
|
|
||||||
|
|
||||||
{#each selectedUsers as user}
|
{#each selectedUsers as user}
|
||||||
{#key user.id}
|
{#key user.id}
|
||||||
<button
|
<button
|
||||||
on:click={() => handleUnselect(user)}
|
on:click={() => handleUnselect(user)}
|
||||||
class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
|
class="flex place-items-center gap-1 rounded-full border border-gray-500 p-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
<UserAvatar {user} size="sm" />
|
<UserAvatar {user} size="sm" />
|
||||||
<p class="text-xs font-medium">{user.name}</p>
|
<p class="text-xs font-medium">{user.name}</p>
|
||||||
</button>
|
</button>
|
||||||
{/key}
|
{/key}
|
||||||
{/each}
|
{/each}
|
||||||
<div class="flex place-content-end mr-0 ml-auto p-5">
|
</div>
|
||||||
<Button size="sm" rounded="lg" on:click={() => dispatch('select', selectedUsers)}>Add</Button>
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
<div class="immich-scrollbar max-h-[500px] overflow-y-auto">
|
||||||
{#if users.length > 0}
|
{#if users.length > 0}
|
||||||
<p class="px-5 text-xs font-medium">SUGGESTIONS</p>
|
<p class="px-5 text-xs font-medium">SUGGESTIONS</p>
|
||||||
|
|
||||||
|
@ -92,10 +89,11 @@
|
||||||
class="flex w-full place-items-center gap-4 px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
class="flex w-full place-items-center gap-4 px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
{#if selectedUsers.includes(user)}
|
{#if selectedUsers.includes(user)}
|
||||||
<span
|
<div
|
||||||
class="flex h-12 w-12 place-content-center place-items-center rounded-full border bg-immich-primary text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-primary dark:text-immich-dark-bg"
|
class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-primary text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-primary dark:text-immich-dark-bg"
|
||||||
>✓</span
|
|
||||||
>
|
>
|
||||||
|
<Icon path={mdiCheck} size={24} />
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<UserAvatar {user} size="md" />
|
<UserAvatar {user} size="md" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -118,7 +116,20 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if users.length > 0}
|
||||||
|
<div class="p-3">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
fullwidth
|
||||||
|
rounded="full"
|
||||||
|
disabled={!selectedUsers.length}
|
||||||
|
on:click={() => dispatch('select', selectedUsers)}>Add</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div id="shared-buttons" class="my-4 flex place-content-center place-items-center justify-around">
|
<div id="shared-buttons" class="my-4 flex place-content-center place-items-center justify-around">
|
||||||
<button
|
<button
|
||||||
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
||||||
|
|
|
@ -172,12 +172,12 @@
|
||||||
</div>
|
</div>
|
||||||
{#if innerHeight}
|
{#if innerHeight}
|
||||||
<div
|
<div
|
||||||
class="overflow-y-auto immich-scrollbar relative w-full"
|
class="overflow-y-auto immich-scrollbar relative w-full px-2"
|
||||||
style="height: {divHeight}px;padding-bottom: {chatHeight}px"
|
style="height: {divHeight}px;padding-bottom: {chatHeight}px"
|
||||||
>
|
>
|
||||||
{#each reactions as reaction, index (reaction.id)}
|
{#each reactions as reaction, index (reaction.id)}
|
||||||
{#if reaction.type === 'comment'}
|
{#if reaction.type === 'comment'}
|
||||||
<div class="flex dark:bg-gray-800 bg-gray-200 p-3 mx-2 mt-3 rounded-lg gap-4 justify-start">
|
<div class="flex dark:bg-gray-800 bg-gray-200 py-3 pl-3 mt-3 rounded-lg gap-4 justify-start">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<UserAvatar user={reaction.user} size="sm" />
|
<UserAvatar user={reaction.user} size="sm" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -215,7 +215,7 @@
|
||||||
|
|
||||||
{#if (index != reactions.length - 1 && !shouldGroup(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
{#if (index != reactions.length - 1 && !shouldGroup(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
||||||
<div
|
<div
|
||||||
class=" px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
||||||
title={new Date(reaction.createdAt).toLocaleDateString(undefined, timeOptions)}
|
title={new Date(reaction.createdAt).toLocaleDateString(undefined, timeOptions)}
|
||||||
>
|
>
|
||||||
{timeSince(luxon.DateTime.fromISO(reaction.createdAt))}
|
{timeSince(luxon.DateTime.fromISO(reaction.createdAt))}
|
||||||
|
@ -223,7 +223,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{:else if reaction.type === 'like'}
|
{:else if reaction.type === 'like'}
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex p-3 mx-2 mt-3 rounded-full gap-4 items-center text-sm">
|
<div class="flex py-3 pl-3 mt-3 gap-4 items-center text-sm">
|
||||||
<div class="text-red-600"><Icon path={mdiHeart} size={20} /></div>
|
<div class="text-red-600"><Icon path={mdiHeart} size={20} /></div>
|
||||||
|
|
||||||
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
|
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
|
||||||
|
@ -260,7 +260,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if (index != reactions.length - 1 && isTenMinutesApart(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
{#if (index != reactions.length - 1 && isTenMinutesApart(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
||||||
<div
|
<div
|
||||||
class=" px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
||||||
title={new Date(reaction.createdAt).toLocaleDateString(navigator.language, timeOptions)}
|
title={new Date(reaction.createdAt).toLocaleDateString(navigator.language, timeOptions)}
|
||||||
>
|
>
|
||||||
{timeSince(luxon.DateTime.fromISO(reaction.createdAt))}
|
{timeSince(luxon.DateTime.fromISO(reaction.createdAt))}
|
||||||
|
@ -274,7 +274,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute w-full bottom-0">
|
<div class="absolute w-full bottom-0">
|
||||||
<div class="flex items-center justify-center p-2 mr-2" bind:clientHeight={chatHeight}>
|
<div class="flex items-center justify-center p-2" bind:clientHeight={chatHeight}>
|
||||||
<div class="flex p-2 gap-4 h-fit bg-gray-200 text-immich-dark-gray rounded-3xl w-full">
|
<div class="flex p-2 gap-4 h-fit bg-gray-200 text-immich-dark-gray rounded-3xl w-full">
|
||||||
<div>
|
<div>
|
||||||
<UserAvatar {user} size="md" showTitle={false} />
|
<UserAvatar {user} size="md" showTitle={false} />
|
||||||
|
|
|
@ -741,7 +741,7 @@
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 150 }}
|
transition:fly={{ duration: 150 }}
|
||||||
id="activity-panel"
|
id="activity-panel"
|
||||||
class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg pl-4"
|
class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<ActivityViewer
|
<ActivityViewer
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
export let checked = false;
|
|
||||||
export let disabled = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ toggle: boolean }>();
|
|
||||||
const onToggle = (event: Event) => dispatch('toggle', (event.target as HTMLInputElement).checked);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<label class="relative inline-block h-[10px] w-[36px] flex-none">
|
|
||||||
<input
|
|
||||||
class="disabled::cursor-not-allowed h-0 w-0 opacity-0"
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked
|
|
||||||
on:click={onToggle}
|
|
||||||
{disabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if disabled}
|
|
||||||
<span class="slider slider-disabled cursor-not-allowed" />
|
|
||||||
{:else}
|
|
||||||
<span class="slider slider-enabled cursor-pointer" />
|
|
||||||
{/if}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.slider {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #ccc;
|
|
||||||
-webkit-transition: 0.4s;
|
|
||||||
transition: 0.4s;
|
|
||||||
border-radius: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider:before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: -4px;
|
|
||||||
background-color: gray;
|
|
||||||
-webkit-transition: 0.4s;
|
|
||||||
transition: 0.4s;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider:before {
|
|
||||||
-webkit-transform: translateX(18px);
|
|
||||||
-ms-transform: translateX(18px);
|
|
||||||
transform: translateX(18px);
|
|
||||||
background-color: #4250af;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider-disabled {
|
|
||||||
background-color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider-enabled {
|
|
||||||
background-color: #adcbfa;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -100,6 +100,7 @@
|
||||||
let reactions: ActivityResponseDto[] = [];
|
let reactions: ActivityResponseDto[] = [];
|
||||||
let globalWidth: number;
|
let globalWidth: number;
|
||||||
let assetGridWidth: number;
|
let assetGridWidth: number;
|
||||||
|
let textarea: HTMLTextAreaElement;
|
||||||
|
|
||||||
const assetStore = new AssetStore({ albumId: album.id });
|
const assetStore = new AssetStore({ albumId: album.id });
|
||||||
const assetInteractionStore = createAssetInteractionStore();
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
|
@ -120,7 +121,13 @@
|
||||||
$: showActivityStatus =
|
$: showActivityStatus =
|
||||||
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
|
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
|
||||||
|
|
||||||
afterNavigate(({ from }) => {
|
$: {
|
||||||
|
if (textarea) {
|
||||||
|
textarea.value = album.description;
|
||||||
|
autoGrowHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: afterNavigate(({ from }) => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
|
|
||||||
let url: string | undefined = from?.url?.pathname;
|
let url: string | undefined = from?.url?.pathname;
|
||||||
|
@ -140,6 +147,13 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const autoGrowHeight = () => {
|
||||||
|
// little hack so that the height of the text area is correctly initialized
|
||||||
|
textarea.scrollHeight;
|
||||||
|
textarea.style.height = '5px';
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleEnableActivity = async () => {
|
const handleToggleEnableActivity = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.albumApi.updateAlbumInfo({
|
const { data } = await api.albumApi.updateAlbumInfo({
|
||||||
|
@ -634,7 +648,12 @@
|
||||||
disabled={!isOwned}
|
disabled={!isOwned}
|
||||||
title="Edit description"
|
title="Edit description"
|
||||||
>
|
>
|
||||||
{album.description || 'Add description'}
|
<textarea
|
||||||
|
class="w-full bg-transparent resize-none overflow-hidden outline-none"
|
||||||
|
bind:this={textarea}
|
||||||
|
bind:value={album.description}
|
||||||
|
placeholder="Add description"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
@ -678,7 +697,7 @@
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 150 }}
|
transition:fly={{ duration: 150 }}
|
||||||
id="activity-panel"
|
id="activity-panel"
|
||||||
class="z-[2] w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg pl-4"
|
class="z-[2] w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<ActivityViewer
|
<ActivityViewer
|
||||||
|
@ -751,3 +770,15 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<UpdatePanel {assetStore} />
|
<UpdatePanel {assetStore} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
::placeholder {
|
||||||
|
color: rgb(60, 60, 60);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-ms-input-placeholder {
|
||||||
|
/* Edge 12 -18 */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
{#if shouldShowDeleteConfirmDialog}
|
{#if shouldShowDeleteConfirmDialog}
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
user={selectedUser}
|
user={selectedUser}
|
||||||
on:succes={onUserDeleteSuccess}
|
on:success={onUserDeleteSuccess}
|
||||||
on:fail={onUserDeleteFail}
|
on:fail={onUserDeleteFail}
|
||||||
on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
|
on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue