Compare commits
68 Commits
fix/locate
...
fix/docs-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f163cc07f9 | ||
|
|
46869f664d | ||
|
|
f2b553182a | ||
|
|
8fe54a4de1 | ||
|
|
ce4e8fa6ba | ||
|
|
efa21af6f9 | ||
|
|
ea610760ee | ||
|
|
a5e0d83d9f | ||
|
|
9793828dc7 | ||
|
|
aed7bb53aa | ||
|
|
1fdbe2c6b8 | ||
|
|
84302dc14c | ||
|
|
f7250f24fe | ||
|
|
53680d9643 | ||
|
|
b2d00405f1 | ||
|
|
cf60f4cdcd | ||
|
|
d764a59011 | ||
|
|
7ee1b977c1 | ||
|
|
9838634067 | ||
|
|
eee793bfe4 | ||
|
|
b3342323de | ||
|
|
6f3cb4f1bb | ||
|
|
54ed78d0bf | ||
|
|
265ed0b38f | ||
|
|
63c2f4415b | ||
|
|
a7cfd7f183 | ||
|
|
ee4c45d5d3 | ||
|
|
24334aa3df | ||
|
|
882baecf21 | ||
|
|
f16327d0ab | ||
|
|
8353db6a50 | ||
|
|
5270107926 | ||
|
|
740ca14a68 | ||
|
|
966ab22065 | ||
|
|
78fbe0fd49 | ||
|
|
5862c454b7 | ||
|
|
8ee495b08f | ||
|
|
83db851b00 | ||
|
|
70037018c8 | ||
|
|
796444d211 | ||
|
|
0d66a15d9b | ||
|
|
3cf8ed5f2d | ||
|
|
ff01af2450 | ||
|
|
2de1b832e5 | ||
|
|
25142bb6c6 | ||
|
|
01660b20fd | ||
|
|
9affee1ea0 | ||
|
|
d02a82b618 | ||
|
|
ad87dff18d | ||
|
|
b7e06e7b6f | ||
|
|
21f49572b1 | ||
|
|
2b7d28528d | ||
|
|
cf4cf56ac0 | ||
|
|
50ac27238e | ||
|
|
b06b8ceef6 | ||
|
|
119f92bb20 | ||
|
|
6973683ea7 | ||
|
|
42f46b11f4 | ||
|
|
0fd16a3c46 | ||
|
|
43b06a036d | ||
|
|
55ad83d80d | ||
|
|
a80b9be07c | ||
|
|
24234bedf1 | ||
|
|
51150a3ed1 | ||
|
|
075436a5d1 | ||
|
|
9da138e01e | ||
|
|
1a2a46014e | ||
|
|
29acf89979 |
@@ -6,28 +6,35 @@ services:
|
||||
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
||||
volumes: !override # bind mount host to /workspaces/immich
|
||||
- ..:/workspaces/immich
|
||||
- cli_node_modules:/workspaces/immich/cli/node_modules
|
||||
- e2e_node_modules:/workspaces/immich/e2e/node_modules
|
||||
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
|
||||
- server_node_modules:/workspaces/immich/server/node_modules
|
||||
- web_node_modules:/workspaces/immich/web/node_modules
|
||||
- ${UPLOAD_LOCATION}/photos:/data
|
||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||
- pnpm-store:/usr/src/app/.pnpm-store
|
||||
- server-node_modules:/usr/src/app/server/node_modules
|
||||
- web-node_modules:/usr/src/app/web/node_modules
|
||||
- github-node_modules:/usr/src/app/.github/node_modules
|
||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
immich-web:
|
||||
env_file: !reset []
|
||||
immich-machine-learning:
|
||||
env_file: !reset []
|
||||
database:
|
||||
env_file: !reset []
|
||||
environment: !override
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD-postgres}
|
||||
POSTGRES_USER: ${DB_USERNAME-postgres}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
|
||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||
POSTGRES_HOST_AUTH_METHOD: md5
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
|
||||
|
||||
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
|
||||
redis:
|
||||
env_file: !reset []
|
||||
volumes:
|
||||
# Node modules for each service to avoid conflicts and ensure consistent dependencies
|
||||
cli_node_modules:
|
||||
e2e_node_modules:
|
||||
open_api_node_modules:
|
||||
server_node_modules:
|
||||
web_node_modules:
|
||||
|
||||
# UPLOAD_LOCATION must be set to a absolute path or vol-upload
|
||||
vol-upload:
|
||||
|
||||
# DB_DATA_LOCATION must be set to a absolute path or vol-database
|
||||
vol-database:
|
||||
upload-devcontainer-volume:
|
||||
postgres-devcontainer-volume:
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"userEnvProbe": "loginInteractiveShell",
|
||||
"remoteEnv": {
|
||||
// The location where your uploaded files are stored
|
||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
|
||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
|
||||
// Connection secret for postgres. You should change it to a random password
|
||||
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
||||
|
||||
8
.github/workflows/build-mobile.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
runs-on: mich
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -66,14 +66,14 @@ jobs:
|
||||
working-directory: ./mobile
|
||||
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
||||
|
||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Restore Gradle Cache
|
||||
id: cache-gradle-restore
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
|
||||
- name: Save Gradle Cache
|
||||
id: cache-gradle-save
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
|
||||
2
.github/workflows/cache-cleanup.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
10
.github/workflows/cli.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
working-directory: ./cli
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm publish
|
||||
- run: pnpm publish --no-git-checks
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
@@ -44,13 +44,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -76,6 +76,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
8
.github/workflows/docker.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
suffix: ['']
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
tag-suffix: '-rocm'
|
||||
platforms: linux/amd64
|
||||
runner-mapping: '{"linux/amd64": "mich"}'
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@946acac326940f8badf09ccf591d9cb345d6a689 # multi-runner-build-workflow-v0.2.1
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
name: Build and Push Server
|
||||
needs: pre-job
|
||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@946acac326940f8badf09ccf591d9cb345d6a689 # multi-runner-build-workflow-v0.2.1
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
6
.github/workflows/docs-build.yml
vendored
@@ -31,6 +31,8 @@ jobs:
|
||||
- 'open-api/immich-openapi-specs.json'
|
||||
force-filters: |
|
||||
- '.github/workflows/docs-build.yml'
|
||||
- '.github/workflows/docs-deploy.yml'
|
||||
- 'deployment/**'
|
||||
force-events: 'release'
|
||||
force-branches: 'main'
|
||||
|
||||
@@ -47,7 +49,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
10
.github/workflows/docs-deploy.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
run: echo 'The triggering workflow did not succeed' && exit 1
|
||||
- name: Get artifact
|
||||
id: get-artifact
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
return { found: true, id: matchArtifact.id };
|
||||
- name: Determine deploy parameters
|
||||
id: parameters
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
with:
|
||||
@@ -108,13 +108,13 @@ jobs:
|
||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Load parameters
|
||||
id: parameters
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
||||
with:
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/docs-destroy.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
6
.github/workflows/fix-format.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
message: 'chore: fix formatting'
|
||||
|
||||
- name: Remove label
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
|
||||
2
.github/workflows/pr-labeler.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
|
||||
12
.github/workflows/prepare-release.yml
vendored
@@ -55,20 +55,20 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -117,18 +117,18 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2.4.0
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.IMMICH_VERSION }}
|
||||
|
||||
2
.github/workflows/preview-label.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
6
.github/workflows/sdk.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
run:
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -35,6 +35,6 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
- name: Publish
|
||||
run: pnpm publish
|
||||
run: pnpm publish --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
working-directory: ./mobile
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
66
.github/workflows/test.yml
vendored
@@ -56,13 +56,13 @@ jobs:
|
||||
working-directory: ./server
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -93,13 +93,13 @@ jobs:
|
||||
working-directory: ./cli
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -133,13 +133,13 @@ jobs:
|
||||
working-directory: ./cli
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -168,13 +168,13 @@ jobs:
|
||||
working-directory: ./web
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
- name: Run pnpm install
|
||||
run: pnpm rebuild && pnpm install --frozen-lockfile
|
||||
- name: Run linter
|
||||
run: pnpm lint:p
|
||||
run: pnpm lint
|
||||
if: ${{ !cancelled() }}
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
@@ -205,13 +205,13 @@ jobs:
|
||||
working-directory: ./web
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -236,13 +236,13 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -277,13 +277,13 @@ jobs:
|
||||
working-directory: ./e2e
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -316,13 +316,13 @@ jobs:
|
||||
working-directory: ./server
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -347,14 +347,14 @@ jobs:
|
||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -395,14 +395,14 @@ jobs:
|
||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -441,7 +441,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Flutter SDK
|
||||
@@ -466,12 +466,12 @@ jobs:
|
||||
run:
|
||||
working-directory: ./machine-learning
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
|
||||
# with:
|
||||
# python-version: 3.11
|
||||
@@ -503,13 +503,13 @@ jobs:
|
||||
working-directory: ./.github
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './.github/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -525,7 +525,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run ShellCheck
|
||||
@@ -540,13 +540,13 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -581,7 +581,7 @@ jobs:
|
||||
contents: read
|
||||
services:
|
||||
postgres:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:da52bbead5d818adaa8077c8dcdaad0aaf93038c31ad8348b51f9f0ec1310a4d
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:dbf18b3ffea4a81434c65b71e20d27203baf903a0275f4341e4c16dfd901fd67
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
@@ -595,13 +595,13 @@ jobs:
|
||||
working-directory: ./server
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
14
.vscode/launch.json
vendored
@@ -18,6 +18,20 @@
|
||||
"name": "Immich Workers",
|
||||
"remoteRoot": "/usr/src/app/server",
|
||||
"localRoot": "${workspaceFolder}/server"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Immich CLI",
|
||||
"program": "${workspaceFolder}/cli/dist/index.js",
|
||||
"args": ["upload", "--help"],
|
||||
"runtimeArgs": ["--enable-source-maps"],
|
||||
"console": "integratedTerminal",
|
||||
"resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"],
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/cli/dist/**/*.js"],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "Build Immich CLI"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
.vscode/tasks.json
vendored
@@ -5,6 +5,7 @@
|
||||
"label": "Fix Permissions, Install Dependencies",
|
||||
"type": "shell",
|
||||
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
@@ -25,6 +26,7 @@
|
||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
||||
"type": "shell",
|
||||
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
@@ -45,6 +47,7 @@
|
||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
||||
"type": "shell",
|
||||
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
@@ -67,6 +70,11 @@
|
||||
"runOn": "folderOpen"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Build Immich CLI",
|
||||
"type": "shell",
|
||||
"command": "pnpm --filter cli build:dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2
Makefile
@@ -91,8 +91,6 @@ format-%:
|
||||
pnpm --filter $(call map-package,$*) run format:fix
|
||||
lint-%:
|
||||
pnpm --filter $(call map-package,$*) run lint:fix
|
||||
lint-web:
|
||||
pnpm --filter $(call map-package,$*) run lint:p
|
||||
check-%:
|
||||
pnpm --filter $(call map-package,$*) run check
|
||||
check-web:
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
<a href="readme_i18n/README_de_DE.md">Deutsch</a>
|
||||
<a href="readme_i18n/README_nl_NL.md">Nederlands</a>
|
||||
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
|
||||
<a href="readme_i18n/README_zh_CN.md">中文</a>
|
||||
<a href="readme_i18n/README_zh_CN.md">简体中文</a>
|
||||
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
|
||||
<a href="readme_i18n/README_uk_UA.md">Українська</a>
|
||||
<a href="readme_i18n/README_ru_RU.md">Русский</a>
|
||||
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
|
||||
|
||||
@@ -6,25 +6,33 @@ Please see the [Immich CLI documentation](https://docs.immich.app/features/comma
|
||||
|
||||
Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
|
||||
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ pnpm install
|
||||
$ pnpm run build
|
||||
|
||||
Then, to build the open-api client run the following in the open-api folder:
|
||||
|
||||
$ ./bin/generate-open-api.sh
|
||||
|
||||
To run the Immich CLI from source, run the following in the cli folder:
|
||||
## Run from build
|
||||
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ ts-node .
|
||||
Go to the cli folder and build it:
|
||||
|
||||
You'll need ts-node, the easiest way to install it is to use npm:
|
||||
$ pnpm install
|
||||
$ pnpm run build
|
||||
$ node dist/index.js
|
||||
|
||||
$ npm i -g ts-node
|
||||
## Run and Debug from source (VSCode)
|
||||
|
||||
With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug
|
||||
|
||||
`"args": ["upload", "--help"],`
|
||||
|
||||
replace that for the command of your choice.
|
||||
|
||||
## Install from build
|
||||
|
||||
You can also build and install the CLI using
|
||||
|
||||
$ npm run build
|
||||
$ npm install -g .
|
||||
$ pnpm run build
|
||||
$ pnpm install -g .
|
||||
****
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^22.18.1",
|
||||
"@types/node": "^22.18.8",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"byte-size": "^9.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
@@ -43,6 +43,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"build:dev": "vite build --sourcemap true",
|
||||
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"prepack": "npm run build",
|
||||
|
||||
@@ -22,7 +22,7 @@ For organizations seeking to resell Immich, we have established the following gu
|
||||
|
||||
- Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team.
|
||||
|
||||
- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directy from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work.
|
||||
- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directly from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work.
|
||||
|
||||
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
||||
|
||||
|
||||
@@ -55,3 +55,19 @@ Additionally, some jobs (such as memories generation) run on a schedule, which i
|
||||
:::note
|
||||
Some jobs ([External Libraries](/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings.
|
||||
:::
|
||||
|
||||
## Job processing order
|
||||
|
||||
The below diagram shows the job run order for newly uploaded files
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Asset Upload] --> B[Metadata Extraction]
|
||||
B --> C[Storage Template Migration]
|
||||
C --> D["Thumbnail Generation (Large, small, blurred and person)"]
|
||||
D --> E[Smart Search]
|
||||
D --> F[Face Detection]
|
||||
D --> G[Video Transcoding]
|
||||
E --> H[Duplicate Detection]
|
||||
F --> I[Facial Recognition]
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@ Immich supports the Google's Cast protocol so that photos and videos can be cast
|
||||
|
||||
## Enable Google Cast Support
|
||||
|
||||
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retreive them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
|
||||
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
|
||||
|
||||
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# External Libraries
|
||||
|
||||
:::info
|
||||
Currently an external library can only belong to a single user which is selected when the library is initially created.
|
||||
:::
|
||||
|
||||
External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up.
|
||||
|
||||
If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost.
|
||||
|
||||
@@ -21,6 +21,10 @@ Restart Immich by running `docker compose up -d`.
|
||||
|
||||
# Create the library
|
||||
|
||||
:::info
|
||||
External library management requires administrator access and the steps below assume you are using an admin account.
|
||||
:::
|
||||
|
||||
In the Immich web UI:
|
||||
|
||||
- click the **Administration** link in the upper right corner.
|
||||
|
||||
@@ -16,7 +16,7 @@ Immich can easily be installed on a Synology NAS using Container Manager within
|
||||
|
||||
## Step 1 - Download the required files
|
||||
|
||||
Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's a best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`.
|
||||
Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`.
|
||||
|
||||
Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`.
|
||||
|
||||
@@ -25,7 +25,7 @@ When you're all done, you should have the following:
|
||||
- `./docker/immich-app/postgres`
|
||||
- `./docker/immich-app/library`
|
||||
|
||||
Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`.
|
||||
Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`. Note: If you plan to use the Synology Text editor to edit the `.env` file on the NAS within File Station, you will need to rename it to a temporary name (e.g. `example.txt`) in order to see 'Open with Text Editor' in the file context menu. Once saved, rename it back to `.env`.
|
||||
|
||||
## Step 2 - Populate the .env file with custom values
|
||||
|
||||
@@ -34,23 +34,23 @@ Follow [Step 2 in Docker Compose](/install/docker-compose#step-2---populate-the-
|
||||
## Step 3 - Create a new project in Container Manager
|
||||
|
||||
Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**".
|
||||

|
||||

|
||||
|
||||
In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue.
|
||||
|
||||

|
||||

|
||||
|
||||
The following screen will give you the option to further customize your `docker-compose.yml` file, giving you a warning regarding the `start_interval` property. Under the `healthcheck` heading, remove the `start_interval: 30s` completely and click "**Next**".
|
||||
The following screen will give you the option to further customize your `docker-compose.yml` file. Take note of `DB_STORAGE_TYPE: 'HDD'`and uncomment if applicable for your Synology setup.
|
||||
|
||||

|
||||

|
||||
|
||||
Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project.
|
||||
|
||||
Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**".
|
||||
|
||||
Scroll to the bottom of the "**Details**" section, and find the `IP Address` of the container, located in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**.
|
||||
Scroll to the bottom of the "**Details**" section and find the `IP Address` listed in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4 - Configure Firewall Settings
|
||||
|
||||
@@ -63,8 +63,66 @@ Open "**Control Panel**" on your Synology NAS, and select "**Security**". Naviga
|
||||
Click "**Edit Rules**" and add the following firewall rules:
|
||||
|
||||
- Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above
|
||||
|
||||

|
||||
|
||||
- Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283`
|
||||
|
||||

|
||||
|
||||
## Next Steps
|
||||
|
||||
Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md).
|
||||
|
||||
<details>
|
||||
<summary>Updating Immich using Container Manager</summary>
|
||||
Check the post installation and upgrade instructions at the links above before proceeding with this section.
|
||||
|
||||
## Step 1. Backup
|
||||
|
||||
Ensure your photos and videos are backed up. Your `.env` settings will define where they are stored. There is no need to delete any files or folders within the `docker` folder when doing a release upgrade unless instructed in the release notes.
|
||||
|
||||
## Step 2. Check release notes
|
||||
|
||||
Always check the [release notes](https://github.com/immich-app/immich/releases) before proceeding with an update!
|
||||
|
||||
## Step 3. Stop containers & clean up
|
||||
|
||||
Open **Container Manager**. Select **Project** then your Immich app
|
||||
|
||||

|
||||
|
||||
Select **Stop**
|
||||
|
||||

|
||||
|
||||
Select **Action** then **Clean**. This removes the containers.
|
||||
|
||||

|
||||
|
||||
Go to **Image** and select **Remove Unused Images**.
|
||||
|
||||

|
||||
|
||||
## Step 4. Build
|
||||
|
||||
Go to **Project**, select **Action** then **Build**. This will download, unpack, install and start the containers.
|
||||
|
||||

|
||||
|
||||
## Step 5. Update firewall rule
|
||||
|
||||
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
||||
|
||||
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||

|
||||
|
||||
Go to Synology **Control Panel**. Select **Security** and **Firewall**.
|
||||
|
||||

|
||||
|
||||
In this example, the IP addresses mismatch and the firewall rule needs to be edited to match above.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -387,27 +387,35 @@ To migrate from the old storage configuration to the new one, you will need to c
|
||||
3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples:
|
||||
|
||||
```bash
|
||||
rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/
|
||||
rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/
|
||||
rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/
|
||||
rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/
|
||||
rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/
|
||||
rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/
|
||||
sudo rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/
|
||||
sudo rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/
|
||||
sudo rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/
|
||||
sudo rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/
|
||||
sudo rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/
|
||||
sudo rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/
|
||||
```
|
||||
|
||||
Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset.
|
||||
|
||||
:::tip
|
||||
If you were using **ixVolume (dataset created automatically by the system)** for Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example:
|
||||
If you were using **ixVolume (dataset created automatically by the system)** for some of Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example:
|
||||
|
||||
```bash
|
||||
rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/
|
||||
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/
|
||||
```
|
||||
|
||||
If you also were storing your files in the **ixVolume**, the **_upload_** folder is named `uploads` instead of `upload`, so the command to run should be:
|
||||
|
||||
```bash
|
||||
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/uploads/ /mnt/tank/immich/data/upload/
|
||||
```
|
||||
|
||||
This means that depending on your old storage configuration, you might have to use a mix of paths in the `rsync` commands above.
|
||||
|
||||
If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data:
|
||||
|
||||
```bash
|
||||
rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
||||
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -416,7 +424,7 @@ rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
||||
Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example:
|
||||
|
||||
```bash
|
||||
rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/
|
||||
sudo rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/
|
||||
```
|
||||
|
||||
Replace `library` with the name of the folder where you are copying the file.
|
||||
@@ -437,38 +445,37 @@ This will recreate the Immich container with the new storage configuration and s
|
||||
|
||||
If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface.
|
||||
|
||||
:::tip
|
||||
If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands:
|
||||
|
||||
```bash
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/library
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/uploads
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/thumbs
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/profile
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/video
|
||||
rm -r /mnt/.ix-apps/app_mounts/immich/backups
|
||||
sudo rm -r /mnt/.ix-apps/app_mounts/immich/*
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="migrate-old-dataset" label="Keep the existing datasets">
|
||||
|
||||
|
||||
To migrate from the old storage configuration to the new one without creating new datasets.
|
||||
|
||||
1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app.
|
||||
2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have ***modify*** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with ***modify*** permissions.
|
||||
2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have **_modify_** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with **_modify_** permissions.
|
||||
3. **Update the Immich app** to use the existing datasets:
|
||||
- Go to the **Installed Applications** screen and select Immich from the list of installed applications.
|
||||
- Click **Edit** on the **Application Info** widget.
|
||||
- In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox.
|
||||
- For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`.
|
||||
- For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`.
|
||||
- Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings:
|
||||
- **Type**: `Host Path (Path that already exists on the system)`
|
||||
- **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`)
|
||||
- **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`)
|
||||
:::danger Ensure using the correct paths names
|
||||
Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset.
|
||||
:::
|
||||
- **Read Only**: Keep it unticked as Immich needs to write to these datasets.
|
||||
- Click **Update** at the bottom of the page to save changes.
|
||||
- Go to the **Installed Applications** screen and select Immich from the list of installed applications.
|
||||
- Click **Edit** on the **Application Info** widget.
|
||||
- In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox.
|
||||
- For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`.
|
||||
- For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`.
|
||||
- Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings:
|
||||
- **Type**: `Host Path (Path that already exists on the system)`
|
||||
- **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`)
|
||||
- **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`)
|
||||
:::danger Ensure using the correct paths names
|
||||
Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset.
|
||||
:::
|
||||
- **Read Only**: Keep it unticked as Immich needs to write to these datasets.
|
||||
- Click **Update** at the bottom of the page to save changes.
|
||||
4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible.
|
||||
|
||||
</TabItem>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "~3.8.0",
|
||||
"@docusaurus/preset-classic": "~3.8.0",
|
||||
"@docusaurus/theme-common": "~3.8.0",
|
||||
"@docusaurus/core": "~3.9.0",
|
||||
"@docusaurus/preset-classic": "~3.9.0",
|
||||
"@docusaurus/theme-common": "~3.9.0",
|
||||
"@mdi/js": "^7.3.67",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "~3.8.0",
|
||||
"@docusaurus/module-type-aliases": "~3.9.0",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"prettier": "^3.2.4",
|
||||
|
||||
@@ -23,11 +23,6 @@ const projects: CommunityProjectProps[] = [
|
||||
description: 'A Python script to sync folders as albums.',
|
||||
url: 'https://git.orenit.solutions/open/immichalbumpull',
|
||||
},
|
||||
{
|
||||
title: 'Remove offline files',
|
||||
description: 'A simple way to remove orphaned offline assets from the Immich database',
|
||||
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
|
||||
},
|
||||
{
|
||||
title: 'Immich-Tools',
|
||||
description: 'Provides scripts for handling problems on the repair page.',
|
||||
@@ -120,6 +115,11 @@ const projects: CommunityProjectProps[] = [
|
||||
description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)',
|
||||
url: 'https://github.com/sid3windr/immich-stack',
|
||||
},
|
||||
{
|
||||
title: 'Immich Stack',
|
||||
description: 'Automatically groups similar photos into stacks within the Immich photo management system.',
|
||||
url: 'https://github.com/Majorfi/immich-stack/',
|
||||
},
|
||||
];
|
||||
|
||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||
|
||||
BIN
docs/static/img/synology-action-clean.png
vendored
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/static/img/synology-build.png
vendored
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/static/img/synology-container-ip.png
vendored
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 153 KiB |
BIN
docs/static/img/synology-custom-port-firewall-rule.png
vendored
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/static/img/synology-fw-ipedit.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/synology-fw-rules.png
vendored
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/static/img/synology-ipaddress-firewall-rule.png
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/static/img/synology-project-stop.png
vendored
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/static/img/synology-remove-unused.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/static/img/synology-select-proj.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -35,7 +35,7 @@ services:
|
||||
- 2285:2285
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine@sha256:7fe72c486b910f6b1a9769c937dad5d63648ddee82e056f47417542dd40825bb
|
||||
image: redis:6.2-alpine@sha256:2185e741f4c1e7b0ea9ca1e163a3767c4270a73086b6bbea2049a7203212fb7f
|
||||
|
||||
database:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:11ced39d65a92a54d12890ced6a26cc2003f92697d6f0d4d944b98459dba7138
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@socket.io/component-emitter": "^3.1.2",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^22.18.1",
|
||||
"@types/node": "^22.18.8",
|
||||
"@types/oidc-provider": "^9.0.0",
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
@@ -43,7 +43,7 @@
|
||||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"sharp": "^0.34.3",
|
||||
"sharp": "^0.34.4",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"supertest": "^7.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"add_to_albums": "Add to albums",
|
||||
"add_to_albums_count": "Add to albums ({count})",
|
||||
"add_to_shared_album": "Add to shared album",
|
||||
"add_upload_to_stack": "Add upload to stack",
|
||||
"add_url": "Add URL",
|
||||
"added_to_archive": "Added to archive",
|
||||
"added_to_favorites": "Added to favorites",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
from ipaddress import ip_address
|
||||
from pathlib import Path
|
||||
|
||||
from .config import log, non_prefixed_settings, settings
|
||||
@@ -12,6 +13,19 @@ else:
|
||||
|
||||
module_dir = Path(__file__).parent
|
||||
|
||||
|
||||
def is_ipv6(host: str) -> bool:
|
||||
try:
|
||||
return ip_address(host).version == 6
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
bind_host = non_prefixed_settings.immich_host
|
||||
if is_ipv6(bind_host):
|
||||
bind_host = f"[{bind_host}]"
|
||||
bind_address = f"{bind_host}:{non_prefixed_settings.immich_port}"
|
||||
|
||||
try:
|
||||
with subprocess.Popen(
|
||||
[
|
||||
@@ -24,7 +38,7 @@ try:
|
||||
"-c",
|
||||
module_dir / "gunicorn_conf.py",
|
||||
"-b",
|
||||
f"{non_prefixed_settings.immich_host}:{non_prefixed_settings.immich_port}",
|
||||
bind_address,
|
||||
"-w",
|
||||
str(settings.workers),
|
||||
"-t",
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import os
|
||||
import sys
|
||||
from ipaddress import ip_address
|
||||
|
||||
import requests
|
||||
|
||||
port = os.getenv("IMMICH_PORT", 3003)
|
||||
host = os.getenv("IMMICH_HOST", "0.0.0.0")
|
||||
|
||||
|
||||
def is_ipv6(host: str) -> bool:
|
||||
try:
|
||||
return ip_address(host).version == 6
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
host = "localhost" if host == "0.0.0.0" else host
|
||||
host = f"[{host}]" if is_ipv6(host) else host
|
||||
|
||||
try:
|
||||
response = requests.get(f"http://{host}:{port}/ping", timeout=2)
|
||||
|
||||
11
mise.toml
@@ -1,7 +1,7 @@
|
||||
[tools]
|
||||
node = "22.20.0"
|
||||
flutter = "3.35.4"
|
||||
pnpm = "10.15.1"
|
||||
flutter = "3.35.5"
|
||||
pnpm = "10.18.0"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm"]
|
||||
version = "1.30.0"
|
||||
@@ -278,12 +278,7 @@ run = "prettier --write ."
|
||||
[tasks."web:lint"]
|
||||
env._.path = "web/node_modules/.bin"
|
||||
dir = "web"
|
||||
run = "eslint . --max-warnings 0"
|
||||
|
||||
[tasks."web:lint-p"]
|
||||
env._.path = "web/node_modules/.bin"
|
||||
dir = "web"
|
||||
run = "eslint-p . --max-warnings 0 --concurrency=4"
|
||||
run = "eslint . --max-warnings 0 --concurrency 4"
|
||||
|
||||
[tasks."web:lint-fix"]
|
||||
run = "mise run web:lint --fix"
|
||||
|
||||
@@ -136,6 +136,7 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerFgHostApi {
|
||||
fun enable()
|
||||
fun saveNotificationMessage(title: String, body: String)
|
||||
fun configure(settings: BackgroundWorkerSettings)
|
||||
fun disable()
|
||||
|
||||
@@ -164,6 +165,25 @@ interface BackgroundWorkerFgHostApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val titleArg = args[0] as String
|
||||
val bodyArg = args[1] as String
|
||||
val wrapped: List<Any?> = try {
|
||||
api.saveNotificationMessage(titleArg, bodyArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
@@ -204,7 +224,6 @@ interface BackgroundWorkerFgHostApi {
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerBgHostApi {
|
||||
fun onInitialized()
|
||||
fun showNotification(title: String, content: String)
|
||||
fun close()
|
||||
|
||||
companion object {
|
||||
@@ -232,25 +251,6 @@ interface BackgroundWorkerBgHostApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val titleArg = args[0] as String
|
||||
val contentArg = args[1] as String
|
||||
val wrapped: List<Any?> = try {
|
||||
api.showNotification(titleArg, contentArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
|
||||
@@ -73,6 +73,8 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
val notificationConfig = BackgroundWorkerPreferences(ctx).getNotificationConfig()
|
||||
showNotification(notificationConfig.first, notificationConfig.second)
|
||||
|
||||
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
engine = FlutterEngine(ctx)
|
||||
@@ -109,7 +111,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
}
|
||||
|
||||
// TODO: Move this to a separate NotificationManager class
|
||||
override fun showNotification(title: String, content: String) {
|
||||
private fun showNotification(title: String, content: String) {
|
||||
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setOnlyAlertOnce(true)
|
||||
|
||||
@@ -20,6 +20,10 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
enqueueMediaObserver(ctx)
|
||||
}
|
||||
|
||||
override fun saveNotificationMessage(title: String, body: String) {
|
||||
BackgroundWorkerPreferences(ctx).updateNotificationConfig(title, body)
|
||||
}
|
||||
|
||||
override fun configure(settings: BackgroundWorkerSettings) {
|
||||
BackgroundWorkerPreferences(ctx).updateSettings(settings)
|
||||
enqueueMediaObserver(ctx)
|
||||
|
||||
@@ -10,9 +10,13 @@ class BackgroundWorkerPreferences(private val ctx: Context) {
|
||||
private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds"
|
||||
private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging"
|
||||
private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked"
|
||||
private const val SHARED_PREF_NOTIF_TITLE_KEY = "BackgroundWorker::notificationTitle"
|
||||
private const val SHARED_PREF_NOTIF_MSG_KEY = "BackgroundWorker::notificationMessage"
|
||||
|
||||
private const val DEFAULT_MIN_DELAY_SECONDS = 30L
|
||||
private const val DEFAULT_REQUIRE_CHARGING = false
|
||||
private const val DEFAULT_NOTIF_TITLE = "Uploading media"
|
||||
private const val DEFAULT_NOTIF_MSG = "Checking for new assets…"
|
||||
}
|
||||
|
||||
private val sp: SharedPreferences by lazy {
|
||||
@@ -38,6 +42,20 @@ class BackgroundWorkerPreferences(private val ctx: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
fun updateNotificationConfig(title: String, message: String) {
|
||||
sp.edit {
|
||||
putString(SHARED_PREF_NOTIF_TITLE_KEY, title)
|
||||
putString(SHARED_PREF_NOTIF_MSG_KEY, message)
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotificationConfig(): Pair<String, String> {
|
||||
val title =
|
||||
sp.getString(SHARED_PREF_NOTIF_TITLE_KEY, DEFAULT_NOTIF_TITLE) ?: DEFAULT_NOTIF_TITLE
|
||||
val message = sp.getString(SHARED_PREF_NOTIF_MSG_KEY, DEFAULT_NOTIF_MSG) ?: DEFAULT_NOTIF_MSG
|
||||
return Pair(title, message)
|
||||
}
|
||||
|
||||
fun setLocked(paused: Boolean) {
|
||||
sp.edit {
|
||||
putBoolean(SHARED_PREF_LOCK_KEY, paused)
|
||||
|
||||
@@ -64,7 +64,7 @@ PODS:
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- isar_flutter_libs (1.0.0):
|
||||
- isar_community_flutter_libs (1.0.0):
|
||||
- Flutter
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
@@ -149,7 +149,7 @@ DEPENDENCIES:
|
||||
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
- isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||
@@ -210,8 +210,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
isar_flutter_libs:
|
||||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||
isar_community_flutter_libs:
|
||||
:path: ".symlinks/plugins/isar_community_flutter_libs/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
maplibre_gl:
|
||||
@@ -264,7 +264,7 @@ SPEC CHECKSUMS:
|
||||
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
||||
isar_flutter_libs: bc909e72c3d756c2759f14c8776c13b5b0556e26
|
||||
isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd
|
||||
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
||||
|
||||
@@ -182,6 +182,7 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol BackgroundWorkerFgHostApi {
|
||||
func enable() throws
|
||||
func saveNotificationMessage(title: String, body: String) throws
|
||||
func configure(settings: BackgroundWorkerSettings) throws
|
||||
func disable() throws
|
||||
}
|
||||
@@ -205,6 +206,22 @@ class BackgroundWorkerFgHostApiSetup {
|
||||
} else {
|
||||
enableChannel.setMessageHandler(nil)
|
||||
}
|
||||
let saveNotificationMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
saveNotificationMessageChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let titleArg = args[0] as! String
|
||||
let bodyArg = args[1] as! String
|
||||
do {
|
||||
try api.saveNotificationMessage(title: titleArg, body: bodyArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
saveNotificationMessageChannel.setMessageHandler(nil)
|
||||
}
|
||||
let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
configureChannel.setMessageHandler { message, reply in
|
||||
@@ -238,7 +255,6 @@ class BackgroundWorkerFgHostApiSetup {
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol BackgroundWorkerBgHostApi {
|
||||
func onInitialized() throws
|
||||
func showNotification(title: String, content: String) throws
|
||||
func close() throws
|
||||
}
|
||||
|
||||
@@ -261,22 +277,6 @@ class BackgroundWorkerBgHostApiSetup {
|
||||
} else {
|
||||
onInitializedChannel.setMessageHandler(nil)
|
||||
}
|
||||
let showNotificationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
showNotificationChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let titleArg = args[0] as! String
|
||||
let contentArg = args[1] as! String
|
||||
do {
|
||||
try api.showNotification(title: titleArg, content: contentArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showNotificationChannel.setMessageHandler(nil)
|
||||
}
|
||||
let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
closeChannel.setMessageHandler { _, reply in
|
||||
|
||||
@@ -119,10 +119,6 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
})
|
||||
}
|
||||
|
||||
func showNotification(title: String, content: String) throws {
|
||||
// No-op on iOS for the time being
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the currently running background task, either due to timeout or external request.
|
||||
* Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure
|
||||
|
||||
@@ -12,6 +12,10 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
// Android only
|
||||
}
|
||||
|
||||
func saveNotificationMessage(title: String, body: String) throws {
|
||||
// Android only
|
||||
}
|
||||
|
||||
func disable() throws {
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<string>2.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -114,7 +114,7 @@ struct ImmichMemoryProvider: TimelineProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// If we didnt add any memory images (some failure occured or no images in memory),
|
||||
// If we didn't add any memory images (some failure occurred or no images in memory),
|
||||
// default to 12 hours of random photos
|
||||
if entries.count == 0 {
|
||||
// this must be a do/catch since we need to
|
||||
|
||||
@@ -3,27 +3,30 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
class SearchResult {
|
||||
final List<BaseAsset> assets;
|
||||
final double scrollOffset;
|
||||
final int? nextPage;
|
||||
|
||||
const SearchResult({required this.assets, this.nextPage});
|
||||
const SearchResult({required this.assets, this.scrollOffset = 0.0, this.nextPage});
|
||||
|
||||
int get totalAssets => assets.length;
|
||||
|
||||
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage}) {
|
||||
return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage);
|
||||
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage, double? scrollOffset}) {
|
||||
return SearchResult(
|
||||
assets: assets ?? this.assets,
|
||||
nextPage: nextPage ?? this.nextPage,
|
||||
scrollOffset: scrollOffset ?? this.scrollOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'SearchResult(assets: $assets, nextPage: $nextPage)';
|
||||
String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage, scrollOffset: $scrollOffset)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant SearchResult other) {
|
||||
if (identical(this, other)) return true;
|
||||
final listEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return listEquals(other.assets, assets) && other.nextPage == nextPage;
|
||||
return listEquals(other.assets, assets) && other.nextPage == nextPage && other.scrollOffset == scrollOffset;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assets.hashCode ^ nextPage.hashCode;
|
||||
int get hashCode => assets.hashCode ^ nextPage.hashCode ^ scrollOffset.hashCode;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import 'package:immich_mobile/domain/services/log.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||
@@ -44,6 +42,9 @@ class BackgroundWorkerFgService {
|
||||
// TODO: Move this call to native side once old timeline is removed
|
||||
Future<void> enable() => _foregroundHostApi.enable();
|
||||
|
||||
Future<void> saveNotificationMessage(String title, String body) =>
|
||||
_foregroundHostApi.saveNotificationMessage(title, body);
|
||||
|
||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure(
|
||||
BackgroundWorkerSettings(
|
||||
minimumDelaySeconds:
|
||||
@@ -112,13 +113,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
|
||||
configureFileDownloaderNotifications();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await _backgroundHostApi.showNotification(
|
||||
IntlKeys.uploading_media.t(),
|
||||
IntlKeys.backup_background_service_default_notification.t(),
|
||||
);
|
||||
}
|
||||
|
||||
// Notify the host that the background worker service has been initialized and is ready to use
|
||||
_backgroundHostApi.onInitialized();
|
||||
} catch (error, stack) {
|
||||
|
||||
@@ -86,7 +86,7 @@ class StoreService {
|
||||
_cache.remove(key.id);
|
||||
}
|
||||
|
||||
/// Clears all values from thw store (cache and DB)
|
||||
/// Clears all values from the store (cache and DB)
|
||||
Future<void> clear() async {
|
||||
await _storeRepository.deleteAll();
|
||||
_cache.clear();
|
||||
|
||||
@@ -203,7 +203,7 @@ class TimelineService {
|
||||
Future<void> dispose() async {
|
||||
await _bucketSubscription?.cancel();
|
||||
_bucketSubscription = null;
|
||||
_buffer.clear();
|
||||
_buffer = [];
|
||||
_bufferOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
2
mobile/lib/entities/album.entity.g.dart
generated
@@ -132,7 +132,7 @@ const AlbumSchema = CollectionSchema(
|
||||
getId: _albumGetId,
|
||||
getLinks: _albumGetLinks,
|
||||
attach: _albumAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _albumEstimateSize(
|
||||
|
||||
@@ -47,7 +47,7 @@ const AndroidDeviceAssetSchema = CollectionSchema(
|
||||
getId: _androidDeviceAssetGetId,
|
||||
getLinks: _androidDeviceAssetGetLinks,
|
||||
attach: _androidDeviceAssetAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _androidDeviceAssetEstimateSize(
|
||||
|
||||
2
mobile/lib/entities/asset.entity.g.dart
generated
@@ -168,7 +168,7 @@ const AssetSchema = CollectionSchema(
|
||||
getId: _assetGetId,
|
||||
getLinks: _assetGetLinks,
|
||||
attach: _assetAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _assetEstimateSize(
|
||||
|
||||
2
mobile/lib/entities/backup_album.entity.g.dart
generated
@@ -43,7 +43,7 @@ const BackupAlbumSchema = CollectionSchema(
|
||||
getId: _backupAlbumGetId,
|
||||
getLinks: _backupAlbumGetLinks,
|
||||
attach: _backupAlbumAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _backupAlbumEstimateSize(
|
||||
|
||||
@@ -32,7 +32,7 @@ const DuplicatedAssetSchema = CollectionSchema(
|
||||
getId: _duplicatedAssetGetId,
|
||||
getLinks: _duplicatedAssetGetLinks,
|
||||
attach: _duplicatedAssetAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _duplicatedAssetEstimateSize(
|
||||
|
||||
2
mobile/lib/entities/etag.entity.g.dart
generated
@@ -52,7 +52,7 @@ const ETagSchema = CollectionSchema(
|
||||
getId: _eTagGetId,
|
||||
getLinks: _eTagGetLinks,
|
||||
attach: _eTagAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _eTagEstimateSize(
|
||||
|
||||
@@ -60,7 +60,7 @@ const IOSDeviceAssetSchema = CollectionSchema(
|
||||
getId: _iOSDeviceAssetGetId,
|
||||
getLinks: _iOSDeviceAssetGetLinks,
|
||||
attach: _iOSDeviceAssetAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _iOSDeviceAssetEstimateSize(
|
||||
|
||||
@@ -65,7 +65,7 @@ const DeviceAssetEntitySchema = CollectionSchema(
|
||||
getId: _deviceAssetEntityGetId,
|
||||
getLinks: _deviceAssetEntityGetLinks,
|
||||
attach: _deviceAssetEntityAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _deviceAssetEntityEstimateSize(
|
||||
|
||||
@@ -68,7 +68,7 @@ const ExifInfoSchema = CollectionSchema(
|
||||
getId: _exifInfoGetId,
|
||||
getLinks: _exifInfoGetLinks,
|
||||
attach: _exifInfoAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _exifInfoEstimateSize(
|
||||
|
||||
@@ -37,7 +37,7 @@ const StoreValueSchema = CollectionSchema(
|
||||
getId: _storeValueGetId,
|
||||
getLinks: _storeValueGetLinks,
|
||||
attach: _storeValueAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _storeValueEstimateSize(
|
||||
|
||||
@@ -95,7 +95,7 @@ const UserSchema = CollectionSchema(
|
||||
getId: _userGetId,
|
||||
getLinks: _userGetLinks,
|
||||
attach: _userAttach,
|
||||
version: '3.1.8',
|
||||
version: '3.3.0-dev.3',
|
||||
);
|
||||
|
||||
int _userEstimateSize(
|
||||
|
||||
@@ -15,7 +15,9 @@ import 'package:immich_mobile/constants/locales.dart';
|
||||
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
@@ -210,6 +212,14 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
ref.read(backgroundServiceProvider).disableService();
|
||||
ref.read(backgroundWorkerFgServiceProvider).enable();
|
||||
if (Platform.isAndroid) {
|
||||
ref
|
||||
.read(backgroundWorkerFgServiceProvider)
|
||||
.saveNotificationMessage(
|
||||
IntlKeys.uploading_media.t(),
|
||||
IntlKeys.backup_background_service_default_notification.t(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ref.read(backgroundWorkerFgServiceProvider).disable();
|
||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
|
||||
46
mobile/lib/platform/background_worker_api.g.dart
generated
@@ -138,6 +138,29 @@ class BackgroundWorkerFgHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveNotificationMessage(String title, String body) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[title, body]);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> configure(BackgroundWorkerSettings settings) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$pigeonVar_messageChannelSuffix';
|
||||
@@ -221,29 +244,6 @@ class BackgroundWorkerBgHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showNotification(String title, String content) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[title, content]);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix';
|
||||
|
||||
@@ -300,7 +300,7 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> {
|
||||
|
||||
await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.updateAlbum(widget.album.id, name: newTitle, description: newDescription.isEmpty ? null : newDescription);
|
||||
.updateAlbum(widget.album.id, name: newTitle, description: newDescription);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(
|
||||
|
||||
@@ -44,10 +44,7 @@ class DriftTrashPage extends StatelessWidget {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 24.0,
|
||||
child: const Text("trash_page_info").t(context: context, args: {"days": "$trashDays"}),
|
||||
),
|
||||
child: const Text("trash_page_info").t(context: context, args: {"days": "$trashDays"}),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -50,6 +50,11 @@ class DriftEditImagePage extends ConsumerWidget {
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _exitEditing(BuildContext context) {
|
||||
// this assumes that the only way to get to this page is from the AssetViewerRoute
|
||||
context.navigator.popUntil((route) => route.data?.name == AssetViewerRoute.name);
|
||||
}
|
||||
|
||||
Future<void> _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async {
|
||||
try {
|
||||
final Uint8List imageData = await _imageToUint8List(image);
|
||||
@@ -66,7 +71,7 @@ class DriftEditImagePage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
context.navigator.popUntil((route) => route.isFirst);
|
||||
_exitEditing(context);
|
||||
ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!');
|
||||
|
||||
if (localAsset == null) {
|
||||
@@ -91,7 +96,7 @@ class DriftEditImagePage extends ConsumerWidget {
|
||||
backgroundColor: context.scaffoldBackgroundColor,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () => context.navigator.popUntil((route) => route.isFirst),
|
||||
onPressed: () => _exitEditing(context),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
|
||||
@@ -599,9 +599,9 @@ class _SearchResultGrid extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchResult = ref.watch(paginatedSearchProvider);
|
||||
final assets = ref.watch(paginatedSearchProvider.select((s) => s.assets));
|
||||
|
||||
if (searchResult.totalAssets == 0) {
|
||||
if (assets.isEmpty) {
|
||||
return const _SearchEmptyContent();
|
||||
}
|
||||
|
||||
@@ -615,6 +615,7 @@ class _SearchResultGrid extends ConsumerWidget {
|
||||
|
||||
if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) {
|
||||
onScrollEnd();
|
||||
ref.read(paginatedSearchProvider.notifier).setScrollOffset(metrics.maxScrollExtent);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -623,17 +624,18 @@ class _SearchResultGrid extends ConsumerWidget {
|
||||
child: ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(searchResult.assets);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
],
|
||||
child: Timeline(
|
||||
key: ValueKey(searchResult.totalAssets),
|
||||
key: ValueKey(assets.length),
|
||||
groupBy: GroupAssetsBy.none,
|
||||
appBar: null,
|
||||
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
|
||||
snapToMonth: false,
|
||||
initialScrollOffset: ref.read(paginatedSearchProvider.select((s) => s.scrollOffset)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -24,12 +24,20 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
|
||||
return false;
|
||||
}
|
||||
|
||||
state = SearchResult(assets: [...state.assets, ...result.assets], nextPage: result.nextPage);
|
||||
state = SearchResult(
|
||||
assets: [...state.assets, ...result.assets],
|
||||
nextPage: result.nextPage,
|
||||
scrollOffset: state.scrollOffset,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setScrollOffset(double offset) {
|
||||
state = state.copyWith(scrollOffset: offset);
|
||||
}
|
||||
|
||||
clear() {
|
||||
state = const SearchResult(assets: [], nextPage: 1);
|
||||
state = const SearchResult(assets: [], nextPage: 1, scrollOffset: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class EditImageActionButton extends ConsumerWidget {
|
||||
const EditImageActionButton({super.key});
|
||||
@@ -20,12 +20,7 @@ class EditImageActionButton extends ConsumerWidget {
|
||||
}
|
||||
|
||||
final image = Image(image: getFullImageProvider(currentAsset));
|
||||
|
||||
context.navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DriftEditImagePage(asset: currentAsset, image: image, isEdited: false),
|
||||
),
|
||||
);
|
||||
context.pushRoute(DriftEditImageRoute(asset: currentAsset, image: image, isEdited: false));
|
||||
}
|
||||
|
||||
return BaseActionButton(
|
||||
|
||||
@@ -221,10 +221,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
context.scaffoldMessenger.hideCurrentSnackBar();
|
||||
|
||||
// send image to casting if the server has it
|
||||
if (asset.hasRemote) {
|
||||
final remoteAsset = asset as RemoteAsset;
|
||||
|
||||
ref.read(castProvider.notifier).loadMedia(remoteAsset, false);
|
||||
if (asset is RemoteAsset) {
|
||||
ref.read(castProvider.notifier).loadMedia(asset, false);
|
||||
} else {
|
||||
// casting cannot show local assets
|
||||
context.scaffoldMessenger.clearSnackBars();
|
||||
|
||||
@@ -51,7 +51,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
isArchived: isArchived,
|
||||
isTrashEnabled: isTrashEnable,
|
||||
isInLockedView: isInLockedView,
|
||||
isStacked: asset.hasRemote && (asset as RemoteAsset).stackId != null,
|
||||
isStacked: asset is RemoteAsset && asset.stackId != null,
|
||||
currentAlbum: currentAlbum,
|
||||
advancedTroubleshooting: advancedTroubleshooting,
|
||||
source: ActionSource.viewer,
|
||||
|
||||
@@ -44,7 +44,8 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
final showViewInTimelineButton =
|
||||
(previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) &&
|
||||
previousRouteName != AssetViewerRoute.name &&
|
||||
previousRouteName != null;
|
||||
previousRouteName != null &&
|
||||
previousRouteName != LocalTimelineRoute.name;
|
||||
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
|
||||
@@ -88,10 +88,18 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
final videoAsset = await ref.read(assetServiceProvider).getAsset(asset) ?? asset;
|
||||
if (!context.mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) {
|
||||
final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!;
|
||||
final file = await const StorageRepository().getFileForAsset(id);
|
||||
if (!context.mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
@@ -289,7 +297,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||
|
||||
final source = await videoSource;
|
||||
if (source == null) {
|
||||
if (source == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,6 +322,9 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
removeListeners(playerController);
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
isVisible.value = _isCurrentAsset(value, asset);
|
||||
}
|
||||
final curAsset = currentAsset.value;
|
||||
if (curAsset == asset) {
|
||||
return;
|
||||
|
||||
@@ -40,6 +40,7 @@ class Timeline extends StatelessWidget {
|
||||
this.groupBy,
|
||||
this.withScrubber = true,
|
||||
this.snapToMonth = true,
|
||||
this.initialScrollOffset,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
@@ -51,6 +52,7 @@ class Timeline extends StatelessWidget {
|
||||
final GroupAssetsBy? groupBy;
|
||||
final bool withScrubber;
|
||||
final bool snapToMonth;
|
||||
final double? initialScrollOffset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -78,6 +80,7 @@ class Timeline extends StatelessWidget {
|
||||
bottomSheet: bottomSheet,
|
||||
withScrubber: withScrubber,
|
||||
snapToMonth: snapToMonth,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -93,6 +96,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
this.bottomSheet,
|
||||
this.withScrubber = true,
|
||||
this.snapToMonth = true,
|
||||
this.initialScrollOffset,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
@@ -101,6 +105,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
final Widget? bottomSheet;
|
||||
final bool withScrubber;
|
||||
final bool snapToMonth;
|
||||
final double? initialScrollOffset;
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _SliverTimelineState();
|
||||
@@ -124,7 +129,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController(onAttach: _restoreScalePosition);
|
||||
_scrollController = ScrollController(
|
||||
initialScrollOffset: widget.initialScrollOffset ?? 0.0,
|
||||
onAttach: _restoreScalePosition,
|
||||
);
|
||||
_eventSubscription = EventStream.shared.listen(_onEvent);
|
||||
|
||||
final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow);
|
||||
|
||||
@@ -77,11 +77,14 @@ class ActionNotifier extends Notifier<void> {
|
||||
return _getAssets(source).whereType<RemoteAsset>().toIds().toList(growable: false);
|
||||
}
|
||||
|
||||
List<String> _getLocalIdsForSource(ActionSource source) {
|
||||
List<String> _getLocalIdsForSource(ActionSource source, {bool ignoreLocalOnly = false}) {
|
||||
final Set<BaseAsset> assets = _getAssets(source);
|
||||
final List<String> localIds = [];
|
||||
|
||||
for (final asset in assets) {
|
||||
if (ignoreLocalOnly && asset.storage != AssetState.merged) {
|
||||
continue;
|
||||
}
|
||||
if (asset is LocalAsset) {
|
||||
localIds.add(asset.id);
|
||||
} else if (asset is RemoteAsset && asset.localId != null) {
|
||||
@@ -189,7 +192,7 @@ class ActionNotifier extends Notifier<void> {
|
||||
|
||||
Future<ActionResult> moveToLockFolder(ActionSource source) async {
|
||||
final ids = _getOwnedRemoteIdsForSource(source);
|
||||
final localIds = _getLocalIdsForSource(source);
|
||||
final localIds = _getLocalIdsForSource(source, ignoreLocalOnly: true);
|
||||
try {
|
||||
await _service.moveToLockFolder(ids, localIds);
|
||||
return ActionResult(count: ids.length, success: true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -25,7 +26,28 @@ class AssetMediaRepository {
|
||||
|
||||
const AssetMediaRepository(this._assetApiRepository);
|
||||
|
||||
Future<List<String>> deleteAll(List<String> ids) => PhotoManager.editor.deleteWithIds(ids);
|
||||
Future<bool> _androidSupportsTrash() async {
|
||||
if (Platform.isAndroid) {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
int sdkVersion = androidInfo.version.sdkInt;
|
||||
return sdkVersion >= 31;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<List<String>> deleteAll(List<String> ids) async {
|
||||
if (CurrentPlatform.isAndroid) {
|
||||
if (await _androidSupportsTrash()) {
|
||||
return PhotoManager.editor.android.moveToTrash(
|
||||
ids.map((e) => AssetEntity(id: e, width: 1, height: 1, typeInt: 0)).toList(),
|
||||
);
|
||||
} else {
|
||||
return PhotoManager.editor.deleteWithIds(ids);
|
||||
}
|
||||
}
|
||||
return PhotoManager.editor.deleteWithIds(ids);
|
||||
}
|
||||
|
||||
Future<asset_entity.Asset?> get(String id) async {
|
||||
final entity = await AssetEntity.fromId(id);
|
||||
|
||||
@@ -121,7 +121,7 @@ class DownloadRepository {
|
||||
_dummyMetadata['part'] = LivePhotosPart.video.index;
|
||||
tasks[taskIndex++] = DownloadTask(
|
||||
taskId: livePhotoVideoId,
|
||||
url: url,
|
||||
url: getOriginalUrlForRemoteId(livePhotoVideoId),
|
||||
headers: headers,
|
||||
filename: asset.name.toUpperCase().replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'),
|
||||
updates: Updates.statusAndProgress,
|
||||
|
||||
@@ -22,6 +22,8 @@ class BackgroundWorkerSettings {
|
||||
abstract class BackgroundWorkerFgHostApi {
|
||||
void enable();
|
||||
|
||||
void saveNotificationMessage(String title, String body);
|
||||
|
||||
void configure(BackgroundWorkerSettings settings);
|
||||
|
||||
void disable();
|
||||
@@ -33,8 +35,6 @@ abstract class BackgroundWorkerBgHostApi {
|
||||
// required platform channels to notify the native side to start the background upload
|
||||
void onInitialized();
|
||||
|
||||
void showNotification(String title, String content);
|
||||
|
||||
// Called from the background flutter engine to request the native side to cleanup
|
||||
void close();
|
||||
}
|
||||
|
||||
@@ -317,10 +317,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27"
|
||||
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.3"
|
||||
version: "6.1.5"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -437,18 +437,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513"
|
||||
sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.3"
|
||||
version: "12.1.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
|
||||
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
version: "7.0.3"
|
||||
drift:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1022,25 +1022,34 @@ packages:
|
||||
isar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar
|
||||
sha256: e17a9555bc7f22ff26568b8c64d019b4ffa2dc6bd4cb1c8d9b269aefd32e53ad
|
||||
url: "https://pub.isar-community.dev"
|
||||
source: hosted
|
||||
path: "packages/isar"
|
||||
ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
|
||||
resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
|
||||
url: "https://github.com/immich-app/isar"
|
||||
source: git
|
||||
version: "3.1.8"
|
||||
isar_flutter_libs:
|
||||
isar_community:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: isar_community
|
||||
sha256: "28f59e54636c45ba0bb1b3b7f2656f1c50325f740cea6efcd101900be3fba546"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0-dev.3"
|
||||
isar_community_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar_flutter_libs
|
||||
sha256: "78710781e658ce4bff59b3f38c5b2735e899e627f4e926e1221934e77b95231a"
|
||||
url: "https://pub.isar-community.dev"
|
||||
name: isar_community_flutter_libs
|
||||
sha256: c2934fe755bb3181cb67602fd5df0d080b3d3eb52799f98623aa4fc5acbea010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.8"
|
||||
version: "3.3.0-dev.3"
|
||||
isar_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
path: "packages/isar_generator"
|
||||
ref: v3
|
||||
resolved-ref: ad574f60ed6f39d2995cd16fc7dc3de9a646ef30
|
||||
ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
|
||||
resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
|
||||
url: "https://github.com/immich-app/isar"
|
||||
source: git
|
||||
version: "3.1.8"
|
||||
|
||||
@@ -8,8 +8,6 @@ environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
flutter: 3.35.4
|
||||
|
||||
isar_version: &isar_version 3.1.8
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
@@ -24,7 +22,7 @@ dependencies:
|
||||
connectivity_plus: ^6.1.3
|
||||
crop_image: ^1.0.16
|
||||
crypto: ^3.0.6
|
||||
device_info_plus: ^11.3.3
|
||||
device_info_plus: ^12.0.0
|
||||
dynamic_color: ^1.7.0
|
||||
easy_image_viewer: ^1.5.1
|
||||
easy_localization: ^3.0.7+1
|
||||
@@ -81,11 +79,11 @@ dependencies:
|
||||
openapi:
|
||||
path: openapi
|
||||
isar:
|
||||
version: *isar_version
|
||||
hosted: https://pub.isar-community.dev/
|
||||
isar_flutter_libs: # contains Isar Core
|
||||
version: *isar_version
|
||||
hosted: https://pub.isar-community.dev/
|
||||
git:
|
||||
url: https://github.com/immich-app/isar
|
||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||
path: packages/isar/
|
||||
isar_community_flutter_libs: 3.3.0-dev.3
|
||||
# DB
|
||||
drift: ^2.23.1
|
||||
drift_flutter: ^0.2.4
|
||||
@@ -101,7 +99,7 @@ dev_dependencies:
|
||||
isar_generator:
|
||||
git:
|
||||
url: https://github.com/immich-app/isar
|
||||
ref: v3
|
||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||
path: packages/isar_generator/
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18.1",
|
||||
"@types/node": "^22.18.8",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"description": "Monorepo for Immich",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
|
||||
"packageManager": "pnpm@10.18.0+sha512.e804f889f1cecc40d572db084eec3e4881739f8dec69c0ff10d2d1beff9a4e309383ba27b5b750059d7f4c149535b6cd0d2cb1ed3aeb739239a4284a68f40cfa",
|
||||
"engines": {
|
||||
"pnpm": ">=10.0.0"
|
||||
}
|
||||
|
||||
5307
pnpm-lock.yaml
generated
@@ -27,7 +27,7 @@ onlyBuiltDependencies:
|
||||
- '@tailwindcss/oxide'
|
||||
overrides:
|
||||
canvas: 2.11.2
|
||||
sharp: ^0.34.3
|
||||
sharp: ^0.34.4
|
||||
packageExtensions:
|
||||
nestjs-kysely:
|
||||
dependencies:
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
<a href="README_de_DE.md">Deutsch</a>
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_zh_CN.md">简体中文</a>
|
||||
<a href="README_zh_TW.md">正體中文</a>
|
||||
<a href="README_uk_UA.md">Українська</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
<a href="README_de_DE.md">Deutsch</a>
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_zh_CN.md">简体中文</a>
|
||||
<a href="README_zh_TW.md">正體中文</a>
|
||||
<a href="README_uk_UA.md">Українська</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
<a href="README_ko_KR.md">한국어</a>
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_zh_CN.md">简体中文</a>
|
||||
<a href="README_zh_TW.md">正體中文</a>
|
||||
<a href="README_uk_UA.md">Українська</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
|
||||