mirror of
https://github.com/immich-app/immich.git
synced 2026-03-23 10:34:23 -07:00
Compare commits
2 Commits
refactor/z
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7e77422aa | ||
|
|
be93b9040c |
143
.github/workflows/auto-close.yml
vendored
Normal file
143
.github/workflows/auto-close.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
name: Auto-close PRs
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types: [opened, edited, labeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
parse_template:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action != 'labeled' && github.event.pull_request.head.repo.fork == true }}
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/pull_request_template.md
|
||||
sparse-checkout-cone-mode: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check required sections
|
||||
id: check
|
||||
env:
|
||||
BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
OK=true
|
||||
while IFS= read -r header; do
|
||||
printf '%s\n' "$BODY" | grep -qF "$header" || OK=false
|
||||
done < <(sed '/<!--/,/-->/d' .github/pull_request_template.md | grep "^## ")
|
||||
echo "uses_template=$OK" >> "$GITHUB_OUTPUT"
|
||||
|
||||
close_template:
|
||||
runs-on: ubuntu-latest
|
||||
needs: parse_template
|
||||
if: ${{ needs.parse_template.outputs.uses_template == 'false' && github.event.pull_request.state != 'closed' }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment and close
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \
|
||||
-f query='
|
||||
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
||||
addComment(input: {
|
||||
subjectId: $prId,
|
||||
body: $body
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
closePullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
|
||||
- name: Add label
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: gh pr edit "$PR_NUMBER" --add-label "auto-closed:template"
|
||||
|
||||
close_llm:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'auto-closed:llm' }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment and close
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our [CONTRIBUTING.md](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md#use-of-generative-ai), we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
|
||||
-f query='
|
||||
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
||||
addComment(input: {
|
||||
subjectId: $prId,
|
||||
body: $body
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
closePullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
|
||||
reopen:
|
||||
runs-on: ubuntu-latest
|
||||
needs: parse_template
|
||||
if: >-
|
||||
${{
|
||||
needs.parse_template.outputs.uses_template == 'true'
|
||||
&& github.event.pull_request.state == 'closed'
|
||||
&& contains(github.event.pull_request.labels.*.name, 'auto-closed:template')
|
||||
}}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Remove template label
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: gh pr edit "$PR_NUMBER" --remove-label "auto-closed:template" || true
|
||||
|
||||
- name: Check for remaining auto-closed labels
|
||||
id: check_labels
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
REMAINING=$(gh pr view "$PR_NUMBER" --json labels \
|
||||
--jq '[.labels[].name | select(startswith("auto-closed:"))] | length')
|
||||
echo "remaining=$REMAINING" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Reopen PR
|
||||
if: ${{ steps.check_labels.outputs.remaining == '0' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f query='
|
||||
mutation ReopenPR($prId: ID!) {
|
||||
reopenPullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
97
.github/workflows/check-pr-template.yml
vendored
97
.github/workflows/check-pr-template.yml
vendored
@@ -1,97 +0,0 @@
|
||||
name: Check PR Template
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types: [opened, edited]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
LABEL_ID: 'LA_kwDOGyI-8M8AAAACcAeOfg' # auto-closed:template
|
||||
|
||||
jobs:
|
||||
parse:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.head.repo.fork == true }}
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/pull_request_template.md
|
||||
sparse-checkout-cone-mode: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check required sections
|
||||
id: check
|
||||
env:
|
||||
BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
OK=true
|
||||
while IFS= read -r header; do
|
||||
printf '%s\n' "$BODY" | grep -qF "$header" || OK=false
|
||||
done < <(sed '/<!--/,/-->/d' .github/pull_request_template.md | grep "^## ")
|
||||
echo "uses_template=$OK" >> "$GITHUB_OUTPUT"
|
||||
|
||||
act:
|
||||
runs-on: ubuntu-latest
|
||||
needs: parse
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Close PR
|
||||
if: ${{ needs.parse.outputs.uses_template == 'false' && github.event.pull_request.state != 'closed' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f labelId="$LABEL_ID" \
|
||||
-f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \
|
||||
-f query='
|
||||
mutation CommentAndClosePR($prId: ID!, $body: String!, $labelId: ID!) {
|
||||
addComment(input: {
|
||||
subjectId: $prId,
|
||||
body: $body
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
closePullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
addLabelsToLabelable(input: {
|
||||
labelableId: $prId,
|
||||
labelIds: [$labelId]
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
|
||||
- name: Reopen PR (sections now present, PR was auto-closed)
|
||||
if: ${{ needs.parse.outputs.uses_template == 'true' && github.event.pull_request.state == 'closed' && contains(github.event.pull_request.labels.*.node_id, env.LABEL_ID) }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f labelId="$LABEL_ID" \
|
||||
-f query='
|
||||
mutation ReopenPR($prId: ID!, $labelId: ID!) {
|
||||
reopenPullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
removeLabelsFromLabelable(input: {
|
||||
labelableId: $prId,
|
||||
labelIds: [$labelId]
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
38
.github/workflows/close-llm-pr.yml
vendored
38
.github/workflows/close-llm-pr.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Close LLM-generated PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
comment_and_close:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.label.name == 'llm-generated' }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment and close
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our [CONTRIBUTING.md](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md#use-of-generative-ai), we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
|
||||
-f query='
|
||||
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
||||
addComment(input: {
|
||||
subjectId: $prId,
|
||||
body: $body
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
|
||||
closePullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
@@ -1,24 +1,20 @@
|
||||
<script lang="ts">
|
||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||
import Portal from '$lib/elements/Portal.svelte';
|
||||
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { Icon, modalManager } from '@immich/ui';
|
||||
import { mdiMapMarkerOutline, mdiPencil } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
isOwner: boolean;
|
||||
asset: AssetResponseDto;
|
||||
}
|
||||
};
|
||||
|
||||
let { isOwner, asset = $bindable() }: Props = $props();
|
||||
|
||||
let isShowChangeLocation = $state(false);
|
||||
|
||||
const onClose = async (point?: { lng: number; lat: number }) => {
|
||||
isShowChangeLocation = false;
|
||||
|
||||
const onAction = async () => {
|
||||
const point = await modalManager.show(GeolocationPointPickerModal, { asset });
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
@@ -38,7 +34,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full text-start justify-between place-items-start gap-4 py-4"
|
||||
onclick={() => (isOwner ? (isShowChangeLocation = true) : null)}
|
||||
onclick={() => (isOwner ? onAction : undefined)}
|
||||
title={isOwner ? $t('edit_location') : ''}
|
||||
class:hover:text-primary={isOwner}
|
||||
>
|
||||
@@ -72,12 +68,11 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full text-start justify-between place-items-start gap-4 py-4 rounded-lg hover:text-primary"
|
||||
onclick={() => (isShowChangeLocation = true)}
|
||||
onclick={onAction}
|
||||
title={$t('add_location')}
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<div><Icon icon={mdiMapMarkerOutline} size="24" /></div>
|
||||
|
||||
<p>{$t('add_a_location')}</p>
|
||||
</div>
|
||||
<div class="focus:outline-none p-1">
|
||||
@@ -85,9 +80,3 @@
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if isShowChangeLocation}
|
||||
<Portal>
|
||||
<ChangeLocation {asset} {onClose} />
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
<script lang="ts">
|
||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
|
||||
import { getAssetControlContext } from '$lib/utils/context';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAssets } from '@immich/sdk';
|
||||
import { modalManager, toastManager } from '@immich/ui';
|
||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
menuItem?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
let { menuItem = false }: Props = $props();
|
||||
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||
|
||||
let isShowChangeLocation = $state(false);
|
||||
|
||||
async function handleConfirm(point?: { lng: number; lat: number }) {
|
||||
isShowChangeLocation = false;
|
||||
|
||||
const onAction = async () => {
|
||||
const point = await modalManager.show(GeolocationPointPickerModal, {});
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
@@ -29,20 +27,14 @@
|
||||
|
||||
try {
|
||||
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
|
||||
toastManager.primary();
|
||||
clearSelect();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_location'));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if menuItem}
|
||||
<MenuOption
|
||||
text={$t('change_location')}
|
||||
icon={mdiMapMarkerMultipleOutline}
|
||||
onClick={() => (isShowChangeLocation = true)}
|
||||
/>
|
||||
{/if}
|
||||
{#if isShowChangeLocation}
|
||||
<ChangeLocation onClose={handleConfirm} />
|
||||
<MenuOption text={$t('change_location')} icon={mdiMapMarkerMultipleOutline} onClick={onAction} />
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cleanClass } from '$lib';
|
||||
import { cleanClass, isDefined } from '$lib';
|
||||
|
||||
describe('cleanClass', () => {
|
||||
it('should return a string of class names', () => {
|
||||
@@ -13,3 +13,19 @@ describe('cleanClass', () => {
|
||||
expect(cleanClass('class1', ['class2', 'class3'])).toBe('class1 class2 class3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDefined', () => {
|
||||
it('should return false for null', () => {
|
||||
expect(isDefined(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for undefined', () => {
|
||||
expect(isDefined(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for everything else', () => {
|
||||
for (const value of [0, 1, 2, true, false, {}, 'foo', 'bar', []]) {
|
||||
expect(isDefined(value)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,3 +14,5 @@ export const cleanClass = (...classNames: unknown[]) => {
|
||||
.join(' '),
|
||||
);
|
||||
};
|
||||
|
||||
export const isDefined = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined;
|
||||
|
||||
15
web/src/lib/managers/geolocation.manager.svelte.ts
Normal file
15
web/src/lib/managers/geolocation.manager.svelte.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { LatLng } from '$lib/types';
|
||||
|
||||
class GeolocationManager {
|
||||
#lastPoint = $state<LatLng>();
|
||||
|
||||
get lastPoint() {
|
||||
return this.#lastPoint;
|
||||
}
|
||||
|
||||
onSelected(point: LatLng) {
|
||||
this.#lastPoint = point;
|
||||
}
|
||||
}
|
||||
|
||||
export const geolocationManager = new GeolocationManager();
|
||||
@@ -1,30 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { isDefined } from '$lib';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
import { listNavigation } from '$lib/actions/list-navigation';
|
||||
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
|
||||
import type Map from '$lib/components/shared-components/map/map.svelte';
|
||||
import { timeDebounceOnSearch, timeToLoadTheMap } from '$lib/constants';
|
||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||
import { lastChosenLocation } from '$lib/stores/asset-editor.store';
|
||||
import { geolocationManager } from '$lib/managers/geolocation.manager.svelte';
|
||||
import type { LatLng } from '$lib/types';
|
||||
import { delay } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
|
||||
import { ConfirmModal, LoadingSpinner } from '@immich/ui';
|
||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
interface Point {
|
||||
lng: number;
|
||||
lat: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
asset?: AssetResponseDto | undefined;
|
||||
point?: Point;
|
||||
onClose: (point?: Point) => void;
|
||||
}
|
||||
type Props = {
|
||||
asset?: AssetResponseDto;
|
||||
point?: LatLng;
|
||||
onClose: (point?: LatLng) => void;
|
||||
};
|
||||
|
||||
let { asset = undefined, point: initialPoint, onClose }: Props = $props();
|
||||
let { asset, point: initialPoint, onClose }: Props = $props();
|
||||
|
||||
let places: PlacesResponseDto[] = $state([]);
|
||||
let suggestedPlaces: PlacesResponseDto[] = $derived(places.slice(0, 5));
|
||||
@@ -35,15 +32,22 @@
|
||||
let hideSuggestion = $state(false);
|
||||
let mapElement = $state<ReturnType<typeof Map>>();
|
||||
|
||||
let previousLocation = get(lastChosenLocation);
|
||||
let assetPoint = $derived.by<LatLng | undefined>(() => {
|
||||
if (!asset || !asset.exifInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let assetLat = $derived(initialPoint?.lat ?? asset?.exifInfo?.latitude ?? undefined);
|
||||
let assetLng = $derived(initialPoint?.lng ?? asset?.exifInfo?.longitude ?? undefined);
|
||||
const { latitude, longitude } = asset.exifInfo;
|
||||
if (!isDefined(latitude) || !isDefined(longitude)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mapLat = $derived(assetLat ?? previousLocation?.lat ?? undefined);
|
||||
let mapLng = $derived(assetLng ?? previousLocation?.lng ?? undefined);
|
||||
return { lat: latitude, lng: longitude };
|
||||
});
|
||||
|
||||
let zoom = $derived(mapLat && mapLng ? 12.5 : 1);
|
||||
let point = $state<LatLng | undefined>(assetPoint ?? initialPoint ?? geolocationManager.lastPoint);
|
||||
let zoom = $derived(point ? 12.5 : 1);
|
||||
let initialCenter = $state(point);
|
||||
|
||||
$effect(() => {
|
||||
if (mapElement && initialPoint) {
|
||||
@@ -57,11 +61,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
let point: Point | null = $state(initialPoint ?? null);
|
||||
|
||||
const handleConfirm = (confirmed?: boolean) => {
|
||||
if (point && confirmed) {
|
||||
lastChosenLocation.set(point);
|
||||
geolocationManager.onSelected(point);
|
||||
onClose(point);
|
||||
} else {
|
||||
onClose();
|
||||
@@ -201,12 +203,12 @@
|
||||
{:then { default: Map }}
|
||||
<Map
|
||||
bind:this={mapElement}
|
||||
mapMarkers={assetLat !== undefined && assetLng !== undefined && asset
|
||||
mapMarkers={asset && assetPoint
|
||||
? [
|
||||
{
|
||||
id: asset.id,
|
||||
lat: assetLat,
|
||||
lon: assetLng,
|
||||
lat: assetPoint.lat,
|
||||
lon: assetPoint.lng,
|
||||
city: asset.exifInfo?.city ?? null,
|
||||
state: asset.exifInfo?.state ?? null,
|
||||
country: asset.exifInfo?.country ?? null,
|
||||
@@ -214,7 +216,7 @@
|
||||
]
|
||||
: []}
|
||||
{zoom}
|
||||
center={mapLat && mapLng ? { lat: mapLat, lng: mapLng } : undefined}
|
||||
center={initialCenter}
|
||||
simplified={true}
|
||||
clickable={true}
|
||||
onClickPoint={(selected) => (point = selected)}
|
||||
@@ -225,7 +227,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
|
||||
<CoordinatesInput lat={point ? point.lat : assetLat} lng={point ? point.lng : assetLng} {onUpdate} />
|
||||
<CoordinatesInput lat={point?.lat} lng={point?.lng} {onUpdate} />
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
//-----other
|
||||
export const lastChosenLocation = writable<{ lng: number; lat: number } | null>(null);
|
||||
@@ -5,6 +5,8 @@ import type { ActionItem } from '@immich/ui';
|
||||
import type { DateTime } from 'luxon';
|
||||
import type { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
export type LatLng = { lng: number; lat: number };
|
||||
|
||||
export interface ReleaseEvent {
|
||||
isAvailable: boolean;
|
||||
/** ISO8601 */
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
@@ -8,6 +7,7 @@
|
||||
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||
import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
@@ -19,9 +19,9 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
data: PageData;
|
||||
}
|
||||
};
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
};
|
||||
|
||||
const handlePickOnMap = async () => {
|
||||
const point = await modalManager.show(ChangeLocation, {
|
||||
const point = await modalManager.show(GeolocationPointPickerModal, {
|
||||
point: {
|
||||
lat: location.latitude,
|
||||
lng: location.longitude,
|
||||
|
||||
Reference in New Issue
Block a user