Compare commits

...

54 Commits

Author SHA1 Message Date
Alex The Bot
cfcae39699 Version v1.53.0 2023-04-06 04:56:28 +00:00
Alex Tran
4c923bae7d chore(mobile): Add changelog 2023-04-05 23:55:23 -05:00
Zack Pollard
a5a6bebf0b feat(all): transcoding improvements (#2171)
* test: rename some fixtures and add text for vertical video conversion

* feat: transcode video asset when audio or container don't match target

* chore: add niceness to the ffmpeg command to allow other processes to be prioritised

* chore: change video conversion queue to one concurrency

* feat: add transcode disabled preset to completely turn off transcoding

* linter

* Change log level and remove unused await

* opps forgot to save

* better logging

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-04-05 22:32:59 -05:00
Michel Heusschen
6f1d0a3caa fix(web): remove search bar focus after searching (#2177) 2023-04-05 11:43:52 -05:00
Michel Heusschen
2b5484539d fix(web): hide assets when selecting album cover (#2176) 2023-04-05 11:41:27 -05:00
Michel Heusschen
5d21dc95ea fix(web): small gap in navbar (#2175) 2023-04-05 11:40:27 -05:00
Michel Heusschen
e32b6c98df chore(web): update svelte related packages (#2174) 2023-04-05 11:40:00 -05:00
Michel Heusschen
7b9248c10a chore(server): update typeorm (#2173) 2023-04-05 13:23:03 +01:00
Alex
4853240de9 fix(server): Revert alpine version to fix Sharp dependencies not installed correctly (#2172) 2023-04-05 02:57:30 +00:00
Michel Heusschen
d5f2e3e45c refactor(server): more consistent param validation (#2166) 2023-04-04 17:24:08 -05:00
martyfuhry
ad680b6a35 fix(mobile): Fixed hero animation re-enabling on immich asset grid (#2169)
* fixed hero animation re-enabling on immich asset grid

* comments
2023-04-04 17:23:47 -05:00
Jason Rasmussen
4cb74f0fe4 refactor(server): reverse geocoding (#2167)
* refactor(server): reverse geocoding

* fix: nullable results
2023-04-04 17:23:07 -05:00
Alex
333ab1124b fix(mobile): shared page does not get all shared albums (#2160) 2023-04-04 13:40:23 -05:00
Jason Rasmussen
48393c215b refactor(server): video transcode processor (#2163)
* refactor(server): video transcode processor

* refactor: rename shouldRotate to isVideoVertical, remove unnecessary await

* refactor: rename getOptions to getFfmpegOptions to be clearer in that context

* fix: optimal preset converting vertical videos already smaller than target resolution

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2023-04-04 10:48:02 -04:00
Jason Rasmussen
ec6a7ae97c fix(server): do not link live photos across users (#2162) 2023-04-03 23:48:05 -05:00
Zack Pollard
808d6423be feat(all): ffmpeg quality options improvements (#2161)
* feat: change target scaling to resolution in ffmpeg config

* feat(microservices): scale vertical video correctly, only scale if video is larger than target
2023-04-03 20:42:53 -05:00
Mauro Molin
9076f3e69e Fix bulk upload example commands to work with paths with spaces (#2159)
The examples commands didn't work when the result of the subcommand $(pwd) contained spaces. This commit adds the proper escaping for them to work. This change also fixes the behavior of the alias allowing the correct current directory to be passed when it is placed inside the .bashrc file.
2023-04-03 20:19:10 -05:00
Sergey Kondrikov
7e526f87b4 feat(server): enhanced thumbnails generation code (#2147)
* Add size parameter to extractVideoThumbnail

* Ensure minimum dimension of webp thumbnail
2023-04-03 20:18:27 -05:00
Skyler Mäntysaari
fc585bffcc feat(server): Support TypeSense High-Availibility configuration (#2146)
* feat(server): Support TypeSense High-Availibility configuration.

* Lint fixes

* Address comments.
2023-04-03 20:16:45 -05:00
Alex
d6f2ca6aaa feat(mobile): improved logging page experience (#2158)
* feat(mobile): improve logging page

* Use new API for share file

* removed unused code

* Better safe area on the home screen

* Added preparing share dialog to home screen
2023-04-03 16:43:46 -05:00
Devin Buhl
2dcccb37a0 chore(docker): Default NODE_ENV to production for server image and update alpine version (#2157)
* default NODE_ENV to production for server image

* update node image to use 3.17 alpine in server

* update web docker image to use alpine 3.17

* remove NODE_ENV from production docker-compose

* NODE_ENV is also needed default in machine-learning
2023-04-03 15:05:29 -05:00
Michel Heusschen
c584791b65 feat(server): improve validation in controllers (#2149)
* feat(server): improve validation in controllers

* set ValidationPipe config with decorator
2023-04-02 23:24:18 -05:00
bo0tzz
ed551500e7 chore: Simplify install script (#2148)
We can directly access the artifacts from the latest release, rather than needing to get the tag from the API first.
2023-04-02 14:13:24 -05:00
AndreAle94
94b2ea9b5f Add timezone to exif entity (#1894)
* Add timezone to exif entity

* Refactor logging

---------

Co-authored-by: Andrea Alemani <andrea.alemani94@gmail.com>
2023-04-02 14:11:24 -05:00
Alex
8b001b87d2 chores(doc): Update documentation (#2145)
* Update app architecture with typesense

* Update readme

* Added local search

* replace diagram

* Update search page
2023-04-01 21:22:16 -05:00
Michel Heusschen
b06ddec2d5 feat(server/web): jobs clear button + queue status (#2144)
* feat(server/web): jobs clear button + queue status

* adjust design and colors

* Adjust some styling

* show status next to buttons instead of on top

* Update rounded corner for badge

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-04-01 15:46:07 -05:00
Alex
d04f340b5b fix(server): user update (#2143)
* fix(server): user update

* update dto

* generate api

* improve validation

* add e2e tests for updating user

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
2023-04-01 11:43:45 -05:00
Michel Heusschen
aaaf1a6cf8 feat(web): pause and resume jobs (#2125)
* feat(web): pause and resume jobs

* add bg color to status instead of using badge

* styling

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-03-31 23:53:20 -05:00
Skyler Mäntysaari
23e4449f27 feat(server): redis sentinel support (#2141)
* feat(server): redis sentinel initial support

* feat(server): Lint fixes

* Include example for Redis Sentinel.

* Address PR comments
2023-03-31 15:33:21 -05:00
Michel Heusschen
51785a1ead feat(server): apply ValidationPipe on controllers (#2137) 2023-03-31 10:14:01 -05:00
Jason Rasmussen
49f66be8af chore(server): use ioredis (#2116) 2023-03-31 09:36:08 -05:00
Michel Heusschen
009b6e3ca5 feat(server): add faststart to ffmpeg options (#2138) 2023-03-31 09:34:54 -05:00
Thomas
c011b06bea docs: use correct helm chart url (#2133) 2023-03-31 05:25:53 -05:00
Jason Rasmussen
34d300d1da refactor(server): flatten infra folders (#2120)
* refactor: flatten infra folders

* fix: database migrations

* fix: test related import

* fix: github actions workflow

* chore: rename schemas to typesense-schemas
2023-03-30 14:38:55 -05:00
Jason Rasmussen
468e620372 chore: remove unused config (#2121) 2023-03-30 12:22:25 -05:00
manuke42
e05153d7bb add fdroid isar build script (#2100)
Co-authored-by: Manuel K <manuel@kerk-net.de>
2023-03-30 10:31:44 +02:00
Alex Tran
4aa4a3b597 chore: pump openapi gen files 2023-03-29 13:18:09 -05:00
Alex The Bot
b1d17302bc Version v1.52.1 2023-03-29 17:37:33 +00:00
Alex
cc3ffcbb84 fix(server): incorrect video file path to serve for mobile vs web (#2118) 2023-03-29 12:36:18 -05:00
Michel Heusschen
eda9e580c9 fix(server): add paused property to JobCountsDto (#2112) 2023-03-29 10:33:03 -05:00
Alex Tran
76a07a3ebc chore: add change logs 2023-03-28 17:19:10 -05:00
Alex Tran
abe87686a2 chore: post release openapi update 2023-03-28 15:49:21 -05:00
Alex Tran
6371c11fc5 Merge branch 'main' of github.com:immich-app/immich 2023-03-28 15:48:50 -05:00
Alex Tran
2f64af9cb2 Merge branch 'main' of github.com:immich-app/immich 2023-03-28 15:07:53 -05:00
Alex Tran
0c61521521 Merge branch 'main' of github.com:immich-app/immich 2023-03-28 14:47:51 -05:00
Alex Tran
10ccbeab35 Merge branch 'main' of github.com:immich-app/immich 2023-03-28 13:45:53 -05:00
Alex Tran
6f3f8b0a48 Merge branch 'main' of github.com:immich-app/immich 2023-03-28 09:57:08 -05:00
Alex Tran
5e207aa7c1 Merge branch 'main' of github.com:immich-app/immich 2023-03-27 22:07:15 -05:00
Alex Tran
97bbe42599 Merge branch 'main' of github.com:immich-app/immich 2023-03-27 10:41:24 -05:00
Alex Tran
833c099025 Merge branch 'main' of github.com:immich-app/immich 2023-03-27 08:45:12 -05:00
Alex Tran
75d28d3c58 Merge branch 'main' of github.com:immich-app/immich 2023-03-26 10:43:52 -05:00
Alex Tran
2f9fcd96c7 Merge branch 'main' of github.com:immich-app/immich 2023-03-25 21:51:10 -05:00
Alex Tran
193dd01e06 Merge branch 'main' of github.com:immich-app/immich 2023-03-25 10:15:48 -05:00
Alex Tran
c0ed623d26 chore: fix api 2023-03-24 23:17:40 -05:00
315 changed files with 5187 additions and 1587 deletions

View File

@@ -110,13 +110,13 @@ jobs:
continue-on-error: true
run: |
cd server
npm run typeorm:migrations:generate ./libs/infra/src/db/migrations/TestMigration
npm run typeorm:migrations:generate ./libs/infra/src/migrations/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
id: verify-changed-files
with:
files: |
server/libs/infra/src/db/migrations/
server/libs/infra/src/migrations/
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |

View File

@@ -72,7 +72,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Scrubbable/draggable scrollbar | Yes | Yes |
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes |
| Metadata view (EXIF, map) | Yes | Yes |
| Search by metadata, objects and image tags | Yes | No |
| Search by metadata, objects and CLIP | Yes | No |
| Administrative functions (user management) | N/A | Yes |
| Background backup | Yes | N/A |
| Virtual scroll | Yes | Yes |

View File

@@ -9,8 +9,6 @@ services:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
environment:
- NODE_ENV=production
depends_on:
- redis
- database
@@ -25,8 +23,6 @@ services:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
environment:
- NODE_ENV=production
depends_on:
- redis
- database
@@ -41,8 +37,6 @@ services:
- model-cache:/cache
env_file:
- .env
environment:
- NODE_ENV=production
restart: always
immich-web:

View File

@@ -16,6 +16,11 @@ DB_DATABASE_NAME=immich
REDIS_HOSTNAME=immich_redis
# REDIS_URL will be used to pass custom options to ioredis.
# Example for Sentinel
# {"sentinels":[{"host":"redis-sentinel-node-0","port":26379},{"host":"redis-sentinel-node-1","port":26379},{"host":"redis-sentinel-node-2","port":26379}],"name":"redis-sentinel"}
# REDIS_URL=ioredis://eyJzZW50aW5lbHMiOlt7Imhvc3QiOiJyZWRpcy1zZW50aW5lbDEiLCJwb3J0IjoyNjM3OX0seyJob3N0IjoicmVkaXMtc2VudGluZWwyIiwicG9ydCI6MjYzNzl9XSwibmFtZSI6Im15bWFzdGVyIn0=
# Optional Redis settings:
# Note: these parameters are not automatically passed to the Redis Container
@@ -24,6 +29,7 @@ REDIS_HOSTNAME=immich_redis
# REDIS_PORT=6379
# REDIS_DBINDEX=0
# REDIS_USERNAME=
# REDIS_PASSWORD=
# REDIS_SOCKET=
@@ -41,6 +47,14 @@ UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_ba
###################################################################################
TYPESENSE_API_KEY=some-random-text
# TYPESENSE_ENABLED=false
# TYPESENSE_URL uses base64 encoding for the nodes json.
# Example JSON that was used:
# [
# { 'host': 'typesense-1.example.net', 'port': '443', 'protocol': 'https' },
# { 'host': 'typesense-2.example.net', 'port': '443', 'protocol': 'https' },
# { 'host': 'typesense-3.example.net', 'port': '443', 'protocol': 'https' },
# ]
# TYPESENSE_URL=ha://WwogICAgeyAnaG9zdCc6ICd0eXBlc2Vuc2UtMS5leGFtcGxlLm5ldCcsICdwb3J0JzogJzQ0MycsICdwcm90b2NvbCc6ICdodHRwcycgfSwKICAgIHsgJ2hvc3QnOiAndHlwZXNlbnNlLTIuZXhhbXBsZS5uZXQnLCAncG9ydCc6ICc0NDMnLCAncHJvdG9jb2wnOiAnaHR0cHMnIH0sCiAgICB7ICdob3N0JzogJ3R5cGVzZW5zZS0zLmV4YW1wbGUubmV0JywgJ3BvcnQnOiAnNDQzJywgJ3Byb3RvY29sJzogJ2h0dHBzJyB9LApd
###################################################################################
# Reverse Geocoding

View File

@@ -20,7 +20,7 @@ Immich is a full-stack [TypeScript](https://www.typescriptlang.org/) application
### Web
- [SvelteKit](https://kit.svelte.dev/)
- [tailwindcss](https://tailwindcss.com/)
- [Tailwindcss](https://tailwindcss.com/)
### Server
@@ -34,6 +34,8 @@ Immich is a full-stack [TypeScript](https://www.typescriptlang.org/) application
- [PostgreSQL](https://www.postgresql.org/)
- [Redis](https://redis.io/) for job queuing.
- [Typesense](https://typesense.org/) for search.
### Web Server

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 570 KiB

View File

@@ -60,15 +60,15 @@ Be aware that as this runs inside a container, you need to mount the folder from
```bash title="Upload current directory"
cd /DIRECTORY/WITH/IMAGES
docker run -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
```bash title="Upload target directory"
docker run -it --rm -v /DIRECTORY/WITH/IMAGES:/import ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
```bash title="Create an alias"
alias immich="docker run -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest"
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
@@ -79,7 +79,7 @@ If you are running the CLI container on the same machine as your Immich server,
3. Use `--server http://immich-server:3001/` for the upload command instead of the external address.
```bash title="Upload to internal address"
docker run --network immich_default -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001/
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001/
```
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,16 +1,20 @@
# Search
:::warning Work In Progress
Search is work-in-progress and subject to change. Stay tuned!
:::
Immich uses Typesense as the primary search database to enable high performance search mechanism.
## Search by Place
Typesense is a powerful search engine that can be integrated with popular natural language processing (NLP) models like CLIP and SBERT to provide highly accurate and relevant search results. Here are some benefits of using Typesense integrated search for CLIP and SBERT:
:::info
Searching is currently only implemented in the [Mobile App](/docs/features/mobile-app.mdx)
:::
Improved Search Accuracy: Typesense uses a combination of indexing, querying, and ranking algorithms to quickly and accurately retrieve relevant search results. When integrated with CLIP and SBERT, Typesense can leverage the semantic understanding and deep learning capabilities of these models to further improve the accuracy of search results.
Searching by the name of a city, state, or country is possible for assets with geolocation data and successful [Reverse Geocoding](/docs/features/reverse-geocoding.md).
Faster Search Response Times: Typesense is optimized for lightning-fast search response times, making it ideal for applications that require near-instantaneous search results. By integrating with CLIP and SBERT, Typesense can reduce the time required to process complex search queries, making it even faster and more efficient.
<img src={require('./img/reverse-geocoding-mobile1.png').default} width='33%' title='Reverse Geocoding' />
<img src={require('./img/reverse-geocoding-mobile2.png').default} width='33%' title='Reverse Geocoding' />
Enhanced Semantic Search Capabilities: CLIP and SBERT are powerful NLP models that can extract the semantic meaning from text, enabling more nuanced search queries. By integrating with Typesense, these models can help to improve the accuracy of semantic search, enabling users to find the most relevant results based on the true meaning of their query.
Greater Search Flexibility: Typesense provides flexible search capabilities, including fuzzy search, partial search, enabling users to find the information they need quickly and easily. When integrated with CLIP and SBERT, Typesense can offer even greater flexibility, allowing users to refine their search queries using natural language and providing more accurate and relevant results.
(Generated by Chat-GPT4)
Some search examples:
<img src={require('./img/search-ex-2.webp').default} title='Search Example 1' />
<img src={require('./img/search-ex-3.webp').default} title='Search Example 2' />

View File

@@ -4,12 +4,12 @@ sidebar_position: 40
# Kubernetes
You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/tree/main/charts/apps/immich).
You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/tree/main/charts/immich).
If you want examples of how other people run Immich on Kubernetes, using the official chart or otherwise, you can find them at https://nanne.dev/k8s-at-home-search/#/immich.
:::caution DNS in Alpine containers
Immich makes use of Alpine container images. These can encounter [a DNS resolution bug](https://stackoverflow.com/a/65593511) on Kubernetes clusters if the host
Immich makes use of Alpine container images. These can encounter [a DNS resolution bug](https://stackoverflow.com/a/65593511) on Kubernetes clusters if the host
nodes have a search domain set, like:
```
@@ -18,7 +18,7 @@ search home.lan
nameserver 192.168.1.1
```
When you encounter this bug, it will cause the immich-microservices to crash on startup because it cannot download
the geocoder data. This can be solved in one of two ways: Either reconfigure your nodes to remove the searchdomain from
When you encounter this bug, it will cause the immich-microservices to crash on startup because it cannot download
the geocoder data. This can be solved in one of two ways: Either reconfigure your nodes to remove the searchdomain from
`resolv.conf`, or set the `DISABLE_REVERSE_GEOCODING` environment variable for Immich to `true` to disable the geocoder.
:::
:::

View File

@@ -1,70 +1,71 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require("prism-react-renderer/themes/github");
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Immich",
title: 'Immich',
tagline:
"High performance self-hosted photo and video backup solution directly from your mobile phone",
url: "https://documentation.immich.app",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "img/favicon.png",
'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://documentation.immich.app',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.png',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: "immich-app", // Usually your GitHub org/user name.
projectName: "immich", // Usually your repo name.
deploymentBranch: "main",
organizationName: 'immich-app', // Usually your GitHub org/user name.
projectName: 'immich', // Usually your repo name.
deploymentBranch: 'main',
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: "en",
locales: ["en"],
defaultLocale: 'en',
locales: ['en'],
},
plugins: [
async function myPlugin(context, options) {
return {
name: "docusaurus-tailwindcss",
name: 'docusaurus-tailwindcss',
configurePostCss(postcssOptions) {
// Appends TailwindCSS and AutoPrefixer.
postcssOptions.plugins.push(require("tailwindcss"));
postcssOptions.plugins.push(require("autoprefixer"));
postcssOptions.plugins.push(require('tailwindcss'));
postcssOptions.plugins.push(require('autoprefixer'));
return postcssOptions;
},
};
},
require.resolve('docusaurus-lunr-search'),
],
presets: [
[
"docusaurus-preset-openapi",
'docusaurus-preset-openapi',
/** @type {import('docusaurus-preset-openapi').Options} */
({
docs: {
showLastUpdateAuthor: true,
showLastUpdateTime: true,
sidebarPath: require.resolve("./sidebars.js"),
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
editUrl: 'https://github.com/immich-app/immich/tree/main/docs/',
},
api: {
path: "../server/immich-openapi-specs.json",
routeBasePath: "/docs/api",
path: '../server/immich-openapi-specs.json',
routeBasePath: '/docs/api',
},
// blog: {
// showReadingTime: true,
// editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
// },
theme: {
customCss: require.resolve("./src/css/custom.css"),
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
@@ -74,13 +75,13 @@ const config = {
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
colorMode: {
defaultMode: "dark",
defaultMode: 'dark',
},
announcementBar: {
id: "site_announcement_immich",
id: 'site_announcement_immich',
content: `⚠️ The project is under <strong>very active</strong> development. Expect bugs and changes. Do not use it as <strong>the only way</strong> to store your photos and videos!`,
backgroundColor: "#593f00",
textColor: "#ffefc9",
backgroundColor: '#593f00',
textColor: '#ffefc9',
isCloseable: false,
},
docs: {
@@ -90,72 +91,72 @@ const config = {
},
navbar: {
logo: {
alt: "Immich University Logo",
src: "img/color-logo.png",
srcDark: "img/logo.png",
alt: 'Immich University Logo',
src: 'img/color-logo.png',
srcDark: 'img/logo.png',
},
items: [
{
to: "/docs/overview/introduction",
position: "right",
label: "Docs",
to: '/docs/overview/introduction',
position: 'right',
label: 'Docs',
},
{
to: "/docs/api",
position: "right",
label: "API",
to: '/docs/api',
position: 'right',
label: 'API',
},
{
href: "https://github.com/immich-app/immich",
label: "GitHub",
position: "right",
href: 'https://github.com/immich-app/immich',
label: 'GitHub',
position: 'right',
},
{
href: "https://github.com/orgs/immich-app/projects/1",
label: "Roadmap",
position: "right",
href: 'https://github.com/orgs/immich-app/projects/1',
label: 'Roadmap',
position: 'right',
},
],
},
footer: {
style: "light",
style: 'light',
links: [
{
title: "Overview",
title: 'Overview',
items: [
{
label: "Welcome",
to: "/docs/overview/introduction",
label: 'Welcome',
to: '/docs/overview/introduction',
},
{
label: "Installation",
to: "/docs/install/requirements",
label: 'Installation',
to: '/docs/install/requirements',
},
],
},
{
title: "Community",
title: 'Community',
items: [
{
label: "Discord",
href: "https://discord.com/invite/D8JsnBEuKb",
label: 'Discord',
href: 'https://discord.com/invite/D8JsnBEuKb',
},
],
},
{
title: "Links",
title: 'Links',
items: [
// {
// label: "Blog",
// to: "/blog",
// },
{
label: "GitHub",
href: "https://github.com/immich-app/immich",
label: 'GitHub',
href: 'https://github.com/immich-app/immich',
},
{
label: "Roadmap",
href: "https://github.com/orgs/immich-app/projects/1",
label: 'Roadmap',
href: 'https://github.com/orgs/immich-app/projects/1',
},
],
},
@@ -166,7 +167,7 @@ const config = {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
image: "overview/img/feature-panel.png",
image: 'overview/img/feature-panel.png',
}),
};

591
docs/package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.20",
"prism-react-renderer": "^1.3.5",
@@ -3634,6 +3635,11 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -3903,6 +3909,11 @@
"node": ">= 8"
}
},
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -3957,6 +3968,14 @@
"node": ">= 4.0.0"
}
},
"node_modules/autocomplete.js": {
"version": "0.37.1",
"resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.37.1.tgz",
"integrity": "sha512-PgSe9fHYhZEsm/9jggbjtVsGXJkPLvd+9mC7gZJ662vVL5CRWEtm/mIrrzCx0MrNxHVwxD5d00UOn6NsmL2LUQ==",
"dependencies": {
"immediate": "^3.2.3"
}
},
"node_modules/autoprefixer": {
"version": "10.4.13",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -4148,6 +4167,15 @@
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="
},
"node_modules/bcp-47-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz",
"integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -4583,6 +4611,11 @@
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz",
"integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw=="
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@@ -4743,6 +4776,14 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@@ -4887,6 +4928,11 @@
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@@ -5278,6 +5324,11 @@
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-selector-parser": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
"integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
},
"node_modules/css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
@@ -5664,6 +5715,18 @@
"node": ">=8"
}
},
"node_modules/direction": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
"bin": {
"direction": "cli.js"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -5685,6 +5748,53 @@
"node": ">=6"
}
},
"node_modules/docusaurus-lunr-search": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-2.3.2.tgz",
"integrity": "sha512-Ngvm2kXwliWThqAThXI1912rOKHlFL7BjIc+OVNUfzkjpk5ar4TFEh+EUaaMOLw4V0BBko3CW0Ym7prqqm3jLQ==",
"dependencies": {
"autocomplete.js": "^0.37.0",
"classnames": "^2.2.6",
"gauge": "^3.0.0",
"hast-util-select": "^4.0.0",
"hast-util-to-text": "^2.0.0",
"hogan.js": "^3.0.2",
"lunr": "^2.3.8",
"lunr-languages": "^1.4.0",
"minimatch": "^3.0.4",
"object-assign": "^4.1.1",
"rehype-parse": "^7.0.1",
"to-vfile": "^6.1.0",
"unified": "^9.0.0",
"unist-util-is": "^4.0.2"
},
"engines": {
"node": ">= 8.10.0"
},
"peerDependencies": {
"@docusaurus/core": "^2.0.0-alpha.60 || ^2.0.0",
"react": "^16.8.4 || ^17",
"react-dom": "^16.8.4 || ^17"
}
},
"node_modules/docusaurus-lunr-search/node_modules/parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"node_modules/docusaurus-lunr-search/node_modules/rehype-parse": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-7.0.1.tgz",
"integrity": "sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw==",
"dependencies": {
"hast-util-from-parse5": "^6.0.0",
"parse5": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/docusaurus-plugin-openapi": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.3.tgz",
@@ -6689,6 +6799,43 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gauge/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/gauge/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -6994,6 +7141,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/has-yarn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
@@ -7037,6 +7189,24 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-has-property": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz",
"integrity": "sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-is-element": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz",
"integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
@@ -7072,6 +7242,31 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"node_modules/hast-util-select": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-4.0.2.tgz",
"integrity": "sha512-8EEG2//bN5rrzboPWD2HdS3ugLijNioS1pqOTIolXNf67xxShYw4SQEmVXd3imiBG+U2bC2nVTySr/iRAA7Cjg==",
"dependencies": {
"bcp-47-match": "^1.0.0",
"comma-separated-tokens": "^1.0.0",
"css-selector-parser": "^1.0.0",
"direction": "^1.0.0",
"hast-util-has-property": "^1.0.0",
"hast-util-is-element": "^1.0.0",
"hast-util-to-string": "^1.0.0",
"hast-util-whitespace": "^1.0.0",
"not": "^0.1.0",
"nth-check": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0",
"unist-util-visit": "^2.0.0",
"zwitch": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-parse5": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz",
@@ -7088,6 +7283,38 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-string": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz",
"integrity": "sha512-eK0MxRX47AV2eZ+Lyr18DCpQgodvaS3fAQO2+b9Two9F5HEoRPhiUMNzoXArMJfZi2yieFzUBMRl3HNJ3Jus3w==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-text": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-2.0.1.tgz",
"integrity": "sha512-8nsgCARfs6VkwH2jJU9b8LNTuR4700na+0h3PqCaEk4MAnMDeu5P0tP8mjk9LLNGxIeQRLbiDbZVw6rku+pYsQ==",
"dependencies": {
"hast-util-is-element": "^1.0.0",
"repeat-string": "^1.0.0",
"unist-util-find-after": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-whitespace": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz",
"integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
@@ -7133,6 +7360,18 @@
"value-equal": "^1.0.1"
}
},
"node_modules/hogan.js": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
"dependencies": {
"mkdirp": "0.3.0",
"nopt": "1.0.10"
},
"bin": {
"hulk": "bin/hulk"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -7435,6 +7674,11 @@
"node": ">=14.0.0"
}
},
"node_modules/immediate": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
},
"node_modules/immer": {
"version": "9.0.16",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz",
@@ -8232,6 +8476,16 @@
"node": ">=10"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
},
"node_modules/lunr-languages": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.10.0.tgz",
"integrity": "sha512-BBjKKcwrieJlzwwc9M5H/MRXGJ2qyOSDx/NXYiwkuKjiLOOoouh0WsDzeqcLoUWcX31y7i8sb8IgsZKObdUCkw=="
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -8538,6 +8792,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
"engines": {
"node": "*"
}
},
"node_modules/monaco-editor": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.31.1.tgz",
@@ -8657,6 +8920,20 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg=="
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -8684,6 +8961,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/not": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
"integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -12774,6 +13056,19 @@
"node": ">=8.0"
}
},
"node_modules/to-vfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-6.1.0.tgz",
"integrity": "sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==",
"dependencies": {
"is-buffer": "^2.0.0",
"vfile": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -12989,6 +13284,18 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-find-after": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz",
"integrity": "sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ==",
"dependencies": {
"unist-util-is": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-generated": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz",
@@ -13943,6 +14250,32 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wide-align/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/wide-align/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
@@ -16831,6 +17164,11 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -17031,6 +17369,11 @@
"picomatch": "^2.0.4"
}
},
"aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -17076,6 +17419,14 @@
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"autocomplete.js": {
"version": "0.37.1",
"resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.37.1.tgz",
"integrity": "sha512-PgSe9fHYhZEsm/9jggbjtVsGXJkPLvd+9mC7gZJ662vVL5CRWEtm/mIrrzCx0MrNxHVwxD5d00UOn6NsmL2LUQ==",
"requires": {
"immediate": "^3.2.3"
}
},
"autoprefixer": {
"version": "10.4.13",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -17206,6 +17557,11 @@
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="
},
"bcp-47-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz",
"integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w=="
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -17502,6 +17858,11 @@
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz",
"integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw=="
},
"classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@@ -17624,6 +17985,11 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
"colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@@ -17744,6 +18110,11 @@
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@@ -18000,6 +18371,11 @@
"nth-check": "^2.0.1"
}
},
"css-selector-parser": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
"integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
},
"css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
@@ -18268,6 +18644,11 @@
"path-type": "^4.0.0"
}
},
"direction": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -18286,6 +18667,43 @@
"@leichtgewicht/ip-codec": "^2.0.1"
}
},
"docusaurus-lunr-search": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-2.3.2.tgz",
"integrity": "sha512-Ngvm2kXwliWThqAThXI1912rOKHlFL7BjIc+OVNUfzkjpk5ar4TFEh+EUaaMOLw4V0BBko3CW0Ym7prqqm3jLQ==",
"requires": {
"autocomplete.js": "^0.37.0",
"classnames": "^2.2.6",
"gauge": "^3.0.0",
"hast-util-select": "^4.0.0",
"hast-util-to-text": "^2.0.0",
"hogan.js": "^3.0.2",
"lunr": "^2.3.8",
"lunr-languages": "^1.4.0",
"minimatch": "^3.0.4",
"object-assign": "^4.1.1",
"rehype-parse": "^7.0.1",
"to-vfile": "^6.1.0",
"unified": "^9.0.0",
"unist-util-is": "^4.0.2"
},
"dependencies": {
"parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"rehype-parse": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-7.0.1.tgz",
"integrity": "sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw==",
"requires": {
"hast-util-from-parse5": "^6.0.0",
"parse5": "^6.0.0"
}
}
}
},
"docusaurus-plugin-openapi": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.3.tgz",
@@ -19036,6 +19454,39 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"requires": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"dependencies": {
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
}
}
},
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -19266,6 +19717,11 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"has-yarn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
@@ -19298,6 +19754,16 @@
"web-namespaces": "^1.0.0"
}
},
"hast-util-has-property": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz",
"integrity": "sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg=="
},
"hast-util-is-element": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz",
"integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ=="
},
"hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
@@ -19327,6 +19793,27 @@
}
}
},
"hast-util-select": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-4.0.2.tgz",
"integrity": "sha512-8EEG2//bN5rrzboPWD2HdS3ugLijNioS1pqOTIolXNf67xxShYw4SQEmVXd3imiBG+U2bC2nVTySr/iRAA7Cjg==",
"requires": {
"bcp-47-match": "^1.0.0",
"comma-separated-tokens": "^1.0.0",
"css-selector-parser": "^1.0.0",
"direction": "^1.0.0",
"hast-util-has-property": "^1.0.0",
"hast-util-is-element": "^1.0.0",
"hast-util-to-string": "^1.0.0",
"hast-util-whitespace": "^1.0.0",
"not": "^0.1.0",
"nth-check": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0",
"unist-util-visit": "^2.0.0",
"zwitch": "^1.0.0"
}
},
"hast-util-to-parse5": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz",
@@ -19339,6 +19826,26 @@
"zwitch": "^1.0.0"
}
},
"hast-util-to-string": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz",
"integrity": "sha512-eK0MxRX47AV2eZ+Lyr18DCpQgodvaS3fAQO2+b9Two9F5HEoRPhiUMNzoXArMJfZi2yieFzUBMRl3HNJ3Jus3w=="
},
"hast-util-to-text": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-2.0.1.tgz",
"integrity": "sha512-8nsgCARfs6VkwH2jJU9b8LNTuR4700na+0h3PqCaEk4MAnMDeu5P0tP8mjk9LLNGxIeQRLbiDbZVw6rku+pYsQ==",
"requires": {
"hast-util-is-element": "^1.0.0",
"repeat-string": "^1.0.0",
"unist-util-find-after": "^3.0.0"
}
},
"hast-util-whitespace": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz",
"integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A=="
},
"hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
@@ -19374,6 +19881,15 @@
"value-equal": "^1.0.1"
}
},
"hogan.js": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
"requires": {
"mkdirp": "0.3.0",
"nopt": "1.0.10"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -19589,6 +20105,11 @@
"queue": "6.0.2"
}
},
"immediate": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
},
"immer": {
"version": "9.0.16",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz",
@@ -20162,6 +20683,16 @@
"yallist": "^4.0.0"
}
},
"lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
},
"lunr-languages": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.10.0.tgz",
"integrity": "sha512-BBjKKcwrieJlzwwc9M5H/MRXGJ2qyOSDx/NXYiwkuKjiLOOoouh0WsDzeqcLoUWcX31y7i8sb8IgsZKObdUCkw=="
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -20374,6 +20905,11 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
"mkdirp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew=="
},
"monaco-editor": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.31.1.tgz",
@@ -20461,6 +20997,14 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg=="
},
"nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"requires": {
"abbrev": "1"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -20476,6 +21020,11 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
},
"not": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
"integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -23447,6 +23996,15 @@
"is-number": "^7.0.0"
}
},
"to-vfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-6.1.0.tgz",
"integrity": "sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==",
"requires": {
"is-buffer": "^2.0.0",
"vfile": "^4.0.0"
}
},
"toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -23588,6 +24146,14 @@
"resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz",
"integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw=="
},
"unist-util-find-after": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz",
"integrity": "sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ==",
"requires": {
"unist-util-is": "^4.0.0"
}
},
"unist-util-generated": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz",
@@ -24241,6 +24807,31 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"requires": {
"string-width": "^1.0.2 || 2 || 3 || 4"
},
"dependencies": {
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
}
}
},
"widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",

View File

@@ -20,6 +20,7 @@
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.20",
"prism-react-renderer": "^1.3.5",

View File

@@ -0,0 +1,297 @@
import Hogan from "hogan.js";
import LunrSearchAdapter from "./lunar-search";
import autocomplete from "autocomplete.js";
import templates from "./templates";
import utils from "./utils";
import $ from "autocomplete.js/zepto";
class DocSearch {
constructor({
searchDocs,
searchIndex,
inputSelector,
debug = false,
baseUrl = '/',
queryDataCallback = null,
autocompleteOptions = {
debug: false,
hint: false,
autoselect: true
},
transformData = false,
queryHook = false,
handleSelected = false,
enhancedSearchInput = false,
layout = "collumns"
}) {
this.input = DocSearch.getInputFromSelector(inputSelector);
this.queryDataCallback = queryDataCallback || null;
const autocompleteOptionsDebug =
autocompleteOptions && autocompleteOptions.debug
? autocompleteOptions.debug
: false;
// eslint-disable-next-line no-param-reassign
autocompleteOptions.debug = debug || autocompleteOptionsDebug;
this.autocompleteOptions = autocompleteOptions;
this.autocompleteOptions.cssClasses =
this.autocompleteOptions.cssClasses || {};
this.autocompleteOptions.cssClasses.prefix =
this.autocompleteOptions.cssClasses.prefix || "ds";
const inputAriaLabel =
this.input &&
typeof this.input.attr === "function" &&
this.input.attr("aria-label");
this.autocompleteOptions.ariaLabel =
this.autocompleteOptions.ariaLabel || inputAriaLabel || "search input";
this.isSimpleLayout = layout === "simple";
this.client = new LunrSearchAdapter(searchDocs, searchIndex, baseUrl);
if (enhancedSearchInput) {
this.input = DocSearch.injectSearchBox(this.input);
}
this.autocomplete = autocomplete(this.input, autocompleteOptions, [
{
source: this.getAutocompleteSource(transformData, queryHook),
templates: {
suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout),
footer: templates.footer,
empty: DocSearch.getEmptyTemplate()
}
}
]);
const customHandleSelected = handleSelected;
this.handleSelected = customHandleSelected || this.handleSelected;
// We prevent default link clicking if a custom handleSelected is defined
if (customHandleSelected) {
$(".algolia-autocomplete").on("click", ".ds-suggestions a", event => {
event.preventDefault();
});
}
this.autocomplete.on(
"autocomplete:selected",
this.handleSelected.bind(null, this.autocomplete.autocomplete)
);
this.autocomplete.on(
"autocomplete:shown",
this.handleShown.bind(null, this.input)
);
if (enhancedSearchInput) {
DocSearch.bindSearchBoxEvent();
}
}
static injectSearchBox(input) {
input.before(templates.searchBox);
const newInput = input
.prev()
.prev()
.find("input");
input.remove();
return newInput;
}
static bindSearchBoxEvent() {
$('.searchbox [type="reset"]').on("click", function () {
$("input#docsearch").focus();
$(this).addClass("hide");
autocomplete.autocomplete.setVal("");
});
$("input#docsearch").on("keyup", () => {
const searchbox = document.querySelector("input#docsearch");
const reset = document.querySelector('.searchbox [type="reset"]');
reset.className = "searchbox__reset";
if (searchbox.value.length === 0) {
reset.className += " hide";
}
});
}
/**
* Returns the matching input from a CSS selector, null if none matches
* @function getInputFromSelector
* @param {string} selector CSS selector that matches the search
* input of the page
* @returns {void}
*/
static getInputFromSelector(selector) {
const input = $(selector).filter("input");
return input.length ? $(input[0]) : null;
}
/**
* Returns the `source` method to be passed to autocomplete.js. It will query
* the Algolia index and call the callbacks with the formatted hits.
* @function getAutocompleteSource
* @param {function} transformData An optional function to transform the hits
* @param {function} queryHook An optional function to transform the query
* @returns {function} Method to be passed as the `source` option of
* autocomplete
*/
getAutocompleteSource(transformData, queryHook) {
return (query, callback) => {
if (queryHook) {
// eslint-disable-next-line no-param-reassign
query = queryHook(query) || query;
}
this.client.search(query).then(hits => {
if (
this.queryDataCallback &&
typeof this.queryDataCallback == "function"
) {
this.queryDataCallback(hits);
}
if (transformData) {
hits = transformData(hits) || hits;
}
callback(DocSearch.formatHits(hits));
});
};
}
// Given a list of hits returned by the API, will reformat them to be used in
// a Hogan template
static formatHits(receivedHits) {
const clonedHits = utils.deepClone(receivedHits);
const hits = clonedHits.map(hit => {
if (hit._highlightResult) {
// eslint-disable-next-line no-param-reassign
hit._highlightResult = utils.mergeKeyWithParent(
hit._highlightResult,
"hierarchy"
);
}
return utils.mergeKeyWithParent(hit, "hierarchy");
});
// Group hits by category / subcategory
let groupedHits = utils.groupBy(hits, "lvl0");
$.each(groupedHits, (level, collection) => {
const groupedHitsByLvl1 = utils.groupBy(collection, "lvl1");
const flattenedHits = utils.flattenAndFlagFirst(
groupedHitsByLvl1,
"isSubCategoryHeader"
);
groupedHits[level] = flattenedHits;
});
groupedHits = utils.flattenAndFlagFirst(groupedHits, "isCategoryHeader");
// Translate hits into smaller objects to be send to the template
return groupedHits.map(hit => {
const url = DocSearch.formatURL(hit);
const category = utils.getHighlightedValue(hit, "lvl0");
const subcategory = utils.getHighlightedValue(hit, "lvl1") || category;
const displayTitle = utils
.compact([
utils.getHighlightedValue(hit, "lvl2") || subcategory,
utils.getHighlightedValue(hit, "lvl3"),
utils.getHighlightedValue(hit, "lvl4"),
utils.getHighlightedValue(hit, "lvl5"),
utils.getHighlightedValue(hit, "lvl6")
])
.join(
'<span class="aa-suggestion-title-separator" aria-hidden="true"> </span>'
);
const text = utils.getSnippetedValue(hit, "content");
const isTextOrSubcategoryNonEmpty =
(subcategory && subcategory !== "") ||
(displayTitle && displayTitle !== "");
const isLvl1EmptyOrDuplicate =
!subcategory || subcategory === "" || subcategory === category;
const isLvl2 =
displayTitle && displayTitle !== "" && displayTitle !== subcategory;
const isLvl1 =
!isLvl2 &&
(subcategory && subcategory !== "" && subcategory !== category);
const isLvl0 = !isLvl1 && !isLvl2;
return {
isLvl0,
isLvl1,
isLvl2,
isLvl1EmptyOrDuplicate,
isCategoryHeader: hit.isCategoryHeader,
isSubCategoryHeader: hit.isSubCategoryHeader,
isTextOrSubcategoryNonEmpty,
category,
subcategory,
title: displayTitle,
text,
url
};
});
}
static formatURL(hit) {
const { url, anchor } = hit;
if (url) {
const containsAnchor = url.indexOf("#") !== -1;
if (containsAnchor) return url;
else if (anchor) return `${hit.url}#${hit.anchor}`;
return url;
} else if (anchor) return `#${hit.anchor}`;
/* eslint-disable */
console.warn("no anchor nor url for : ", JSON.stringify(hit));
/* eslint-enable */
return null;
}
static getEmptyTemplate() {
return args => Hogan.compile(templates.empty).render(args);
}
static getSuggestionTemplate(isSimpleLayout) {
const stringTemplate = isSimpleLayout
? templates.suggestionSimple
: templates.suggestion;
const template = Hogan.compile(stringTemplate);
return suggestion => template.render(suggestion);
}
handleSelected(input, event, suggestion, datasetNumber, context = {}) {
// Do nothing if click on the suggestion, as it's already a <a href>, the
// browser will take care of it. This allow Ctrl-Clicking on results and not
// having the main window being redirected as well
if (context.selectionMethod === "click") {
return;
}
input.setVal("");
window.location.assign(suggestion.url);
}
handleShown(input) {
const middleOfInput = input.offset().left + input.width() / 2;
let middleOfWindow = $(document).width() / 2;
if (isNaN(middleOfWindow)) {
middleOfWindow = 900;
}
const alignClass =
middleOfInput - middleOfWindow >= 0
? "algolia-autocomplete-right"
: "algolia-autocomplete-left";
const otherAlignClass =
middleOfInput - middleOfWindow < 0
? "algolia-autocomplete-right"
: "algolia-autocomplete-left";
const autocompleteWrapper = $(".algolia-autocomplete");
if (!autocompleteWrapper.hasClass(alignClass)) {
autocompleteWrapper.addClass(alignClass);
}
if (autocompleteWrapper.hasClass(otherAlignClass)) {
autocompleteWrapper.removeClass(otherAlignClass);
}
}
}
export default DocSearch;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,114 @@
import React, { useRef, useCallback, useState } from "react";
import classnames from "classnames";
import { useHistory } from "@docusaurus/router";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { usePluginData } from '@docusaurus/useGlobalData';
import useIsBrowser from "@docusaurus/useIsBrowser";
const Search = props => {
const initialized = useRef(false);
const searchBarRef = useRef(null);
const [indexReady, setIndexReady] = useState(false);
const history = useHistory();
const { siteConfig = {} } = useDocusaurusContext();
const isBrowser = useIsBrowser();
const { baseUrl } = siteConfig;
const initAlgolia = (searchDocs, searchIndex, DocSearch) => {
new DocSearch({
searchDocs,
searchIndex,
baseUrl,
inputSelector: "#search_input_react",
// Override algolia's default selection event, allowing us to do client-side
// navigation and avoiding a full page refresh.
handleSelected: (_input, _event, suggestion) => {
const url = suggestion.url || "/";
// Use an anchor tag to parse the absolute url into a relative url
// Alternatively, we can use new URL(suggestion.url) but its not supported in IE
const a = document.createElement("a");
a.href = url;
// Algolia use closest parent element id #__docusaurus when a h1 page title does not have an id
// So, we can safely remove it. See https://github.com/facebook/docusaurus/issues/1828 for more details.
history.push(url);
}
});
};
const pluginData = usePluginData('docusaurus-lunr-search');
const getSearchDoc = () =>
process.env.NODE_ENV === "production"
? fetch(`${baseUrl}${pluginData.fileNames.searchDoc}`).then((content) => content.json())
: Promise.resolve([]);
const getLunrIndex = () =>
process.env.NODE_ENV === "production"
? fetch(`${baseUrl}${pluginData.fileNames.lunrIndex}`).then((content) => content.json())
: Promise.resolve([]);
const loadAlgolia = () => {
if (!initialized.current) {
Promise.all([
getSearchDoc(),
getLunrIndex(),
import("./DocSearch"),
import("./algolia.css")
]).then(([searchDocs, searchIndex, { default: DocSearch }]) => {
if (searchDocs.length === 0) {
return;
}
initAlgolia(searchDocs, searchIndex, DocSearch);
setIndexReady(true);
});
initialized.current = true;
}
};
const toggleSearchIconClick = useCallback(
e => {
if (!searchBarRef.current.contains(e.target)) {
searchBarRef.current.focus();
}
props.handleSearchBarToggle && props.handleSearchBarToggle(!props.isSearchBarExpanded);
},
[props.isSearchBarExpanded]
);
if (isBrowser) {
loadAlgolia();
}
return (
<div className="navbar__search" key="search-box">
<span
aria-label="expand searchbar"
role="button"
className={classnames("search-icon", {
"search-icon-hidden": props.isSearchBarExpanded
})}
onClick={toggleSearchIconClick}
onKeyDown={toggleSearchIconClick}
tabIndex={0}
/>
<input
id="search_input_react"
type="search"
placeholder={indexReady ? 'Search' : 'Loading...'}
aria-label="Search"
className={classnames(
"navbar__search-input",
{ "search-bar-expanded": props.isSearchBarExpanded },
{ "search-bar": !props.isSearchBarExpanded }
)}
onClick={loadAlgolia}
onMouseOver={loadAlgolia}
onFocus={toggleSearchIconClick}
onBlur={toggleSearchIconClick}
ref={searchBarRef}
disabled={!indexReady}
/>
</div>
);
};
export default Search;

View File

@@ -0,0 +1,147 @@
import lunr from "@generated/lunr.client";
lunr.tokenizer.separator = /[\s\-/]+/;
class LunrSearchAdapter {
constructor(searchDocs, searchIndex, baseUrl = '/') {
this.searchDocs = searchDocs;
this.lunrIndex = lunr.Index.load(searchIndex);
this.baseUrl = baseUrl;
}
getLunrResult(input) {
return this.lunrIndex.query(function (query) {
const tokens = lunr.tokenizer(input);
query.term(tokens, {
boost: 10
});
query.term(tokens, {
wildcard: lunr.Query.wildcard.TRAILING
});
});
}
getHit(doc, formattedTitle, formattedContent) {
return {
hierarchy: {
lvl0: doc.pageTitle || doc.title,
lvl1: doc.type === 0 ? null : doc.title
},
url: doc.url,
_snippetResult: formattedContent ? {
content: {
value: formattedContent,
matchLevel: "full"
}
} : null,
_highlightResult: {
hierarchy: {
lvl0: {
value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle,
},
lvl1:
doc.type === 0
? null
: {
value: formattedTitle || doc.title
}
}
}
};
}
getTitleHit(doc, position, length) {
const start = position[0];
const end = position[0] + length;
let formattedTitle = doc.title.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.title.substring(start, end) + '</span>' + doc.title.substring(end, doc.title.length);
return this.getHit(doc, formattedTitle)
}
getKeywordHit(doc, position, length) {
const start = position[0];
const end = position[0] + length;
let formattedTitle = doc.title + '<br /><i>Keywords: ' + doc.keywords.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.keywords.substring(start, end) + '</span>' + doc.keywords.substring(end, doc.keywords.length) + '</i>'
return this.getHit(doc, formattedTitle)
}
getContentHit(doc, position) {
const start = position[0];
const end = position[0] + position[1];
let previewStart = start;
let previewEnd = end;
let ellipsesBefore = true;
let ellipsesAfter = true;
for (let k = 0; k < 3; k++) {
const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
const nextDot = doc.content.lastIndexOf('.', previewStart - 2);
if ((nextDot > 0) && (nextDot > nextSpace)) {
previewStart = nextDot + 1;
ellipsesBefore = false;
break;
}
if (nextSpace < 0) {
previewStart = 0;
ellipsesBefore = false;
break;
}
previewStart = nextSpace + 1;
}
for (let k = 0; k < 10; k++) {
const nextSpace = doc.content.indexOf(' ', previewEnd + 1);
const nextDot = doc.content.indexOf('.', previewEnd + 1);
if ((nextDot > 0) && (nextDot < nextSpace)) {
previewEnd = nextDot;
ellipsesAfter = false;
break;
}
if (nextSpace < 0) {
previewEnd = doc.content.length;
ellipsesAfter = false;
break;
}
previewEnd = nextSpace;
}
let preview = doc.content.substring(previewStart, start);
if (ellipsesBefore) {
preview = '... ' + preview;
}
preview += '<span class="algolia-docsearch-suggestion--highlight">' + doc.content.substring(start, end) + '</span>';
preview += doc.content.substring(end, previewEnd);
if (ellipsesAfter) {
preview += ' ...';
}
return this.getHit(doc, null, preview);
}
search(input) {
return new Promise((resolve, rej) => {
const results = this.getLunrResult(input);
const hits = [];
results.length > 5 && (results.length = 5);
this.titleHitsRes = []
this.contentHitsRes = []
results.forEach(result => {
const doc = this.searchDocs[result.ref];
const { metadata } = result.matchData;
for (let i in metadata) {
if (metadata[i].title) {
if (!this.titleHitsRes.includes(result.ref)) {
const position = metadata[i].title.position[0]
hits.push(this.getTitleHit(doc, position, input.length));
this.titleHitsRes.push(result.ref);
}
} else if (metadata[i].content) {
const position = metadata[i].content.position[0]
hits.push(this.getContentHit(doc, position))
} else if (metadata[i].keywords) {
const position = metadata[i].keywords.position[0]
hits.push(this.getKeywordHit(doc, position, input.length));
this.titleHitsRes.push(result.ref);
}
}
});
hits.length > 5 && (hits.length = 5);
resolve(hits);
});
}
}
export default LunrSearchAdapter;

View File

@@ -0,0 +1,33 @@
.search-icon {
background-image: var(--ifm-navbar-search-input-icon);
height: auto;
width: 24px;
cursor: pointer;
padding: 8px;
line-height: 32px;
background-repeat: no-repeat;
background-position: center;
display: none;
}
.search-icon-hidden {
visibility: hidden;
}
@media (max-width: 360px) {
.search-bar {
width: 0 !important;
background: none !important;
padding: 0 !important;
transition: none !important;
}
.search-bar-expanded {
width: 9rem !important;
}
.search-icon {
display: inline;
vertical-align: sub;
}
}

View File

@@ -0,0 +1,112 @@
const prefix = 'algolia-docsearch';
const suggestionPrefix = `${prefix}-suggestion`;
const footerPrefix = `${prefix}-footer`;
const templates = {
suggestion: `
<a class="${suggestionPrefix}
{{#isCategoryHeader}}${suggestionPrefix}__main{{/isCategoryHeader}}
{{#isSubCategoryHeader}}${suggestionPrefix}__secondary{{/isSubCategoryHeader}}
"
aria-label="Link to the result"
href="{{{url}}}"
>
<div class="${suggestionPrefix}--category-header">
<span class="${suggestionPrefix}--category-header-lvl0">{{{category}}}</span>
</div>
<div class="${suggestionPrefix}--wrapper">
<div class="${suggestionPrefix}--subcategory-column">
<span class="${suggestionPrefix}--subcategory-column-text">{{{subcategory}}}</span>
</div>
{{#isTextOrSubcategoryNonEmpty}}
<div class="${suggestionPrefix}--content">
<div class="${suggestionPrefix}--subcategory-inline">{{{subcategory}}}</div>
<div class="${suggestionPrefix}--title">{{{title}}}</div>
{{#text}}<div class="${suggestionPrefix}--text">{{{text}}}</div>{{/text}}
</div>
{{/isTextOrSubcategoryNonEmpty}}
</div>
</a>
`,
suggestionSimple: `
<div class="${suggestionPrefix}
{{#isCategoryHeader}}${suggestionPrefix}__main{{/isCategoryHeader}}
{{#isSubCategoryHeader}}${suggestionPrefix}__secondary{{/isSubCategoryHeader}}
suggestion-layout-simple
">
<div class="${suggestionPrefix}--category-header">
{{^isLvl0}}
<span class="${suggestionPrefix}--category-header-lvl0 ${suggestionPrefix}--category-header-item">{{{category}}}</span>
{{^isLvl1}}
{{^isLvl1EmptyOrDuplicate}}
<span class="${suggestionPrefix}--category-header-lvl1 ${suggestionPrefix}--category-header-item">
{{{subcategory}}}
</span>
{{/isLvl1EmptyOrDuplicate}}
{{/isLvl1}}
{{/isLvl0}}
<div class="${suggestionPrefix}--title ${suggestionPrefix}--category-header-item">
{{#isLvl2}}
{{{title}}}
{{/isLvl2}}
{{#isLvl1}}
{{{subcategory}}}
{{/isLvl1}}
{{#isLvl0}}
{{{category}}}
{{/isLvl0}}
</div>
</div>
<div class="${suggestionPrefix}--wrapper">
{{#text}}
<div class="${suggestionPrefix}--content">
<div class="${suggestionPrefix}--text">{{{text}}}</div>
</div>
{{/text}}
</div>
</div>
`,
footer: `
<div class="${footerPrefix}">
</div>
`,
empty: `
<div class="${suggestionPrefix}">
<div class="${suggestionPrefix}--wrapper">
<div class="${suggestionPrefix}--content ${suggestionPrefix}--no-results">
<div class="${suggestionPrefix}--title">
<div class="${suggestionPrefix}--text">
No results found for query <b>"{{query}}"</b>
</div>
</div>
</div>
</div>
</div>
`,
searchBox: `
<form novalidate="novalidate" onsubmit="return false;" class="searchbox">
<div role="search" class="searchbox__wrapper">
<input id="docsearch" type="search" name="search" placeholder="Search the docs" autocomplete="off" required="required" class="searchbox__input"/>
<button type="submit" title="Submit your search query." class="searchbox__submit" >
<svg width=12 height=12 role="img" aria-label="Search">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sbx-icon-search-13"></use>
</svg>
</button>
<button type="reset" title="Clear the search query." class="searchbox__reset hide">
<svg width=12 height=12 role="img" aria-label="Reset">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sbx-icon-clear-3"></use>
</svg>
</button>
</div>
</form>
<div class="svg-icons" style="height: 0; width: 0; position: absolute; visibility: hidden">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="sbx-icon-clear-3" viewBox="0 0 40 40"><path d="M16.228 20L1.886 5.657 0 3.772 3.772 0l1.885 1.886L20 16.228 34.343 1.886 36.228 0 40 3.772l-1.886 1.885L23.772 20l14.342 14.343L40 36.228 36.228 40l-1.885-1.886L20 23.772 5.657 38.114 3.772 40 0 36.228l1.886-1.885L16.228 20z" fill-rule="evenodd"></symbol>
<symbol id="sbx-icon-search-13" viewBox="0 0 40 40"><path d="M26.806 29.012a16.312 16.312 0 0 1-10.427 3.746C7.332 32.758 0 25.425 0 16.378 0 7.334 7.333 0 16.38 0c9.045 0 16.378 7.333 16.378 16.38 0 3.96-1.406 7.593-3.746 10.426L39.547 37.34c.607.608.61 1.59-.004 2.203a1.56 1.56 0 0 1-2.202.004L26.807 29.012zm-10.427.627c7.322 0 13.26-5.938 13.26-13.26 0-7.324-5.938-13.26-13.26-13.26-7.324 0-13.26 5.936-13.26 13.26 0 7.322 5.936 13.26 13.26 13.26z" fill-rule="evenodd"></symbol>
</svg>
</div>
`,
};
export default templates;

View File

@@ -0,0 +1,270 @@
import $ from "autocomplete.js/zepto";
const utils = {
/*
* Move the content of an object key one level higher.
* eg.
* {
* name: 'My name',
* hierarchy: {
* lvl0: 'Foo',
* lvl1: 'Bar'
* }
* }
* Will be converted to
* {
* name: 'My name',
* lvl0: 'Foo',
* lvl1: 'Bar'
* }
* @param {Object} object Main object
* @param {String} property Main object key to move up
* @return {Object}
* @throws Error when key is not an attribute of Object or is not an object itself
*/
mergeKeyWithParent(object, property) {
if (object[property] === undefined) {
return object;
}
if (typeof object[property] !== 'object') {
return object;
}
const newObject = $.extend({}, object, object[property]);
delete newObject[property];
return newObject;
},
/*
* Group all objects of a collection by the value of the specified attribute
* If the attribute is a string, use the lowercase form.
*
* eg.
* groupBy([
* {name: 'Tim', category: 'dev'},
* {name: 'Vincent', category: 'dev'},
* {name: 'Ben', category: 'sales'},
* {name: 'Jeremy', category: 'sales'},
* {name: 'AlexS', category: 'dev'},
* {name: 'AlexK', category: 'sales'}
* ], 'category');
* =>
* {
* 'devs': [
* {name: 'Tim', category: 'dev'},
* {name: 'Vincent', category: 'dev'},
* {name: 'AlexS', category: 'dev'}
* ],
* 'sales': [
* {name: 'Ben', category: 'sales'},
* {name: 'Jeremy', category: 'sales'},
* {name: 'AlexK', category: 'sales'}
* ]
* }
* @param {array} collection Array of objects to group
* @param {String} property The attribute on which apply the grouping
* @return {array}
* @throws Error when one of the element does not have the specified property
*/
groupBy(collection, property) {
const newCollection = {};
$.each(collection, (index, item) => {
if (item[property] === undefined) {
throw new Error(`[groupBy]: Object has no key ${property}`);
}
let key = item[property];
if (typeof key === 'string') {
key = key.toLowerCase();
}
// fix #171 the given data type of docsearch hits might be conflict with the properties of the native Object,
// such as the constructor, so we need to do this check.
if (!Object.prototype.hasOwnProperty.call(newCollection, key)) {
newCollection[key] = [];
}
newCollection[key].push(item);
});
return newCollection;
},
/*
* Return an array of all the values of the specified object
* eg.
* values({
* foo: 42,
* bar: true,
* baz: 'yep'
* })
* =>
* [42, true, yep]
* @param {object} object Object to extract values from
* @return {array}
*/
values(object) {
return Object.keys(object).map(key => object[key]);
},
/*
* Flattens an array
* eg.
* flatten([1, 2, [3, 4], [5, 6]])
* =>
* [1, 2, 3, 4, 5, 6]
* @param {array} array Array to flatten
* @return {array}
*/
flatten(array) {
const results = [];
array.forEach(value => {
if (!Array.isArray(value)) {
results.push(value);
return;
}
value.forEach(subvalue => {
results.push(subvalue);
});
});
return results;
},
/*
* Flatten all values of an object into an array, marking each first element of
* each group with a specific flag
* eg.
* flattenAndFlagFirst({
* 'devs': [
* {name: 'Tim', category: 'dev'},
* {name: 'Vincent', category: 'dev'},
* {name: 'AlexS', category: 'dev'}
* ],
* 'sales': [
* {name: 'Ben', category: 'sales'},
* {name: 'Jeremy', category: 'sales'},
* {name: 'AlexK', category: 'sales'}
* ]
* , 'isTop');
* =>
* [
* {name: 'Tim', category: 'dev', isTop: true},
* {name: 'Vincent', category: 'dev', isTop: false},
* {name: 'AlexS', category: 'dev', isTop: false},
* {name: 'Ben', category: 'sales', isTop: true},
* {name: 'Jeremy', category: 'sales', isTop: false},
* {name: 'AlexK', category: 'sales', isTop: false}
* ]
* @param {object} object Object to flatten
* @param {string} flag Flag to set to true on first element of each group
* @return {array}
*/
flattenAndFlagFirst(object, flag) {
const values = this.values(object).map(collection =>
collection.map((item, index) => {
// eslint-disable-next-line no-param-reassign
item[flag] = index === 0;
return item;
})
);
return this.flatten(values);
},
/*
* Removes all empty strings, null, false and undefined elements array
* eg.
* compact([42, false, null, undefined, '', [], 'foo']);
* =>
* [42, [], 'foo']
* @param {array} array Array to compact
* @return {array}
*/
compact(array) {
const results = [];
array.forEach(value => {
if (!value) {
return;
}
results.push(value);
});
return results;
},
/*
* Returns the highlighted value of the specified key in the specified object.
* If no highlighted value is available, will return the key value directly
* eg.
* getHighlightedValue({
* _highlightResult: {
* text: {
* value: '<mark>foo</mark>'
* }
* },
* text: 'foo'
* }, 'text');
* =>
* '<mark>foo</mark>'
* @param {object} object Hit object returned by the Algolia API
* @param {string} property Object key to look for
* @return {string}
**/
getHighlightedValue(object, property) {
if (
object._highlightResult &&
object._highlightResult.hierarchy_camel &&
object._highlightResult.hierarchy_camel[property] &&
object._highlightResult.hierarchy_camel[property].matchLevel &&
object._highlightResult.hierarchy_camel[property].matchLevel !== 'none' &&
object._highlightResult.hierarchy_camel[property].value
) {
return object._highlightResult.hierarchy_camel[property].value;
}
if (
object._highlightResult &&
object._highlightResult &&
object._highlightResult[property] &&
object._highlightResult[property].value
) {
return object._highlightResult[property].value;
}
return object[property];
},
/*
* Returns the snippeted value of the specified key in the specified object.
* If no highlighted value is available, will return the key value directly.
* Will add starting and ending ellipsis (…) if we detect that a sentence is
* incomplete
* eg.
* getSnippetedValue({
* _snippetResult: {
* text: {
* value: '<mark>This is an unfinished sentence</mark>'
* }
* },
* text: 'This is an unfinished sentence'
* }, 'text');
* =>
* '<mark>This is an unfinished sentence</mark>…'
* @param {object} object Hit object returned by the Algolia API
* @param {string} property Object key to look for
* @return {string}
**/
getSnippetedValue(object, property) {
if (
!object._snippetResult ||
!object._snippetResult[property] ||
!object._snippetResult[property].value
) {
return object[property];
}
let snippet = object._snippetResult[property].value;
if (snippet[0] !== snippet[0].toUpperCase()) {
snippet = `${snippet}`;
}
if (['.', '!', '?'].indexOf(snippet[snippet.length - 1]) === -1) {
snippet = `${snippet}`;
}
return snippet;
},
/*
* Deep clone an object.
* Note: This will not clone functions and dates
* @param {object} object Object to clone
* @return {object}
*/
deepClone(object) {
return JSON.parse(JSON.stringify(object));
},
};
export default utils;

View File

@@ -2,19 +2,10 @@ echo "Starting Immich installation..."
ip_address=$(hostname -I | awk '{print $1}')
release_version=$(curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" |
grep '"tag_name":' |
sed -E 's/.*"([^"]+)".*/\1/')
RED='\033[0;31m'
GREEN='\032[0;31m'
NC='\033[0m' # No Color
get_release_version() {
curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" | # Get latest release from GitHub api
grep '"tag_name":' | # Get tag line
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
}
create_immich_directory() {
echo "Creating Immich directory..."
mkdir -p ./immich-app/immich-data
@@ -23,12 +14,12 @@ create_immich_directory() {
download_docker_compose_file() {
echo "Downloading docker-compose.yml..."
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/docker-compose.yml -o ./docker-compose.yml >/dev/null 2>&1
curl -L https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -o ./docker-compose.yml >/dev/null 2>&1
}
download_dot_env_file() {
echo "Downloading .env file..."
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/example.env -o ./.env >/dev/null 2>&1
curl -L https://github.com/immich-app/immich/releases/latest/download/example.env -o ./.env >/dev/null 2>&1
}
replace_env_value() {
@@ -69,7 +60,7 @@ start_docker_compose() {
show_friendly_message() {
echo "Succesfully deployed Immich!"
echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api"
echo "The backup (or upload) location is $upload_location"
echo "The library location is $upload_location"
echo "---------------------------------------------------"
echo "If you want to configure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.

View File

@@ -11,6 +11,8 @@ RUN /opt/venv/bin/pip install --no-deps sentence-transformers
FROM python:3.10-slim
ENV NODE_ENV=production
COPY --from=builder /opt/venv /opt/venv
ENV TRANSFORMERS_CACHE=/cache \

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 75,
"android.injected.version.name" => "1.52.0",
"android.injected.version.code" => 76,
"android.injected.version.name" => "1.53.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -0,0 +1,8 @@
* refactor: migrate all Hive boxes to Isar database.
* feat: Use new search API and GridView for Places / Locations.
* refactor: store backup settings on device.
* feat: Explore favorites, recently added, videos, and motion photos.
* fix: Fixed mobile app not reporting webm MIME type.
* feature: Hardening synchronization mechanism + Pull to refresh.
* feat: improve explore page and allow metadata search.
* feat: Allow headers to upload large file in chunk.

View File

@@ -0,0 +1,3 @@
* Improved logging page experience
* Fixed shared page does not get all shared albums
* Fixed hero animation re-enabling on immich asset grid

View File

@@ -5,19 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000232">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000285">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="70.685298">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="168.230955">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="33.624781">
<failure message="/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:229:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:229:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing&apos;&#10;Fastfile:42:in `block (2 levels) in parsing_binding&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/lane.rb:33:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:49:in `block in execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:45:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:45:in `execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:354:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:43:in `start&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/bin/fastlane:23:in `&lt;top (required)&gt;&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/bin/fastlane:25:in `load&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/bin/fastlane:25:in `&lt;main&gt;&apos;&#10;&#10;Google Api Error: Invalid request - The release created has notes in language en-US with length 508, which is too long (max: 500)." />
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="43.975952">
</testcase>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.52.0"
version_number: "1.53.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -38,19 +38,27 @@ class ImmichAssetGrid extends HookConsumerWidget {
// Needs to suppress hero animations when navigating to this widget
final enableHeroAnimations = useState(false);
final transitionDuration = ModalRoute.of(context)?.transitionDuration;
// Wait for transition to complete, then re-enable
ModalRoute.of(context)?.animation?.addListener(() {
// If we've already enabled, we are done
if (enableHeroAnimations.value) {
return;
}
final animation = ModalRoute.of(context)?.animation;
if (animation != null) {
// When the animation is complete, re-enable hero animations
enableHeroAnimations.value = animation.isCompleted;
}
});
useEffect(
() {
// Wait for transition to complete, then re-enable
if (transitionDuration == null) {
// No route transition found, maybe we opened this up first
enableHeroAnimations.value = true;
} else {
// Unfortunately, using the transition animation itself didn't
// seem to work reliably. So instead, wait until the duration of the
// animation has elapsed to re-enable the hero animations
Future.delayed(transitionDuration)
.then((_) {
enableHeroAnimations.value = true;
});
}
return null;
},
[],
);
Future<bool> onWillPop() async {
enableHeroAnimations.value = false;

View File

@@ -27,6 +27,7 @@ import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:immich_mobile/shared/services/share.service.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/ui/share_dialog.dart';
class HomePage extends HookConsumerWidget {
const HomePage({Key? key}) : super(key: key);
@@ -81,7 +82,19 @@ class HomePage extends HookConsumerWidget {
}
void onShareAssets() {
ref.watch(shareServiceProvider).shareAssets(selection.value.toList());
showDialog(
context: context,
builder: (BuildContext buildContext) {
ref
.watch(shareServiceProvider)
.shareAssets(selection.value.toList())
.then((_) => Navigator.of(buildContext).pop());
return const ShareDialog();
},
barrierDismissible: false,
);
// ref.watch(shareServiceProvider).shareAssets(selection.value.toList());
selectionEnabledHook.value = false;
}
@@ -244,6 +257,7 @@ class HomePage extends HookConsumerWidget {
return SafeArea(
top: true,
bottom: false,
child: Stack(
children: [
ref.watch(assetProvider).renderList == null ||
@@ -261,17 +275,14 @@ class HomePage extends HookConsumerWidget {
onRefresh: refreshAssets,
),
if (selectionEnabledHook.value)
SafeArea(
bottom: true,
child: ControlBottomAppBar(
onShare: onShareAssets,
onFavorite: onFavoriteAssets,
onDelete: onDelete,
onAddToAlbum: onAddToAlbum,
albums: albums,
sharedAlbums: sharedAlbums,
onCreateNewAlbum: onCreateNewAlbum,
),
ControlBottomAppBar(
onShare: onShareAssets,
onFavorite: onFavoriteAssets,
onDelete: onDelete,
onAddToAlbum: onAddToAlbum,
albums: albums,
sharedAlbums: sharedAlbums,
onCreateNewAlbum: onCreateNewAlbum,
),
],
),
@@ -279,9 +290,11 @@ class HomePage extends HookConsumerWidget {
}
return Scaffold(
appBar: HomePageAppBar(
onPopBack: reloadAllAsset,
),
appBar: !selectionEnabledHook.value
? HomePageAppBar(
onPopBack: reloadAllAsset,
)
: null,
drawer: const ProfileDrawer(),
body: buildBody(),
);

View File

@@ -34,8 +34,10 @@ import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/gallery_permission_guard.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/views/app_log_detail_page.dart';
import 'package:immich_mobile/shared/views/app_log_page.dart';
import 'package:immich_mobile/shared/views/splash_screen.dart';
import 'package:immich_mobile/shared/views/tab_controller_page.dart';
@@ -47,8 +49,12 @@ part 'router.gr.dart';
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(page: SplashScreenPage, initial: true),
AutoRoute(page: PermissionOnboardingPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: LoginPage,
AutoRoute(
page: PermissionOnboardingPage,
guards: [AuthGuard, DuplicateGuard],
),
AutoRoute(
page: LoginPage,
guards: [
DuplicateGuard,
],
@@ -65,7 +71,10 @@ part 'router.gr.dart';
],
transitionsBuilder: TransitionsBuilders.fadeIn,
),
AutoRoute(page: GalleryViewerPage, guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard]),
AutoRoute(
page: GalleryViewerPage,
guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard],
),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]),
@@ -75,7 +84,10 @@ part 'router.gr.dart';
AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AllVideosPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AllMotionPhotosPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: RecentlyAddedPage, guards: [AuthGuard, DuplicateGuard],),
AutoRoute(
page: RecentlyAddedPage,
guards: [AuthGuard, DuplicateGuard],
),
CustomRoute<AssetSelectionPageResult?>(
page: AssetSelectionPage,
guards: [AuthGuard, DuplicateGuard],
@@ -92,14 +104,18 @@ part 'router.gr.dart';
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(
page: BackupAlbumSelectionPage,
guards: [AuthGuard, DuplicateGuard],
),
AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard, DuplicateGuard]),
CustomRoute(
page: FailedBackupStatusPage,
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(page: SettingsPage,
AutoRoute(
page: SettingsPage,
guards: [
AuthGuard,
DuplicateGuard,
@@ -109,6 +125,9 @@ part 'router.gr.dart';
page: AppLogPage,
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(
page: AppLogDetailPage,
),
],
)
class AppRouter extends _$AppRouter {
@@ -116,14 +135,19 @@ class AppRouter extends _$AppRouter {
final ApiService _apiService;
AppRouter(
this._apiService,
this._apiService,
GalleryPermissionNotifier galleryPermissionNotifier,
) : super(
authGuard: AuthGuard(_apiService),
) : super(
authGuard: AuthGuard(_apiService),
duplicateGuard: DuplicateGuard(),
galleryPermissionGuard: GalleryPermissionGuard(galleryPermissionNotifier),
galleryPermissionGuard:
GalleryPermissionGuard(galleryPermissionNotifier),
);
}
final appRouterProvider =
Provider((ref) => AppRouter(ref.watch(apiServiceProvider), ref.watch(galleryPermissionNotifier.notifier)));
final appRouterProvider = Provider(
(ref) => AppRouter(
ref.watch(apiServiceProvider),
ref.watch(galleryPermissionNotifier.notifier),
),
);

View File

@@ -230,6 +230,16 @@ class _$AppRouter extends RootStackRouter {
barrierDismissible: false,
);
},
AppLogDetailRoute.name: (routeData) {
final args = routeData.argsAs<AppLogDetailRouteArgs>();
return MaterialPageX<dynamic>(
routeData: routeData,
child: AppLogDetailPage(
key: args.key,
logMessage: args.logMessage,
),
);
},
HomeRoute.name: (routeData) {
return MaterialPageX<dynamic>(
routeData: routeData,
@@ -485,6 +495,10 @@ class _$AppRouter extends RootStackRouter {
AppLogRoute.name,
path: '/app-log-page',
),
RouteConfig(
AppLogDetailRoute.name,
path: '/app-log-detail-page',
),
];
}
@@ -974,6 +988,40 @@ class AppLogRoute extends PageRouteInfo<void> {
static const String name = 'AppLogRoute';
}
/// generated route for
/// [AppLogDetailPage]
class AppLogDetailRoute extends PageRouteInfo<AppLogDetailRouteArgs> {
AppLogDetailRoute({
Key? key,
required LoggerMessage logMessage,
}) : super(
AppLogDetailRoute.name,
path: '/app-log-detail-page',
args: AppLogDetailRouteArgs(
key: key,
logMessage: logMessage,
),
);
static const String name = 'AppLogDetailRoute';
}
class AppLogDetailRouteArgs {
const AppLogDetailRouteArgs({
this.key,
required this.logMessage,
});
final Key? key;
final LoggerMessage logMessage;
@override
String toString() {
return 'AppLogDetailRouteArgs{key: $key, logMessage: $logMessage}';
}
}
/// generated route for
/// [HomePage]
class HomeRoute extends PageRouteInfo<void> {

View File

@@ -102,15 +102,13 @@ class ImmichLogger {
}
// Share file
// ignore: deprecated_member_use
await Share.shareFiles(
[filePath],
await Share.shareXFiles(
[XFile(filePath)],
subject: "Immich logs $dateTime",
sharePositionOrigin: Rect.zero,
).then(
(value) => logFile.delete(),
);
// Clean up temp file
await logFile.delete();
}
/// Flush pending log messages to persistent storage

View File

@@ -36,7 +36,6 @@ class ShareService {
}
});
// ignore: deprecated_member_use
Share.shareXFiles(
await Future.wait(downloadedXFiles),
sharePositionOrigin: Rect.zero,

View File

@@ -369,7 +369,6 @@ class SyncService {
List<AssetPathEntity> onDevice, [
Set<String>? excludedAssets,
]) async {
_log.info("Syncing ${onDevice.length} albums from device: $onDevice");
onDevice.sort((a, b) => a.id.compareTo(b.id));
final List<Album> inDb =
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();
@@ -575,7 +574,7 @@ class SyncService {
return true;
}
},
onlyFirst: (Asset a) => throw Exception("programming error"),
onlyFirst: (Asset a) => {},
onlySecond: (Asset b) => toUpsert.add(b),
);
return Pair(existing, toUpsert);

View File

@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:flutter/services.dart';
class AppLogDetailPage extends HookConsumerWidget {
const AppLogDetailPage({super.key, required this.logMessage});
final LoggerMessage logMessage;
@override
Widget build(BuildContext context, WidgetRef ref) {
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
buildStackMessage(String stackTrace) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"STACK TRACES",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: stackTrace))
.then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
],
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
stackTrace,
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
buildLogMessage(String message) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"MESSAGE",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: message)).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
],
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
message,
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
buildLogContext1(String context1) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"FROM",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
context1.toString(),
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Log Detail"),
),
body: SafeArea(
child: ListView(
children: [
buildLogMessage(logMessage.message),
if (logMessage.context1 != null)
buildLogContext1(logMessage.context1.toString()),
if (logMessage.context2 != null)
buildStackMessage(logMessage.context2.toString())
],
),
),
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:intl/intl.dart';
@@ -123,6 +124,12 @@ class AppLogPage extends HookConsumerWidget {
itemBuilder: (context, index) {
var logMessage = logMessages.value[index];
return ListTile(
onTap: () => AutoRouter.of(context).push(
AppLogDetailRoute(
logMessage: logMessage,
),
),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
visualDensity: VisualDensity.compact,
dense: true,
tileColor: getTileColor(logMessage.level),

View File

@@ -53,6 +53,7 @@ doc/JobCommand.md
doc/JobCommandDto.md
doc/JobCountsDto.md
doc/JobName.md
doc/JobStatusDto.md
doc/LoginCredentialDto.md
doc/LoginResponseDto.md
doc/LogoutResponseDto.md
@@ -60,6 +61,7 @@ doc/OAuthApi.md
doc/OAuthCallbackDto.md
doc/OAuthConfigDto.md
doc/OAuthConfigResponseDto.md
doc/QueueStatusDto.md
doc/RemoveAssetsDto.md
doc/SearchAlbumResponseDto.md
doc/SearchApi.md
@@ -170,12 +172,14 @@ lib/model/job_command.dart
lib/model/job_command_dto.dart
lib/model/job_counts_dto.dart
lib/model/job_name.dart
lib/model/job_status_dto.dart
lib/model/login_credential_dto.dart
lib/model/login_response_dto.dart
lib/model/logout_response_dto.dart
lib/model/o_auth_callback_dto.dart
lib/model/o_auth_config_dto.dart
lib/model/o_auth_config_response_dto.dart
lib/model/queue_status_dto.dart
lib/model/remove_assets_dto.dart
lib/model/search_album_response_dto.dart
lib/model/search_asset_dto.dart
@@ -264,6 +268,7 @@ test/job_command_dto_test.dart
test/job_command_test.dart
test/job_counts_dto_test.dart
test/job_name_test.dart
test/job_status_dto_test.dart
test/login_credential_dto_test.dart
test/login_response_dto_test.dart
test/logout_response_dto_test.dart
@@ -271,6 +276,7 @@ test/o_auth_api_test.dart
test/o_auth_callback_dto_test.dart
test/o_auth_config_dto_test.dart
test/o_auth_config_response_dto_test.dart
test/queue_status_dto_test.dart
test/remove_assets_dto_test.dart
test/search_album_response_dto_test.dart
test/search_api_test.dart

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.51.2
- API version: 1.52.1
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -200,12 +200,14 @@ Class | Method | HTTP request | Description
- [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md)
- [JobName](doc//JobName.md)
- [JobStatusDto](doc//JobStatusDto.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
- [OAuthConfigDto](doc//OAuthConfigDto.md)
- [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [RemoveAssetsDto](doc//RemoveAssetsDto.md)
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
- [SearchAssetDto](doc//SearchAssetDto.md)

View File

@@ -91,7 +91,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.deleteKey(id);
@@ -143,7 +143,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getKey(id);
@@ -245,7 +245,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final aPIKeyUpdateDto = APIKeyUpdateDto(); // APIKeyUpdateDto |
try {

View File

@@ -45,7 +45,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final addAssetsDto = AddAssetsDto(); // AddAssetsDto |
final key = key_example; // String |
@@ -102,7 +102,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final addUsersDto = AddUsersDto(); // AddUsersDto |
try {
@@ -263,7 +263,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.deleteAlbum(albumId);
@@ -315,7 +315,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final skip = 8.14; // num |
final key = key_example; // String |
@@ -421,7 +421,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final key = key_example; // String |
try {
@@ -531,7 +531,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final removeAssetsDto = RemoveAssetsDto(); // RemoveAssetsDto |
try {
@@ -586,7 +586,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final userId = userId_example; // String |
try {
@@ -640,7 +640,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateAlbumDto = UpdateAlbumDto(); // UpdateAlbumDto |
try {

View File

@@ -8,14 +8,14 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**thumbnailGenerationQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**metadataExtractionQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**videoConversionQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**objectTaggingQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**clipEncodingQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**storageTemplateMigrationQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**backgroundTaskQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**searchQueue** | [**JobCountsDto**](JobCountsDto.md) | |
**thumbnailGenerationQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**metadataExtractionQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**videoConversionQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**objectTaggingQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**clipEncodingQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**storageTemplateMigrationQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**backgroundTaskQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**searchQueue** | [**JobStatusDto**](JobStatusDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -325,7 +325,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final key = key_example; // String |
try {
@@ -547,7 +547,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final key = key_example; // String |
try {
@@ -806,7 +806,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final format = ; // ThumbnailFormat |
final key = key_example; // String |
@@ -961,7 +961,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final deviceId = deviceId_example; // String |
final deviceId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getUserAssetsByDeviceId(deviceId);
@@ -1122,7 +1122,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final isThumb = true; // bool |
final isWeb = true; // bool |
final key = key_example; // String |
@@ -1181,7 +1181,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto |
try {

View File

@@ -17,6 +17,7 @@ Name | Type | Description | Notes
**orientation** | **String** | | [optional]
**dateTimeOriginal** | [**DateTime**](DateTime.md) | | [optional]
**modifyDate** | [**DateTime**](DateTime.md) | | [optional]
**timeZone** | **String** | | [optional]
**lensModel** | **String** | | [optional]
**fNumber** | **num** | | [optional]
**focalLength** | **num** | | [optional]

View File

@@ -63,7 +63,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **sendJobCommand**
> sendJobCommand(jobId, jobCommandDto)
> JobStatusDto sendJobCommand(jobId, jobCommandDto)
@@ -88,7 +88,8 @@ final jobId = ; // JobName |
final jobCommandDto = JobCommandDto(); // JobCommandDto |
try {
api_instance.sendJobCommand(jobId, jobCommandDto);
final result = api_instance.sendJobCommand(jobId, jobCommandDto);
print(result);
} catch (e) {
print('Exception when calling JobApi->sendJobCommand: $e\n');
}
@@ -103,7 +104,7 @@ Name | Type | Description | Notes
### Return type
void (empty response body)
[**JobStatusDto**](JobStatusDto.md)
### Authorization
@@ -112,7 +113,7 @@ void (empty response body)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -13,6 +13,7 @@ Name | Type | Description | Notes
**failed** | **int** | |
**delayed** | **int** | |
**waiting** | **int** | |
**paused** | **int** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

16
mobile/openapi/doc/JobStatusDto.md generated Normal file
View File

@@ -0,0 +1,16 @@
# openapi.model.JobStatusDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**jobCounts** | [**JobCountsDto**](JobCountsDto.md) | |
**queueStatus** | [**QueueStatusDto**](QueueStatusDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

16
mobile/openapi/doc/QueueStatusDto.md generated Normal file
View File

@@ -0,0 +1,16 @@
# openapi.model.QueueStatusDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**isActive** | **bool** | |
**isPaused** | **bool** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -38,7 +38,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final editSharedLinkDto = EditSharedLinkDto(); // EditSharedLinkDto |
try {
@@ -195,7 +195,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getSharedLinkById(id);
@@ -248,7 +248,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.removeSharedLink(id);

View File

@@ -12,7 +12,7 @@ Name | Type | Description | Notes
**preset** | **String** | |
**targetVideoCodec** | **String** | |
**targetAudioCodec** | **String** | |
**targetScaling** | **String** | |
**targetResolution** | **String** | |
**transcode** | **String** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -91,7 +91,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.delete(id);
@@ -192,7 +192,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.findOne(id);
@@ -245,7 +245,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateTagDto = UpdateTagDto(); // UpdateTagDto |
try {

View File

@@ -8,14 +8,13 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**email** | **String** | | [optional]
**password** | **String** | | [optional]
**firstName** | **String** | | [optional]
**lastName** | **String** | | [optional]
**id** | **String** | |
**isAdmin** | **bool** | | [optional]
**shouldChangePassword** | **bool** | | [optional]
**profileImagePath** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -149,7 +149,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.deleteUser(userId);
@@ -304,7 +304,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getProfileImage(userId);
@@ -357,7 +357,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getUserById(userId);
@@ -453,7 +453,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.restoreUser(userId);

View File

@@ -86,12 +86,14 @@ part 'model/job_command.dart';
part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart';
part 'model/job_name.dart';
part 'model/job_status_dto.dart';
part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart';
part 'model/o_auth_callback_dto.dart';
part 'model/o_auth_config_dto.dart';
part 'model/o_auth_config_response_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/remove_assets_dto.dart';
part 'model/search_album_response_dto.dart';
part 'model/search_asset_dto.dart';

View File

@@ -102,10 +102,18 @@ class JobApi {
/// * [JobName] jobId (required):
///
/// * [JobCommandDto] jobCommandDto (required):
Future<void> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto,) async {
Future<JobStatusDto?> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto,) async {
final response = await sendJobCommandWithHttpInfo(jobId, jobCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'JobStatusDto',) as JobStatusDto;
}
return null;
}
}

View File

@@ -280,6 +280,8 @@ class ApiClient {
return JobCountsDto.fromJson(value);
case 'JobName':
return JobNameTypeTransformer().decode(value);
case 'JobStatusDto':
return JobStatusDto.fromJson(value);
case 'LoginCredentialDto':
return LoginCredentialDto.fromJson(value);
case 'LoginResponseDto':
@@ -292,6 +294,8 @@ class ApiClient {
return OAuthConfigDto.fromJson(value);
case 'OAuthConfigResponseDto':
return OAuthConfigResponseDto.fromJson(value);
case 'QueueStatusDto':
return QueueStatusDto.fromJson(value);
case 'RemoveAssetsDto':
return RemoveAssetsDto.fromJson(value);
case 'SearchAlbumResponseDto':

View File

@@ -23,21 +23,21 @@ class AllJobStatusResponseDto {
required this.searchQueue,
});
JobCountsDto thumbnailGenerationQueue;
JobStatusDto thumbnailGenerationQueue;
JobCountsDto metadataExtractionQueue;
JobStatusDto metadataExtractionQueue;
JobCountsDto videoConversionQueue;
JobStatusDto videoConversionQueue;
JobCountsDto objectTaggingQueue;
JobStatusDto objectTaggingQueue;
JobCountsDto clipEncodingQueue;
JobStatusDto clipEncodingQueue;
JobCountsDto storageTemplateMigrationQueue;
JobStatusDto storageTemplateMigrationQueue;
JobCountsDto backgroundTaskQueue;
JobStatusDto backgroundTaskQueue;
JobCountsDto searchQueue;
JobStatusDto searchQueue;
@override
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
@@ -97,14 +97,14 @@ class AllJobStatusResponseDto {
}());
return AllJobStatusResponseDto(
thumbnailGenerationQueue: JobCountsDto.fromJson(json[r'thumbnail-generation-queue'])!,
metadataExtractionQueue: JobCountsDto.fromJson(json[r'metadata-extraction-queue'])!,
videoConversionQueue: JobCountsDto.fromJson(json[r'video-conversion-queue'])!,
objectTaggingQueue: JobCountsDto.fromJson(json[r'object-tagging-queue'])!,
clipEncodingQueue: JobCountsDto.fromJson(json[r'clip-encoding-queue'])!,
storageTemplateMigrationQueue: JobCountsDto.fromJson(json[r'storage-template-migration-queue'])!,
backgroundTaskQueue: JobCountsDto.fromJson(json[r'background-task-queue'])!,
searchQueue: JobCountsDto.fromJson(json[r'search-queue'])!,
thumbnailGenerationQueue: JobStatusDto.fromJson(json[r'thumbnail-generation-queue'])!,
metadataExtractionQueue: JobStatusDto.fromJson(json[r'metadata-extraction-queue'])!,
videoConversionQueue: JobStatusDto.fromJson(json[r'video-conversion-queue'])!,
objectTaggingQueue: JobStatusDto.fromJson(json[r'object-tagging-queue'])!,
clipEncodingQueue: JobStatusDto.fromJson(json[r'clip-encoding-queue'])!,
storageTemplateMigrationQueue: JobStatusDto.fromJson(json[r'storage-template-migration-queue'])!,
backgroundTaskQueue: JobStatusDto.fromJson(json[r'background-task-queue'])!,
searchQueue: JobStatusDto.fromJson(json[r'search-queue'])!,
);
}
return null;

View File

@@ -22,6 +22,7 @@ class ExifResponseDto {
this.orientation,
this.dateTimeOriginal,
this.modifyDate,
this.timeZone,
this.lensModel,
this.fNumber,
this.focalLength,
@@ -52,6 +53,8 @@ class ExifResponseDto {
DateTime? modifyDate;
String? timeZone;
String? lensModel;
num? fNumber;
@@ -83,6 +86,7 @@ class ExifResponseDto {
other.orientation == orientation &&
other.dateTimeOriginal == dateTimeOriginal &&
other.modifyDate == modifyDate &&
other.timeZone == timeZone &&
other.lensModel == lensModel &&
other.fNumber == fNumber &&
other.focalLength == focalLength &&
@@ -106,6 +110,7 @@ class ExifResponseDto {
(orientation == null ? 0 : orientation!.hashCode) +
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(modifyDate == null ? 0 : modifyDate!.hashCode) +
(timeZone == null ? 0 : timeZone!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(fNumber == null ? 0 : fNumber!.hashCode) +
(focalLength == null ? 0 : focalLength!.hashCode) +
@@ -118,7 +123,7 @@ class ExifResponseDto {
(country == null ? 0 : country!.hashCode);
@override
String toString() => 'ExifResponseDto[fileSizeInByte=$fileSizeInByte, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]';
String toString() => 'ExifResponseDto[fileSizeInByte=$fileSizeInByte, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, timeZone=$timeZone, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -167,6 +172,11 @@ class ExifResponseDto {
} else {
// json[r'modifyDate'] = null;
}
if (this.timeZone != null) {
json[r'timeZone'] = this.timeZone;
} else {
// json[r'timeZone'] = null;
}
if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel;
} else {
@@ -252,6 +262,7 @@ class ExifResponseDto {
orientation: mapValueOfType<String>(json, r'orientation'),
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''),
modifyDate: mapDateTime(json, r'modifyDate', ''),
timeZone: mapValueOfType<String>(json, r'timeZone'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
fNumber: json[r'fNumber'] == null
? null

View File

@@ -18,6 +18,7 @@ class JobCountsDto {
required this.failed,
required this.delayed,
required this.waiting,
required this.paused,
});
int active;
@@ -30,13 +31,16 @@ class JobCountsDto {
int waiting;
int paused;
@override
bool operator ==(Object other) => identical(this, other) || other is JobCountsDto &&
other.active == active &&
other.completed == completed &&
other.failed == failed &&
other.delayed == delayed &&
other.waiting == waiting;
other.waiting == waiting &&
other.paused == paused;
@override
int get hashCode =>
@@ -45,10 +49,11 @@ class JobCountsDto {
(completed.hashCode) +
(failed.hashCode) +
(delayed.hashCode) +
(waiting.hashCode);
(waiting.hashCode) +
(paused.hashCode);
@override
String toString() => 'JobCountsDto[active=$active, completed=$completed, failed=$failed, delayed=$delayed, waiting=$waiting]';
String toString() => 'JobCountsDto[active=$active, completed=$completed, failed=$failed, delayed=$delayed, waiting=$waiting, paused=$paused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -57,6 +62,7 @@ class JobCountsDto {
json[r'failed'] = this.failed;
json[r'delayed'] = this.delayed;
json[r'waiting'] = this.waiting;
json[r'paused'] = this.paused;
return json;
}
@@ -84,6 +90,7 @@ class JobCountsDto {
failed: mapValueOfType<int>(json, r'failed')!,
delayed: mapValueOfType<int>(json, r'delayed')!,
waiting: mapValueOfType<int>(json, r'waiting')!,
paused: mapValueOfType<int>(json, r'paused')!,
);
}
return null;
@@ -138,6 +145,7 @@ class JobCountsDto {
'failed',
'delayed',
'waiting',
'paused',
};
}

View File

@@ -0,0 +1,119 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class JobStatusDto {
/// Returns a new [JobStatusDto] instance.
JobStatusDto({
required this.jobCounts,
required this.queueStatus,
});
JobCountsDto jobCounts;
QueueStatusDto queueStatus;
@override
bool operator ==(Object other) => identical(this, other) || other is JobStatusDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
@override
String toString() => 'JobStatusDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
return json;
}
/// Returns a new [JobStatusDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static JobStatusDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "JobStatusDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "JobStatusDto[$key]" has a null value in JSON.');
});
return true;
}());
return JobStatusDto(
jobCounts: JobCountsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!,
);
}
return null;
}
static List<JobStatusDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobStatusDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = JobStatusDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, JobStatusDto> mapFromJson(dynamic json) {
final map = <String, JobStatusDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = JobStatusDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of JobStatusDto-objects as value to a dart map
static Map<String, List<JobStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<JobStatusDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = JobStatusDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
};
}

View File

@@ -0,0 +1,119 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueStatusDto {
/// Returns a new [QueueStatusDto] instance.
QueueStatusDto({
required this.isActive,
required this.isPaused,
});
bool isActive;
bool isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto &&
other.isActive == isActive &&
other.isPaused == isPaused;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isActive.hashCode) +
(isPaused.hashCode);
@override
String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'isActive'] = this.isActive;
json[r'isPaused'] = this.isPaused;
return json;
}
/// Returns a new [QueueStatusDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueStatusDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "QueueStatusDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "QueueStatusDto[$key]" has a null value in JSON.');
});
return true;
}());
return QueueStatusDto(
isActive: mapValueOfType<bool>(json, r'isActive')!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
);
}
return null;
}
static List<QueueStatusDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueStatusDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueStatusDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueStatusDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueStatusDto-objects as value to a dart map
static Map<String, List<QueueStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueStatusDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'isActive',
'isPaused',
};
}

View File

@@ -17,7 +17,7 @@ class SystemConfigFFmpegDto {
required this.preset,
required this.targetVideoCodec,
required this.targetAudioCodec,
required this.targetScaling,
required this.targetResolution,
required this.transcode,
});
@@ -29,7 +29,7 @@ class SystemConfigFFmpegDto {
String targetAudioCodec;
String targetScaling;
String targetResolution;
SystemConfigFFmpegDtoTranscodeEnum transcode;
@@ -39,7 +39,7 @@ class SystemConfigFFmpegDto {
other.preset == preset &&
other.targetVideoCodec == targetVideoCodec &&
other.targetAudioCodec == targetAudioCodec &&
other.targetScaling == targetScaling &&
other.targetResolution == targetResolution &&
other.transcode == transcode;
@override
@@ -49,11 +49,11 @@ class SystemConfigFFmpegDto {
(preset.hashCode) +
(targetVideoCodec.hashCode) +
(targetAudioCodec.hashCode) +
(targetScaling.hashCode) +
(targetResolution.hashCode) +
(transcode.hashCode);
@override
String toString() => 'SystemConfigFFmpegDto[crf=$crf, preset=$preset, targetVideoCodec=$targetVideoCodec, targetAudioCodec=$targetAudioCodec, targetScaling=$targetScaling, transcode=$transcode]';
String toString() => 'SystemConfigFFmpegDto[crf=$crf, preset=$preset, targetVideoCodec=$targetVideoCodec, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, transcode=$transcode]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -61,7 +61,7 @@ class SystemConfigFFmpegDto {
json[r'preset'] = this.preset;
json[r'targetVideoCodec'] = this.targetVideoCodec;
json[r'targetAudioCodec'] = this.targetAudioCodec;
json[r'targetScaling'] = this.targetScaling;
json[r'targetResolution'] = this.targetResolution;
json[r'transcode'] = this.transcode;
return json;
}
@@ -89,7 +89,7 @@ class SystemConfigFFmpegDto {
preset: mapValueOfType<String>(json, r'preset')!,
targetVideoCodec: mapValueOfType<String>(json, r'targetVideoCodec')!,
targetAudioCodec: mapValueOfType<String>(json, r'targetAudioCodec')!,
targetScaling: mapValueOfType<String>(json, r'targetScaling')!,
targetResolution: mapValueOfType<String>(json, r'targetResolution')!,
transcode: SystemConfigFFmpegDtoTranscodeEnum.fromJson(json[r'transcode'])!,
);
}
@@ -144,7 +144,7 @@ class SystemConfigFFmpegDto {
'preset',
'targetVideoCodec',
'targetAudioCodec',
'targetScaling',
'targetResolution',
'transcode',
};
}
@@ -165,12 +165,14 @@ class SystemConfigFFmpegDtoTranscodeEnum {
static const all = SystemConfigFFmpegDtoTranscodeEnum._(r'all');
static const optimal = SystemConfigFFmpegDtoTranscodeEnum._(r'optimal');
static const required_ = SystemConfigFFmpegDtoTranscodeEnum._(r'required');
static const disabled = SystemConfigFFmpegDtoTranscodeEnum._(r'disabled');
/// List of all possible values in this [enum][SystemConfigFFmpegDtoTranscodeEnum].
static const values = <SystemConfigFFmpegDtoTranscodeEnum>[
all,
optimal,
required_,
disabled,
];
static SystemConfigFFmpegDtoTranscodeEnum? fromJson(dynamic value) => SystemConfigFFmpegDtoTranscodeEnumTypeTransformer().decode(value);
@@ -212,6 +214,7 @@ class SystemConfigFFmpegDtoTranscodeEnumTypeTransformer {
case r'all': return SystemConfigFFmpegDtoTranscodeEnum.all;
case r'optimal': return SystemConfigFFmpegDtoTranscodeEnum.optimal;
case r'required': return SystemConfigFFmpegDtoTranscodeEnum.required_;
case r'disabled': return SystemConfigFFmpegDtoTranscodeEnum.disabled;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@@ -13,18 +13,15 @@ part of openapi.api;
class UpdateUserDto {
/// Returns a new [UpdateUserDto] instance.
UpdateUserDto({
required this.id,
this.email,
this.password,
this.firstName,
this.lastName,
required this.id,
this.isAdmin,
this.shouldChangePassword,
this.profileImagePath,
});
String id;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
@@ -57,6 +54,8 @@ class UpdateUserDto {
///
String? lastName;
String id;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
@@ -73,43 +72,32 @@ class UpdateUserDto {
///
bool? shouldChangePassword;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? profileImagePath;
@override
bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto &&
other.id == id &&
other.email == email &&
other.password == password &&
other.firstName == firstName &&
other.lastName == lastName &&
other.id == id &&
other.isAdmin == isAdmin &&
other.shouldChangePassword == shouldChangePassword &&
other.profileImagePath == profileImagePath;
other.shouldChangePassword == shouldChangePassword;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(email == null ? 0 : email!.hashCode) +
(password == null ? 0 : password!.hashCode) +
(firstName == null ? 0 : firstName!.hashCode) +
(lastName == null ? 0 : lastName!.hashCode) +
(id.hashCode) +
(isAdmin == null ? 0 : isAdmin!.hashCode) +
(shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode) +
(profileImagePath == null ? 0 : profileImagePath!.hashCode);
(shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode);
@override
String toString() => 'UpdateUserDto[id=$id, email=$email, password=$password, firstName=$firstName, lastName=$lastName, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword, profileImagePath=$profileImagePath]';
String toString() => 'UpdateUserDto[email=$email, password=$password, firstName=$firstName, lastName=$lastName, id=$id, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'id'] = this.id;
if (this.email != null) {
json[r'email'] = this.email;
} else {
@@ -130,6 +118,7 @@ class UpdateUserDto {
} else {
// json[r'lastName'] = null;
}
json[r'id'] = this.id;
if (this.isAdmin != null) {
json[r'isAdmin'] = this.isAdmin;
} else {
@@ -140,11 +129,6 @@ class UpdateUserDto {
} else {
// json[r'shouldChangePassword'] = null;
}
if (this.profileImagePath != null) {
json[r'profileImagePath'] = this.profileImagePath;
} else {
// json[r'profileImagePath'] = null;
}
return json;
}
@@ -167,14 +151,13 @@ class UpdateUserDto {
}());
return UpdateUserDto(
id: mapValueOfType<String>(json, r'id')!,
email: mapValueOfType<String>(json, r'email'),
password: mapValueOfType<String>(json, r'password'),
firstName: mapValueOfType<String>(json, r'firstName'),
lastName: mapValueOfType<String>(json, r'lastName'),
id: mapValueOfType<String>(json, r'id')!,
isAdmin: mapValueOfType<bool>(json, r'isAdmin'),
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword'),
profileImagePath: mapValueOfType<String>(json, r'profileImagePath'),
);
}
return null;

View File

@@ -16,42 +16,42 @@ void main() {
// final instance = AllJobStatusResponseDto();
group('test AllJobStatusResponseDto', () {
// JobCountsDto thumbnailGenerationQueue
// JobStatusDto thumbnailGenerationQueue
test('to test the property `thumbnailGenerationQueue`', () async {
// TODO
});
// JobCountsDto metadataExtractionQueue
// JobStatusDto metadataExtractionQueue
test('to test the property `metadataExtractionQueue`', () async {
// TODO
});
// JobCountsDto videoConversionQueue
// JobStatusDto videoConversionQueue
test('to test the property `videoConversionQueue`', () async {
// TODO
});
// JobCountsDto objectTaggingQueue
// JobStatusDto objectTaggingQueue
test('to test the property `objectTaggingQueue`', () async {
// TODO
});
// JobCountsDto clipEncodingQueue
// JobStatusDto clipEncodingQueue
test('to test the property `clipEncodingQueue`', () async {
// TODO
});
// JobCountsDto storageTemplateMigrationQueue
// JobStatusDto storageTemplateMigrationQueue
test('to test the property `storageTemplateMigrationQueue`', () async {
// TODO
});
// JobCountsDto backgroundTaskQueue
// JobStatusDto backgroundTaskQueue
test('to test the property `backgroundTaskQueue`', () async {
// TODO
});
// JobCountsDto searchQueue
// JobStatusDto searchQueue
test('to test the property `searchQueue`', () async {
// TODO
});

View File

@@ -61,6 +61,11 @@ void main() {
// TODO
});
// String timeZone
test('to test the property `timeZone`', () async {
// TODO
});
// String lensModel
test('to test the property `lensModel`', () async {
// TODO

View File

@@ -26,7 +26,7 @@ void main() {
//
//
//Future sendJobCommand(JobName jobId, JobCommandDto jobCommandDto) async
//Future<JobStatusDto> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto) async
test('test sendJobCommand', () async {
// TODO
});

View File

@@ -41,6 +41,11 @@ void main() {
// TODO
});
// int paused
test('to test the property `paused`', () async {
// TODO
});
});

View File

@@ -0,0 +1,32 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for JobStatusDto
void main() {
// final instance = JobStatusDto();
group('test JobStatusDto', () {
// JobCountsDto jobCounts
test('to test the property `jobCounts`', () async {
// TODO
});
// QueueStatusDto queueStatus
test('to test the property `queueStatus`', () async {
// TODO
});
});
}

View File

@@ -0,0 +1,32 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for QueueStatusDto
void main() {
// final instance = QueueStatusDto();
group('test QueueStatusDto', () {
// bool isActive
test('to test the property `isActive`', () async {
// TODO
});
// bool isPaused
test('to test the property `isPaused`', () async {
// TODO
});
});
}

View File

@@ -36,8 +36,8 @@ void main() {
// TODO
});
// String targetScaling
test('to test the property `targetScaling`', () async {
// String targetResolution
test('to test the property `targetResolution`', () async {
// TODO
});

View File

@@ -16,11 +16,6 @@ void main() {
// final instance = UpdateUserDto();
group('test UpdateUserDto', () {
// String id
test('to test the property `id`', () async {
// TODO
});
// String email
test('to test the property `email`', () async {
// TODO
@@ -41,6 +36,11 @@ void main() {
// TODO
});
// String id
test('to test the property `id`', () async {
// TODO
});
// bool isAdmin
test('to test the property `isAdmin`', () async {
// TODO
@@ -51,11 +51,6 @@ void main() {
// TODO
});
// String profileImagePath
test('to test the property `profileImagePath`', () async {
// TODO
});
});

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.52.0+75
version: 1.53.0+76
isar_version: &isar_version 3.0.5
environment:

View File

@@ -0,0 +1,14 @@
cd .isar
bash tool/build_android.sh x86
bash tool/build_android.sh x64
bash tool/build_android.sh armv7
bash tool/build_android.sh arm64
mv libisar_android_arm64.so libisar.so
mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/
mv libisar_android_armv7.so libisar.so
mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/
mv libisar_android_x64.so libisar.so
mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86_64/
mv libisar_android_x86.so libisar.so
mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86/
cd ..

View File

@@ -19,6 +19,8 @@ RUN npm prune --omit=dev --omit=optional
FROM node:16-alpine3.14
ENV NODE_ENV=production
WORKDIR /usr/src/app
RUN apk add --no-cache libheif vips ffmpeg perl

View File

@@ -1,5 +1,5 @@
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/db/entities';
import { dataSource } from '@app/infra/db/config';
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
import { dataSource } from '@app/infra/database.config';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

View File

@@ -7,7 +7,6 @@ import {
Param,
Delete,
ValidationPipe,
ParseUUIDPipe,
Put,
Query,
Response,
@@ -33,8 +32,7 @@ import {
} from '../../constants/download.constant';
import { DownloadDto } from '../asset/dto/download-library.dto';
import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto';
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
import { AlbumIdDto } from './dto/album-id.dto';
@ApiTags('Album')
@Controller('album')
@@ -58,7 +56,7 @@ export class AlbumController {
async addUsersToAlbum(
@GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addUsersDto: AddUsersDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
) {
return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId);
}
@@ -68,17 +66,14 @@ export class AlbumController {
async addAssetsToAlbum(
@GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addAssetsDto: AddAssetsDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
): Promise<AddAssetsResponseDto> {
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
}
@Authenticated({ isShared: true })
@Get('/:albumId')
async getAlbumInfo(
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
) {
async getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
return this.albumService.getAlbumInfo(authUser, albumId);
}
@@ -87,17 +82,14 @@ export class AlbumController {
async removeAssetFromAlbum(
@GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
): Promise<AlbumResponseDto> {
return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId);
}
@Authenticated()
@Delete('/:albumId')
async deleteAlbum(
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
) {
async deleteAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
return this.albumService.deleteAlbum(authUser, albumId);
}
@@ -105,7 +97,7 @@ export class AlbumController {
@Delete('/:albumId/user/:userId')
async removeUserFromAlbum(
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
) {
return this.albumService.removeUserFromAlbum(authUser, albumId, userId);
@@ -116,7 +108,7 @@ export class AlbumController {
async updateAlbumInfo(
@GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
) {
return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId);
}
@@ -126,7 +118,7 @@ export class AlbumController {
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
async downloadArchive(
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Param() { albumId }: AlbumIdDto,
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
@Response({ passthrough: true }) res: Res,
) {

View File

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
import { AlbumService } from './album.service';
import { AlbumController } from './album.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AlbumEntity, AssetEntity } from '@app/infra/db/entities';
import { AlbumEntity, AssetEntity } from '@app/infra/entities';
import { AlbumRepository, IAlbumRepository } from './album-repository';
import { DownloadModule } from '../../modules/download/download.module';

View File

@@ -1,7 +1,7 @@
import { AlbumService } from './album.service';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
import { AlbumEntity, UserEntity } from '@app/infra/db/entities';
import { AlbumEntity, UserEntity } from '@app/infra/entities';
import { AlbumResponseDto, ICryptoRepository, IJobRepository, JobName, mapUser } from '@app/domain';
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
import { IAlbumRepository } from './album-repository';

View File

@@ -1,7 +1,7 @@
import { BadRequestException, Inject, Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { CreateAlbumDto } from './dto/create-album.dto';
import { AlbumEntity, SharedLinkType } from '@app/infra/db/entities';
import { AlbumEntity, SharedLinkType } from '@app/infra/entities';
import { AddUsersDto } from './dto/add-users.dto';
import { RemoveAssetsDto } from './dto/remove-assets.dto';
import { UpdateAlbumDto } from './dto/update-album.dto';

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class AlbumIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
albumId!: string;
}

View File

@@ -1,6 +1,6 @@
import { SearchPropertiesDto } from './dto/search-properties.dto';
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
import { AssetEntity, AssetType } from '@app/infra/db/entities';
import { AssetEntity, AssetType } from '@app/infra/entities';
import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm/repository/Repository';

View File

@@ -57,6 +57,8 @@ import { AssetSearchDto } from './dto/asset-search.dto';
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
import FileNotEmptyValidator from '../validation/file-not-empty-validator';
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
import { AssetIdDto } from './dto/asset-id.dto';
import { DeviceIdDto } from './dto/device-id.dto';
function asStreamableFile({ stream, type, length }: ImmichReadStream) {
return new StreamableFile(stream, { type, length });
@@ -111,7 +113,7 @@ export class AssetController {
async downloadFile(
@GetAuthUser() authUser: AuthUserDto,
@Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string,
@Param() { assetId }: AssetIdDto,
) {
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
}
@@ -163,7 +165,7 @@ export class AssetController {
@Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res,
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
@Param('assetId') assetId: string,
@Param() { assetId }: AssetIdDto,
) {
await this.assetService.checkAssetsAccess(authUser, [assetId]);
return this.assetService.serveFile(authUser, assetId, query, res, headers);
@@ -177,7 +179,7 @@ export class AssetController {
@GetAuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string,
@Param() { assetId }: AssetIdDto,
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
) {
await this.assetService.checkAssetsAccess(authUser, [assetId]);
@@ -258,7 +260,7 @@ export class AssetController {
*/
@Authenticated()
@Get('/:deviceId')
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) {
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) {
return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
}
@@ -269,7 +271,7 @@ export class AssetController {
@Get('/assetById/:assetId')
async getAssetById(
@GetAuthUser() authUser: AuthUserDto,
@Param('assetId') assetId: string,
@Param() { assetId }: AssetIdDto,
): Promise<AssetResponseDto> {
await this.assetService.checkAssetsAccess(authUser, [assetId]);
return await this.assetService.getAssetById(authUser, assetId);
@@ -282,7 +284,7 @@ export class AssetController {
@Put('/:assetId')
async updateAsset(
@GetAuthUser() authUser: AuthUserDto,
@Param('assetId') assetId: string,
@Param() { assetId }: AssetIdDto,
@Body(ValidationPipe) dto: UpdateAssetDto,
): Promise<AssetResponseDto> {
await this.assetService.checkAssetsAccess(authUser, [assetId], true);

View File

@@ -1,5 +1,5 @@
import { AuthUserDto, IJobRepository, JobName } from '@app/domain';
import { AssetEntity, UserEntity } from '@app/infra/db/entities';
import { AssetEntity, UserEntity } from '@app/infra/entities';
import { IAssetRepository } from './asset-repository';
import { CreateAssetDto, UploadFile } from './dto/create-asset.dto';

View File

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
import { AssetService } from './asset.service';
import { AssetController } from './asset.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetEntity } from '@app/infra/db/entities';
import { AssetEntity } from '@app/infra/entities';
import { AssetRepository, IAssetRepository } from './asset-repository';
import { DownloadModule } from '../../modules/download/download.module';
import { TagModule } from '../tag/tag.module';

View File

@@ -1,7 +1,7 @@
import { IAssetRepository } from './asset-repository';
import { AssetService } from './asset.service';
import { QueryFailedError, Repository } from 'typeorm';
import { AssetEntity, AssetType } from '@app/infra/db/entities';
import { AssetEntity, AssetType } from '@app/infra/entities';
import { CreateAssetDto } from './dto/create-asset.dto';
import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';

View File

@@ -12,7 +12,7 @@ import {
import { InjectRepository } from '@nestjs/typeorm';
import { QueryFailedError, Repository } from 'typeorm';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/db/entities';
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
import { constants, createReadStream, stat } from 'fs';
import { ServeFileDto } from './dto/serve-file.dto';
import { Response as Res } from 'express';
@@ -314,7 +314,7 @@ export class AssetService {
return new StreamableFile(videoStream);
}
return this.streamFile(asset.originalPath, res, headers, mimeType);
return this.streamFile(videoPath, res, headers, mimeType);
} catch (e) {
this.logger.error(`Error serving VIDEO asset=${asset.id}`);
throw new InternalServerErrorException(`Failed to serve video asset ${e}`, 'ServeFile');

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class AssetIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
assetId!: string;
}

View File

@@ -1,4 +1,4 @@
import { AssetType } from '@app/infra/db/entities';
import { AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { ImmichFile } from '../../../config/asset-upload.config';

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class DeviceIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
deviceId!: string;
}

View File

@@ -1,4 +1,4 @@
import { TagType } from '@app/infra/db/entities';
import { TagType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';

View File

@@ -6,6 +6,7 @@ import { Authenticated } from '../../decorators/authenticated.decorator';
import { ApiTags } from '@nestjs/swagger';
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
import { mapTag, TagResponseDto } from '@app/domain';
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
@Authenticated()
@ApiTags('Tag')
@@ -27,7 +28,7 @@ export class TagController {
}
@Get(':id')
async findOne(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<TagResponseDto> {
async findOne(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
const tag = await this.tagService.findOne(authUser, id);
return mapTag(tag);
}
@@ -35,14 +36,14 @@ export class TagController {
@Patch(':id')
update(
@GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string,
@Param() { id }: UUIDParamDto,
@Body(ValidationPipe) updateTagDto: UpdateTagDto,
): Promise<TagResponseDto> {
return this.tagService.update(authUser, id, updateTagDto);
}
@Delete(':id')
delete(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> {
delete(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.tagService.remove(authUser, id);
}
}

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { TagService } from './tag.service';
import { TagController } from './tag.controller';
import { TagEntity } from '@app/infra/db/entities';
import { TagEntity } from '@app/infra/entities';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TagRepository, ITagRepository } from './tag.repository';

View File

@@ -1,4 +1,4 @@
import { TagEntity, TagType } from '@app/infra/db/entities';
import { TagEntity, TagType } from '@app/infra/entities';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';

View File

@@ -1,4 +1,4 @@
import { TagEntity, TagType, UserEntity } from '@app/infra/db/entities';
import { TagEntity, TagType, UserEntity } from '@app/infra/entities';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { ITagRepository } from './tag.repository';
import { TagService } from './tag.service';

View File

@@ -1,4 +1,4 @@
import { TagEntity } from '@app/infra/db/entities';
import { TagEntity } from '@app/infra/entities';
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { CreateTagDto } from './dto/create-tag.dto';

View File

@@ -1,21 +1,20 @@
import { AlbumService, AuthUserDto } from '@app/domain';
import { GetAlbumsDto } from '@app/domain/album/dto/get-albums.dto';
import { Controller, Get, Query, ValidationPipe } from '@nestjs/common';
import { Controller, Get, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator';
@ApiTags('Album')
@Controller('album')
@Authenticated()
@UseValidation()
export class AlbumController {
constructor(private service: AlbumService) {}
@Get()
async getAllAlbums(
@GetAuthUser() authUser: AuthUserDto,
@Query(new ValidationPipe({ transform: true })) query: GetAlbumsDto,
) {
async getAllAlbums(@GetAuthUser() authUser: AuthUserDto, @Query() query: GetAlbumsDto) {
return this.service.getAllAlbums(authUser, query);
}
}

View File

@@ -6,22 +6,22 @@ import {
APIKeyUpdateDto,
AuthUserDto,
} from '@app/domain';
import { Body, Controller, Delete, Get, Param, Post, Put, ValidationPipe } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator';
import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('API Key')
@Controller('api-key')
@Authenticated()
@UseValidation()
export class APIKeyController {
constructor(private service: APIKeyService) {}
@Post()
createKey(
@GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) dto: APIKeyCreateDto,
): Promise<APIKeyCreateResponseDto> {
createKey(@GetAuthUser() authUser: AuthUserDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
return this.service.create(authUser, dto);
}
@@ -31,21 +31,21 @@ export class APIKeyController {
}
@Get(':id')
getKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<APIKeyResponseDto> {
getKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
return this.service.getById(authUser, id);
}
@Put(':id')
updateKey(
@GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string,
@Body(ValidationPipe) dto: APIKeyUpdateDto,
@Param() { id }: UUIDParamDto,
@Body() dto: APIKeyUpdateDto,
): Promise<APIKeyResponseDto> {
return this.service.update(authUser, id, dto);
}
@Delete(':id')
deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> {
deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(authUser, id);
}
}

View File

@@ -13,20 +13,22 @@ import {
UserResponseDto,
ValidateAccessTokenResponseDto,
} from '@app/domain';
import { Body, Controller, Ip, Post, Req, Res, ValidationPipe } from '@nestjs/common';
import { Body, Controller, Ip, Post, Req, Res } from '@nestjs/common';
import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator';
@ApiTags('Authentication')
@Controller('auth')
@UseValidation()
export class AuthController {
constructor(private readonly service: AuthService) {}
@Post('login')
async login(
@Body(new ValidationPipe({ transform: true })) loginCredential: LoginCredentialDto,
@Body() loginCredential: LoginCredentialDto,
@Ip() clientIp: string,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
@@ -38,9 +40,7 @@ export class AuthController {
@Post('admin-sign-up')
@ApiBadRequestResponse({ description: 'The server already has an admin' })
adminSignUp(
@Body(new ValidationPipe({ transform: true })) signUpCredential: SignUpDto,
): Promise<AdminSignupResponseDto> {
adminSignUp(@Body() signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
return this.service.adminSignUp(signUpCredential);
}

View File

@@ -4,19 +4,21 @@ import {
DeviceInfoService,
UpsertDeviceInfoDto as UpsertDto,
} from '@app/domain';
import { Body, Controller, Put, ValidationPipe } from '@nestjs/common';
import { Body, Controller, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator';
@ApiTags('Device Info')
@Controller('device-info')
@Authenticated()
@UseValidation()
export class DeviceInfoController {
constructor(private readonly service: DeviceInfoService) {}
@Put()
upsertDeviceInfo(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) dto: UpsertDto): Promise<ResponseDto> {
upsertDeviceInfo(@GetAuthUser() authUser: AuthUserDto, @Body() dto: UpsertDto): Promise<ResponseDto> {
return this.service.upsert(authUser, dto);
}
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class UUIDParamDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
id!: string;
}

Some files were not shown because too many files have changed in this diff Show More