diff --git a/.github/workflows/docker-cleanup.yml b/.github/workflows/docker-cleanup.yml index 6edcef91f..ea7374bbf 100644 --- a/.github/workflows/docker-cleanup.yml +++ b/.github/workflows/docker-cleanup.yml @@ -38,7 +38,7 @@ jobs: - name: Clean temporary images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/ephemeral@v0.3.0 + uses: stumpylog/image-cleaner-action/ephemeral@v0.4.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" @@ -70,7 +70,7 @@ jobs: - name: Clean untagged images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/untagged@v0.3.0 + uses: stumpylog/image-cleaner-action/untagged@v0.4.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3da18aa43..823b73c25 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,91 +33,10 @@ jobs: - context: "nginx" image: "immich-proxy" platforms: "linux/amd64,linux/arm64" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3.0.0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 - # Workaround to fix error: - # failed to push: failed to copy: io: read/write on closed pipe - # See https://github.com/docker/build-push-action/issues/761 - with: - driver-opts: | - image=moby/buildkit:v0.10.6 - - - name: Login to Docker Hub - # Only push to Docker Hub when making a release - if: ${{ github.event_name == 'release' }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - # Skip when PR from a fork - if: ${{ !github.event.pull_request.head.repo.fork }} - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate docker image tags - id: metadata - uses: docker/metadata-action@v5 - with: - flavor: | - # Disable latest tag - latest=false - images: | - name=ghcr.io/${{ github.repository_owner }}/${{matrix.image}} - name=altran1502/${{matrix.image}},enable=${{ github.event_name == 'release' }} - tags: | - # Tag with branch name - type=ref,event=branch - # Tag with pr-number - type=ref,event=pr - # Tag with git tag on release - type=ref,event=tag - type=raw,value=release,enable=${{ github.event_name == 'release' }} - - - name: Determine build cache output - id: cache-target - run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - # Essentially just ignore the cache output (PR can't write to registry cache) - echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT - else - echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ matrix.image }}" >> $GITHUB_OUTPUT - fi - - - name: Build and push image - uses: docker/build-push-action@v5.0.0 - with: - context: ${{ matrix.context }} - platforms: ${{ matrix.platforms }} - # Skip pushing when PR from a fork - push: ${{ !github.event.pull_request.head.repo.fork }} - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{matrix.image}} - cache-to: ${{ steps.cache-target.outputs.cache-to }} - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - - build_and_push_server_arm_64: - runs-on: self-hosted - strategy: - # Prevent a failure in one image from stopping the other builds - fail-fast: false - matrix: - include: - context: "server" image: "immich-server" platforms: "linux/arm64,linux/amd64" + steps: - name: Checkout uses: actions/checkout@v4 diff --git a/README.md b/README.md index d4d38d0d5..4f28295eb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
License: MIT - + Discord

diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 9f4d765f2..798d388d7 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -209,43 +209,6 @@ export interface AddUsersDto { */ 'sharedUserIds': Array; } -/** - * - * @export - * @interface AdminSignupResponseDto - */ -export interface AdminSignupResponseDto { - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'createdAt': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'email': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'id': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'lastName': string; -} /** * * @export @@ -331,6 +294,12 @@ export interface AlbumResponseDto { * @memberof AlbumResponseDto */ 'id': string; + /** + * + * @type {boolean} + * @memberof AlbumResponseDto + */ + 'isActivityEnabled': boolean; /** * * @type {string} @@ -1855,7 +1824,7 @@ export interface ImportAssetDto { * @type {boolean} * @memberof ImportAssetDto */ - 'isFavorite': boolean; + 'isFavorite'?: boolean; /** * * @type {boolean} @@ -2255,6 +2224,20 @@ export interface MapMarkerResponseDto { */ 'lon': number; } +/** + * + * @export + * @enum {string} + */ + +export const MapTheme = { + Light: 'light', + Dark: 'dark' +} as const; + +export type MapTheme = typeof MapTheme[keyof typeof MapTheme]; + + /** * * @export @@ -2587,6 +2570,20 @@ export interface QueueStatusDto { */ 'isPaused': boolean; } +/** + * + * @export + * @enum {string} + */ + +export const ReactionLevel = { + Album: 'album', + Asset: 'asset' +} as const; + +export type ReactionLevel = typeof ReactionLevel[keyof typeof ReactionLevel]; + + /** * * @export @@ -2853,12 +2850,6 @@ export interface ServerConfigDto { * @memberof ServerConfigDto */ 'loginPageMessage': string; - /** - * - * @type {string} - * @memberof ServerConfigDto - */ - 'mapTileUrl': string; /** * * @type {string} @@ -3726,6 +3717,12 @@ export interface SystemConfigMachineLearningDto { * @interface SystemConfigMapDto */ export interface SystemConfigMapDto { + /** + * + * @type {string} + * @memberof SystemConfigMapDto + */ + 'darkStyle': string; /** * * @type {boolean} @@ -3737,7 +3734,7 @@ export interface SystemConfigMapDto { * @type {string} * @memberof SystemConfigMapDto */ - 'tileUrl': string; + 'lightStyle': string; } /** * @@ -4160,6 +4157,12 @@ export interface UpdateAlbumDto { * @memberof UpdateAlbumDto */ 'description'?: string; + /** + * + * @type {boolean} + * @memberof UpdateAlbumDto + */ + 'isActivityEnabled'?: boolean; } /** * @@ -5076,11 +5079,12 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {ReactionLevel} [level] * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise => { + getActivities: async (albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'albumId' is not null or undefined assertParamExists('getActivities', 'albumId', albumId) const localVarPath = `/activity`; @@ -5116,6 +5120,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat localVarQueryParameter['type'] = type; } + if (level !== undefined) { + localVarQueryParameter['level'] = level; + } + if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -5216,12 +5224,13 @@ export const ActivityApiFp = function(configuration?: Configuration) { * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {ReactionLevel} [level] * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options); + async getActivities(albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, level, userId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -5270,7 +5279,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP * @throws {RequiredError} */ getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath)); + return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(axios, basePath)); }, /** * @@ -5339,6 +5348,13 @@ export interface ActivityApiGetActivitiesRequest { */ readonly type?: ReactionType + /** + * + * @type {ReactionLevel} + * @memberof ActivityApiGetActivities + */ + readonly level?: ReactionLevel + /** * * @type {string} @@ -5405,7 +5421,7 @@ export class ActivityApi extends BaseAPI { * @memberof ActivityApi */ public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) { - return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); + return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); } /** @@ -6695,16 +6711,18 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * Get all AssetEntity belong to the user + * @param {number} [skip] + * @param {number} [take] * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {number} [skip] * @param {string} [updatedAfter] + * @param {string} [updatedBefore] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { + getAllAssets: async (skip?: number, take?: number, userId?: string, isFavorite?: boolean, isArchived?: boolean, updatedAfter?: string, updatedBefore?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/asset`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -6726,6 +6744,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (skip !== undefined) { + localVarQueryParameter['skip'] = skip; + } + + if (take !== undefined) { + localVarQueryParameter['take'] = take; + } + if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -6738,16 +6764,18 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['isArchived'] = isArchived; } - if (skip !== undefined) { - localVarQueryParameter['skip'] = skip; - } - if (updatedAfter !== undefined) { localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? (updatedAfter as any).toISOString() : updatedAfter; } + if (updatedBefore !== undefined) { + localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ? + (updatedBefore as any).toISOString() : + updatedBefore; + } + if (ifNoneMatch != null) { localVarHeaderParameter['if-none-match'] = String(ifNoneMatch); } @@ -7868,11 +7896,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} deviceId * @param {string} fileCreatedAt * @param {string} fileModifiedAt - * @param {boolean} isFavorite * @param {string} [key] * @param {string} [duration] * @param {boolean} [isArchived] * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] * @param {boolean} [isOffline] * @param {boolean} [isReadOnly] * @param {boolean} [isVisible] @@ -7882,7 +7910,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile: async (assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options: AxiosRequestConfig = {}): Promise => { + uploadFile: async (assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isFavorite?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetData' is not null or undefined assertParamExists('uploadFile', 'assetData', assetData) // verify required parameter 'deviceAssetId' is not null or undefined @@ -7893,8 +7921,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration assertParamExists('uploadFile', 'fileCreatedAt', fileCreatedAt) // verify required parameter 'fileModifiedAt' is not null or undefined assertParamExists('uploadFile', 'fileModifiedAt', fileModifiedAt) - // verify required parameter 'isFavorite' is not null or undefined - assertParamExists('uploadFile', 'isFavorite', isFavorite) const localVarPath = `/asset/upload`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8068,17 +8094,19 @@ export const AssetApiFp = function(configuration?: Configuration) { }, /** * Get all AssetEntity belong to the user + * @param {number} [skip] + * @param {number} [take] * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {number} [skip] * @param {string} [updatedAfter] + * @param {string} [updatedBefore] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch, options); + async getAllAssets(skip?: number, take?: number, userId?: string, isFavorite?: boolean, isArchived?: boolean, updatedAfter?: string, updatedBefore?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8335,11 +8363,11 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} deviceId * @param {string} fileCreatedAt * @param {string} fileModifiedAt - * @param {boolean} isFavorite * @param {string} [key] * @param {string} [duration] * @param {boolean} [isArchived] * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] * @param {boolean} [isOffline] * @param {boolean} [isReadOnly] * @param {boolean} [isVisible] @@ -8349,8 +8377,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async uploadFile(assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData, options); + async uploadFile(assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isFavorite?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, duration, isArchived, isExternal, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -8423,7 +8451,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); + return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); }, /** * Get a single asset\'s information @@ -8626,7 +8654,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ uploadFile(requestParameters: AssetApiUploadFileRequest, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.isFavorite, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(axios, basePath)); + return localVarFp.uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(axios, basePath)); }, }; }; @@ -8721,6 +8749,20 @@ export interface AssetApiDownloadFileRequest { * @interface AssetApiGetAllAssetsRequest */ export interface AssetApiGetAllAssetsRequest { + /** + * + * @type {number} + * @memberof AssetApiGetAllAssets + */ + readonly skip?: number + + /** + * + * @type {number} + * @memberof AssetApiGetAllAssets + */ + readonly take?: number + /** * * @type {string} @@ -8744,17 +8786,17 @@ export interface AssetApiGetAllAssetsRequest { /** * - * @type {number} + * @type {string} * @memberof AssetApiGetAllAssets */ - readonly skip?: number + readonly updatedAfter?: string /** * * @type {string} * @memberof AssetApiGetAllAssets */ - readonly updatedAfter?: string + readonly updatedBefore?: string /** * ETag of data already cached on the client @@ -9274,13 +9316,6 @@ export interface AssetApiUploadFileRequest { */ readonly fileModifiedAt: string - /** - * - * @type {boolean} - * @memberof AssetApiUploadFile - */ - readonly isFavorite: boolean - /** * * @type {string} @@ -9309,6 +9344,13 @@ export interface AssetApiUploadFileRequest { */ readonly isExternal?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiUploadFile + */ + readonly isFavorite?: boolean + /** * * @type {boolean} @@ -9432,7 +9474,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9681,7 +9723,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public uploadFile(requestParameters: AssetApiUploadFileRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.isFavorite, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(this.axios, this.basePath)); } } @@ -10471,7 +10513,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10551,7 +10593,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { + signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath)); }, /** @@ -15062,6 +15104,51 @@ export const SystemConfigApiAxiosParamCreator = function (configuration?: Config + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {MapTheme} theme + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMapStyle: async (theme: MapTheme, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'theme' is not null or undefined + assertParamExists('getMapStyle', 'theme', theme) + const localVarPath = `/system-config/map/style.json`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (theme !== undefined) { + localVarQueryParameter['theme'] = theme; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -15181,6 +15268,16 @@ export const SystemConfigApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getConfigDefaults(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {MapTheme} theme + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getMapStyle(theme: MapTheme, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getMapStyle(theme, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -15226,6 +15323,15 @@ export const SystemConfigApiFactory = function (configuration?: Configuration, b getConfigDefaults(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getConfigDefaults(options).then((request) => request(axios, basePath)); }, + /** + * + * @param {SystemConfigApiGetMapStyleRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMapStyle(requestParameters: SystemConfigApiGetMapStyleRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getMapStyle(requestParameters.theme, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -15246,6 +15352,20 @@ export const SystemConfigApiFactory = function (configuration?: Configuration, b }; }; +/** + * Request parameters for getMapStyle operation in SystemConfigApi. + * @export + * @interface SystemConfigApiGetMapStyleRequest + */ +export interface SystemConfigApiGetMapStyleRequest { + /** + * + * @type {MapTheme} + * @memberof SystemConfigApiGetMapStyle + */ + readonly theme: MapTheme +} + /** * Request parameters for updateConfig operation in SystemConfigApi. * @export @@ -15287,6 +15407,17 @@ export class SystemConfigApi extends BaseAPI { return SystemConfigApiFp(this.configuration).getConfigDefaults(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {SystemConfigApiGetMapStyleRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SystemConfigApi + */ + public getMapStyle(requestParameters: SystemConfigApiGetMapStyleRequest, options?: AxiosRequestConfig) { + return SystemConfigApiFp(this.configuration).getMapStyle(requestParameters.theme, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/cli/src/api/open-api/base.ts b/cli/src/api/open-api/base.ts index 9a534e7bd..b513ad201 100644 --- a/cli/src/api/open-api/base.ts +++ b/cli/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/common.ts b/cli/src/api/open-api/common.ts index 8997b2d52..910b3970e 100644 --- a/cli/src/api/open-api/common.ts +++ b/cli/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/configuration.ts b/cli/src/api/open-api/configuration.ts index 8058881d1..58dce4201 100644 --- a/cli/src/api/open-api/configuration.ts +++ b/cli/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/index.ts b/cli/src/api/open-api/index.ts index d0651f28a..e2baa279b 100644 --- a/cli/src/api/open-api/index.ts +++ b/cli/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 2d44a1f07..ba03a1822 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -1,5 +1,11 @@ +# See: +# - https://immich.app/docs/developer/setup +# - https://immich.app/docs/developer/troubleshooting + version: "3.8" +name: immich-dev + services: immich-server: container_name: immich_server @@ -71,10 +77,6 @@ services: command: npm run dev --host env_file: - .env - environment: - # Rename these values for svelte public interface - - PUBLIC_IMMICH_SERVER_URL=${IMMICH_SERVER_URL} - - PUBLIC_IMMICH_API_URL_EXTERNAL=${IMMICH_API_URL_EXTERNAL} ports: - 3000:3000 - 24678:24678 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 030429985..d9e33b020 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -1,5 +1,7 @@ version: "3.8" +name: immich-prod + services: immich-server: container_name: immich_server @@ -7,9 +9,9 @@ services: build: context: ../server dockerfile: Dockerfile - command: ["./start-server.sh"] + command: [ "./start-server.sh" ] volumes: - - ${UPLOAD_LOCATION}:/usr/src/app/upload + - ${UPLOAD_LOCATION}/photos:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env @@ -18,19 +20,6 @@ services: - database - typesense - immich-machine-learning: - container_name: immich_machine_learning - image: immich-machine-learning:latest - build: - context: ../machine-learning - dockerfile: Dockerfile - volumes: - - ${UPLOAD_LOCATION}:/usr/src/app/upload - - model-cache:/cache - env_file: - - .env - restart: always - immich-microservices: container_name: immich_microservices image: immich-microservices:latest @@ -40,9 +29,9 @@ services: build: context: ../server dockerfile: Dockerfile - command: ["./start-microservices.sh"] + command: [ "./start-microservices.sh" ] volumes: - - ${UPLOAD_LOCATION}:/usr/src/app/upload + - ${UPLOAD_LOCATION}/photos:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env @@ -64,6 +53,18 @@ services: depends_on: - immich-server + immich-machine-learning: + container_name: immich_machine_learning + image: immich-machine-learning:latest + build: + context: ../machine-learning + dockerfile: Dockerfile + volumes: + - model-cache:/cache + env_file: + - .env + restart: always + typesense: container_name: immich_typesense image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd @@ -73,7 +74,7 @@ services: # remove this to get debug messages - GLOG_minloglevel=1 volumes: - - tsdata:/data + - ${UPLOAD_LOCATION}/typesense:/data restart: always redis: @@ -91,7 +92,7 @@ services: POSTGRES_USER: ${DB_USERNAME} POSTGRES_DB: ${DB_DATABASE_NAME} volumes: - - pgdata:/var/lib/postgresql/data + - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data restart: always immich-proxy: @@ -113,6 +114,4 @@ services: restart: always volumes: - pgdata: model-cache: - tsdata: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f020f8e34..2ea9e18fa 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,10 +1,12 @@ version: "3.8" +name: immich + services: immich-server: container_name: immich_server image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} - command: ["start.sh", "immich"] + command: [ "start.sh", "immich" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro @@ -22,7 +24,7 @@ services: # extends: # file: hwaccel.yml # service: hwaccel - command: ["start.sh", "microservices"] + command: [ "start.sh", "microservices" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md index f94e2ca4f..6753e864c 100644 --- a/docs/docs/developer/database-migrations.md +++ b/docs/docs/developer/database-migrations.md @@ -9,6 +9,6 @@ npm run typeorm:migrations:generate ./src/infra/ ``` 2. Check if the migration file makes sense. -3. Move the migration file to folder `./src/infra/database/migrations` in your code editor. +3. Move the migration file to folder `./server/src/infra/migrations` in your code editor. The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately. diff --git a/docs/docs/developer/troubleshooting.md b/docs/docs/developer/troubleshooting.md new file mode 100644 index 000000000..83e4f370d --- /dev/null +++ b/docs/docs/developer/troubleshooting.md @@ -0,0 +1,19 @@ +# Troubleshooting + +:::tip +A great option to get assistance with troubleshooting is to join our [Discord](https://discord.gg/D8JsnBEuKb) server, where we have a dedicated channel for `#contributing`. +::: + +## Known Issues + +### Running on Windows + +Running Immich on Windows can be frustrating and there are lots of ways it can go wrong. Where possible we recommend using Docker on Linux. However, several people have had success running Immich on Windows using Docker via WSL2. + +### NTFS Mounted Volumes + +The docker-compose.dev.yml and docker-compose.prod.yml use volume mounts for the postgres database. On start-up, postgres will try to `chown` the data directory, but fail. See [this post](https://forums.docker.com/t/data-directory-var-lib-postgresql-data-pgdata-has-wrong-ownership/17963/24) for more information about this issue and possible solutions. + +### `Cannot read properties of null (reading 'split')` + +This error occurs when trying to access the app via port `3000` instead of `2283`. During development `immich-proxy` runs on port 2283, while `immich-web` runs on `3000`. diff --git a/docs/docs/features/bulk-upload.md b/docs/docs/features/bulk-upload.md index 5923e5c16..c072f73ef 100644 --- a/docs/docs/features/bulk-upload.md +++ b/docs/docs/features/bulk-upload.md @@ -4,6 +4,10 @@ You can use the CLI to upload an existing gallery to the Immich server [Immich CLI Repository](https://github.com/immich-app/CLI) +:::tip Google Photos Takeout +If you are looking to import your Google Photos takeout, we recommed this community maintained tool [immich-go](https://github.com/simulot/immich-go) +::: + ## Requirements - Node.js 16 or above diff --git a/docs/docs/features/facial-recognition.md b/docs/docs/features/facial-recognition.md index b9efba428..2095df587 100644 --- a/docs/docs/features/facial-recognition.md +++ b/docs/docs/features/facial-recognition.md @@ -1,5 +1,7 @@ # Facial Recognition +## Overview + Immich recognizes faces in your photos and videos and groups them together. You can then assign names to the faces and search for them. The list of people is shown in the Explore page. @@ -13,3 +15,16 @@ Upon clicking on a person, a list of assets that contain their face will be show The asset detail view will also show the faces that are recognized in the asset. + +## Actions + +Additional actions you can do with a detected person are: + +- Change the feature face photo of the person +- Set date of birth +- Merge two or more detected faces into one person +- Hide face + +It can be found from the app bar when you access the detial view of a person + + diff --git a/docs/docs/features/img/facial-recognition-4.png b/docs/docs/features/img/facial-recognition-4.png new file mode 100644 index 000000000..718ee6d45 Binary files /dev/null and b/docs/docs/features/img/facial-recognition-4.png differ diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 9a79ccf17..bb6333659 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -1,6 +1,6 @@ import MobileAppDownload from '../partials/_mobile-app-download.md'; import MobileAppLogin from '../partials/_mobile-app-login.md'; -import MobileAppBackup from '../partials/_mobile-app-login.md'; +import MobileAppBackup from '../partials/_mobile-app-backup.md'; # Mobile App diff --git a/docs/docs/guides/python-file-upload.md b/docs/docs/guides/python-file-upload.md new file mode 100644 index 000000000..dc1be79e0 --- /dev/null +++ b/docs/docs/guides/python-file-upload.md @@ -0,0 +1,42 @@ +# Python File Upload + +```python +#!/usr/bin/python3 + +import requests +import os +from datetime import datetime + +API_KEY = 'YOUR_API_KEY' # replace with a valid api key +BASE_URL = 'http://127.0.0.1:2283/api' # replace as needed + + +def upload(file): + stats = os.stat(file) + + headers = { + 'Accept': 'application/json', + 'x-api-key': API_KEY + } + + data = { + 'deviceAssetId': f'{file}-{stats.st_mtime}', + 'deviceId': 'python', + 'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime), + 'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime), + 'isFavorite': 'false', + } + + files = { + 'assetData': open(file, 'rb') + } + + response = requests.post( + f'{BASE_URL}/asset/upload', headers=headers, data=data, files=files) + + print(response.json()) + # {'id': 'ef96f635-61c7-4639-9e60-61a11c4bbfba', 'duplicate': False} + + +upload('./test.jpg') +``` diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index 195151a54..def99529c 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -17,6 +17,12 @@ The default configuration looks like this: "targetAudioCodec": "aac", "targetResolution": "720", "maxBitrate": "0", + "bframes": -1, + "refs": 0, + "gopSize": 0, + "npl": 0, + "temporalAQ": false, + "cqMode": "auto", "twoPass": false, "transcode": "required", "tonemap": "hable", @@ -44,9 +50,15 @@ The default configuration looks like this: "sidecar": { "concurrency": 5 }, + "library": { + "concurrency": 5 + }, "storageTemplateMigration": { "concurrency": 5 }, + "migration": { + "concurrency": 5 + }, "thumbnailGeneration": { "concurrency": 5 }, @@ -55,16 +67,16 @@ The default configuration looks like this: } }, "machineLearning": { - "classification": { - "minScore": 0.7, - "enabled": true, - "modelName": "microsoft/resnet-50" - }, "enabled": true, "url": "http://immich-machine-learning:3003", + "classification": { + "enabled": true, + "modelName": "microsoft/resnet-50", + "minScore": 0.9 + }, "clip": { "enabled": true, - "modelName": "ViT-B-32::openai" + "modelName": "ViT-B-32__openai" }, "facialRecognition": { "enabled": true, @@ -74,6 +86,14 @@ The default configuration looks like this: "minFaces": 1 } }, + "map": { + "enabled": true, + "tileUrl": "https://tile.openstreetmap.org/{z}/{x}/{y}.png" + }, + "reverseGeocoding": { + "enabled": true, + "citiesFileOverride": "cities500" + }, "oauth": { "enabled": false, "issuerUrl": "", @@ -96,8 +116,27 @@ The default configuration looks like this: "thumbnail": { "webpSize": 250, "jpegSize": 1440, - "quality": 90, + "quality": 80, "colorspace": "p3" + }, + "newVersionCheck": { + "enabled": true + }, + "trash": { + "enabled": true, + "days": 30 + }, + "theme": { + "customCss": "" + }, + "library": { + "scan": { + "enabled": true, + "cronExpression": "0 0 * * *" + } + }, + "stylesheets": { + "css": "" } } ``` diff --git a/docs/docs/partials/img/backup-header.png b/docs/docs/partials/img/backup-header.png index fbd133f32..629f13c8a 100644 Binary files a/docs/docs/partials/img/backup-header.png and b/docs/docs/partials/img/backup-header.png differ diff --git a/docs/docs/partials/img/sign-in-phone.jpeg b/docs/docs/partials/img/sign-in-phone.jpeg index 249216132..18fceed7d 100644 Binary files a/docs/docs/partials/img/sign-in-phone.jpeg and b/docs/docs/partials/img/sign-in-phone.jpeg differ diff --git a/docs/docs/partials/img/storage-template.png b/docs/docs/partials/img/storage-template.png index 1157a8642..5d829b862 100644 Binary files a/docs/docs/partials/img/storage-template.png and b/docs/docs/partials/img/storage-template.png differ diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 72159f51c..e2251924f 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -34,7 +34,7 @@ function HomepageHeader() { - logo + screenshots
diff --git a/docs/static/img/immich-screenshots.png b/docs/static/img/immich-screenshots.png index 7ad188e5a..c8569528d 100644 Binary files a/docs/static/img/immich-screenshots.png and b/docs/static/img/immich-screenshots.png differ diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 6fea1e5d5..3e58b6aa2 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "machine-learning" -version = "1.84.0" +version = "1.85.0" description = "" authors = ["Hau Tran "] readme = "README.md" diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index 281fa52d2..48bff6ba2 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 108, - "android.injected.version.name" => "1.84.0", + "android.injected.version.code" => 109, + "android.injected.version.name" => "1.85.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') diff --git a/mobile/android/fastlane/report.xml b/mobile/android/fastlane/report.xml index a0530d9aa..2add31ea0 100644 --- a/mobile/android/fastlane/report.xml +++ b/mobile/android/fastlane/report.xml @@ -5,17 +5,17 @@ - + - + - + diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 603eeac52..3cef6acb2 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -320,6 +320,7 @@ "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_password": "Password", + "shared_link_edit_expire_after": "Expire after", "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", @@ -377,5 +378,11 @@ "viewer_stack_use_as_main_asset": "Use as Main Asset", "app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", - "app_bar_signout_dialog_ok": "Yes" + "app_bar_signout_dialog_ok": "Yes", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_setting_title": "Comments & likes", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activities_input_disable": "Comment is disabled" } diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b15f480e2..3f83b4748 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -379,7 +379,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 124; + CURRENT_PROJECT_VERSION = 125; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -515,7 +515,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 124; + CURRENT_PROJECT_VERSION = 125; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -543,7 +543,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 124; + CURRENT_PROJECT_VERSION = 125; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index a6fddc98a..c1ceb76cb 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -59,11 +59,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.84.0 + 1.85.0 CFBundleSignature ???? CFBundleVersion - 124 + 125 FLTEnableImpeller ITSAppUsesNonExemptEncryption diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 19cefc12f..7cf4cd3bd 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Beta" lane :beta do increment_version_number( - version_number: "1.84.0" + version_number: "1.85.0" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/ios/fastlane/report.xml b/mobile/ios/fastlane/report.xml index c61f1d5d2..ab82b1ee4 100644 --- a/mobile/ios/fastlane/report.xml +++ b/mobile/ios/fastlane/report.xml @@ -5,32 +5,32 @@ - + - + - + - + - + - + diff --git a/mobile/lib/extensions/build_context_extensions.dart b/mobile/lib/extensions/build_context_extensions.dart new file mode 100644 index 000000000..b2e6ed472 --- /dev/null +++ b/mobile/lib/extensions/build_context_extensions.dart @@ -0,0 +1,54 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +extension ContextHelper on BuildContext { + // Returns the current size from MediaQuery + Size get size => MediaQuery.sizeOf(this); + + // Returns the current width from MediaQuery + double get width => size.width; + + // Returns the current height from MediaQuery + double get height => size.height; + + // Returns true if the app is running on a mobile device (!tablets) + bool get isMobile => width < 550; + + // Returns the current ThemeData + ThemeData get themeData => Theme.of(this); + + // Returns true if the app is using a dark theme + bool get isDarkTheme => themeData.brightness == Brightness.dark; + + // Returns the current Primary color of the Theme + Color get primaryColor => themeData.primaryColor; + + // Returns the Scaffold background color of the Theme + Color get scaffoldBackgroundColor => themeData.scaffoldBackgroundColor; + + // Returns the current TextTheme + TextTheme get textTheme => themeData.textTheme; + + // Current ColorScheme used + ColorScheme get colorScheme => themeData.colorScheme; + + // Pop-out from the current context with optional result + void pop([T? result]) => Navigator.of(this).pop(result); + + // Auto-Push new route from the current context + Future autoPush(PageRouteInfo route) => + AutoRouter.of(this).push(route); + + // Auto-Push navigate route from the current context + Future autoNavigate( + PageRouteInfo route, + ) => + AutoRouter.of(this).navigate(route); + +// Auto-Push replace route from the current context + Future autoReplace(PageRouteInfo route) => + AutoRouter.of(this).replace(route); + + // Auto-Pop from the current context + Future autoPop([T? result]) => AutoRouter.of(this).pop(result); +} diff --git a/mobile/lib/utils/builtin_extensions.dart b/mobile/lib/extensions/collection_extensions.dart similarity index 70% rename from mobile/lib/utils/builtin_extensions.dart rename to mobile/lib/extensions/collection_extensions.dart index 5b769f26f..283726ede 100644 --- a/mobile/lib/utils/builtin_extensions.dart +++ b/mobile/lib/extensions/collection_extensions.dart @@ -2,27 +2,6 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; -extension DurationExtension on String { - Duration? toDuration() { - try { - final parts = split(':') - .map((e) => double.parse(e).toInt()) - .toList(growable: false); - return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]); - } catch (e) { - return null; - } - } - - double toDouble() { - return double.parse(this); - } - - int toInt() { - return int.parse(this); - } -} - extension ListExtension on List { List uniqueConsecutive({ int Function(E a, E b)? compare, diff --git a/mobile/lib/extensions/datetime_extensions.dart b/mobile/lib/extensions/datetime_extensions.dart new file mode 100644 index 000000000..d91837771 --- /dev/null +++ b/mobile/lib/extensions/datetime_extensions.dart @@ -0,0 +1,36 @@ +extension TimeAgoExtension on DateTime { + String timeAgo({bool numericDates = true}) { + DateTime date = toLocal(); + final date2 = DateTime.now().toLocal(); + final difference = date2.difference(date); + + if (difference.inSeconds < 5) { + return 'Just now'; + } else if (difference.inSeconds < 60) { + return '${difference.inSeconds} seconds ago'; + } else if (difference.inMinutes <= 1) { + return (numericDates) ? '1 minute ago' : 'A minute ago'; + } else if (difference.inMinutes < 60) { + return '${difference.inMinutes} minutes ago'; + } else if (difference.inHours <= 1) { + return (numericDates) ? '1 hour ago' : 'An hour ago'; + } else if (difference.inHours < 60) { + return '${difference.inHours} hours ago'; + } else if (difference.inDays <= 1) { + return (numericDates) ? '1 day ago' : 'Yesterday'; + } else if (difference.inDays < 6) { + return '${difference.inDays} days ago'; + } else if ((difference.inDays / 7).ceil() <= 1) { + return (numericDates) ? '1 week ago' : 'Last week'; + } else if ((difference.inDays / 7).ceil() < 4) { + return '${(difference.inDays / 7).ceil()} weeks ago'; + } else if ((difference.inDays / 30).ceil() <= 1) { + return (numericDates) ? '1 month ago' : 'Last month'; + } else if ((difference.inDays / 30).ceil() < 30) { + return '${(difference.inDays / 30).ceil()} months ago'; + } else if ((difference.inDays / 365).ceil() <= 1) { + return (numericDates) ? '1 year ago' : 'Last year'; + } + return '${(difference.inDays / 365).floor()} years ago'; + } +} diff --git a/mobile/lib/utils/flutter_map_extensions.dart b/mobile/lib/extensions/flutter_map_extensions.dart similarity index 100% rename from mobile/lib/utils/flutter_map_extensions.dart rename to mobile/lib/extensions/flutter_map_extensions.dart diff --git a/mobile/lib/extensions/string_extensions.dart b/mobile/lib/extensions/string_extensions.dart new file mode 100644 index 000000000..a25ab4f50 --- /dev/null +++ b/mobile/lib/extensions/string_extensions.dart @@ -0,0 +1,30 @@ +extension StringExtension on String { + String capitalize() { + return split(" ") + .map( + (str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1), + ) + .join(" "); + } +} + +extension DurationExtension on String { + Duration? toDuration() { + try { + final parts = split(':') + .map((e) => double.parse(e).toInt()) + .toList(growable: false); + return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]); + } catch (e) { + return null; + } + } + + double toDouble() { + return double.parse(this); + } + + int toInt() { + return int.parse(this); + } +} diff --git a/mobile/lib/modules/activities/models/activity.model.dart b/mobile/lib/modules/activities/models/activity.model.dart new file mode 100644 index 000000000..417ba4a86 --- /dev/null +++ b/mobile/lib/modules/activities/models/activity.model.dart @@ -0,0 +1,90 @@ +import 'package:immich_mobile/shared/models/user.dart'; +import 'package:openapi/api.dart'; + +enum ActivityType { comment, like } + +class Activity { + final String id; + final String? assetId; + final String? comment; + final DateTime createdAt; + final ActivityType type; + final User user; + + const Activity({ + required this.id, + this.assetId, + this.comment, + required this.createdAt, + required this.type, + required this.user, + }); + + Activity copyWith({ + String? id, + String? assetId, + String? comment, + DateTime? createdAt, + ActivityType? type, + User? user, + }) { + return Activity( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + comment: comment ?? this.comment, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + user: user ?? this.user, + ); + } + + Activity.fromDto(ActivityResponseDto dto) + : id = dto.id, + assetId = dto.assetId, + comment = dto.comment, + createdAt = dto.createdAt, + type = dto.type == ActivityResponseDtoTypeEnum.comment + ? ActivityType.comment + : ActivityType.like, + user = User( + email: dto.user.email, + firstName: dto.user.firstName, + lastName: dto.user.lastName, + profileImagePath: dto.user.profileImagePath, + id: dto.user.id, + // Placeholder values + isAdmin: false, + updatedAt: DateTime.now(), + isPartnerSharedBy: false, + isPartnerSharedWith: false, + memoryEnabled: false, + ); + + @override + String toString() { + return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Activity && + other.id == id && + other.assetId == assetId && + other.comment == comment && + other.createdAt == createdAt && + other.type == type && + other.user == user; + } + + @override + int get hashCode { + return id.hashCode ^ + assetId.hashCode ^ + comment.hashCode ^ + createdAt.hashCode ^ + type.hashCode ^ + user.hashCode; + } +} diff --git a/mobile/lib/modules/activities/providers/activity.provider.dart b/mobile/lib/modules/activities/providers/activity.provider.dart new file mode 100644 index 000000000..c0fa5e628 --- /dev/null +++ b/mobile/lib/modules/activities/providers/activity.provider.dart @@ -0,0 +1,130 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/activities/models/activity.model.dart'; +import 'package:immich_mobile/modules/activities/services/activity.service.dart'; + +class ActivityNotifier extends StateNotifier>> { + final Ref _ref; + final ActivityService _activityService; + final String albumId; + final String? assetId; + + ActivityNotifier( + this._ref, + this._activityService, + this.albumId, + this.assetId, + ) : super( + const AsyncData([]), + ) { + fetchActivity(); + } + + Future fetchActivity() async { + state = const AsyncLoading(); + state = await AsyncValue.guard( + () => _activityService.getAllActivities(albumId, assetId), + ); + } + + Future removeActivity(String id) async { + final activities = state.asData?.value ?? []; + if (await _activityService.removeActivity(id)) { + final removedActivity = activities.firstWhere((a) => a.id == id); + activities.remove(removedActivity); + state = AsyncData(activities); + if (removedActivity.type == ActivityType.comment) { + _ref + .read( + activityStatisticsStateProvider( + (albumId: albumId, assetId: assetId), + ).notifier, + ) + .removeActivity(); + } + } + } + + Future addComment(String comment) async { + final activity = await _activityService.addActivity( + albumId, + ActivityType.comment, + assetId: assetId, + comment: comment, + ); + + if (activity != null) { + final activities = state.asData?.value ?? []; + state = AsyncData([...activities, activity]); + _ref + .read( + activityStatisticsStateProvider( + (albumId: albumId, assetId: assetId), + ).notifier, + ) + .addActivity(); + if (assetId != null) { + // Add a count to the current album's provider as well + _ref + .read( + activityStatisticsStateProvider( + (albumId: albumId, assetId: null), + ).notifier, + ) + .addActivity(); + } + } + } + + Future addLike() async { + final activity = await _activityService + .addActivity(albumId, ActivityType.like, assetId: assetId); + if (activity != null) { + final activities = state.asData?.value ?? []; + state = AsyncData([...activities, activity]); + } + } +} + +class ActivityStatisticsNotifier extends StateNotifier { + final String albumId; + final String? assetId; + final ActivityService _activityService; + ActivityStatisticsNotifier(this._activityService, this.albumId, this.assetId) + : super(0) { + fetchStatistics(); + } + + Future fetchStatistics() async { + state = await _activityService.getStatistics(albumId, assetId: assetId); + } + + Future addActivity() async { + state = state + 1; + } + + Future removeActivity() async { + state = state - 1; + } +} + +typedef ActivityParams = ({String albumId, String? assetId}); + +final activityStateProvider = StateNotifierProvider.autoDispose + .family>, ActivityParams>( + (ref, args) { + return ActivityNotifier( + ref, + ref.watch(activityServiceProvider), + args.albumId, + args.assetId, + ); +}); + +final activityStatisticsStateProvider = StateNotifierProvider.autoDispose + .family((ref, args) { + return ActivityStatisticsNotifier( + ref.watch(activityServiceProvider), + args.albumId, + args.assetId, + ); +}); diff --git a/mobile/lib/modules/activities/services/activity.service.dart b/mobile/lib/modules/activities/services/activity.service.dart new file mode 100644 index 000000000..fce77a196 --- /dev/null +++ b/mobile/lib/modules/activities/services/activity.service.dart @@ -0,0 +1,85 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/activities/models/activity.model.dart'; +import 'package:immich_mobile/shared/providers/api.provider.dart'; +import 'package:immich_mobile/shared/services/api.service.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; + +final activityServiceProvider = + Provider((ref) => ActivityService(ref.watch(apiServiceProvider))); + +class ActivityService { + final ApiService _apiService; + final Logger _log = Logger("ActivityService"); + + ActivityService(this._apiService); + + Future> getAllActivities( + String albumId, + String? assetId, + ) async { + try { + final list = await _apiService.activityApi + .getActivities(albumId, assetId: assetId); + return list != null ? list.map(Activity.fromDto).toList() : []; + } catch (e) { + _log.severe( + "failed to fetch activities for albumId - $albumId; assetId - $assetId -> $e", + ); + rethrow; + } + } + + Future getStatistics(String albumId, {String? assetId}) async { + try { + final dto = await _apiService.activityApi + .getActivityStatistics(albumId, assetId: assetId); + return dto?.comments ?? 0; + } catch (e) { + _log.severe( + "failed to fetch activity statistics for albumId - $albumId; assetId - $assetId -> $e", + ); + } + return 0; + } + + Future removeActivity(String id) async { + try { + await _apiService.activityApi.deleteActivity(id); + return true; + } catch (e) { + _log.severe( + "failed to remove activity id - $id -> $e", + ); + } + return false; + } + + Future addActivity( + String albumId, + ActivityType type, { + String? assetId, + String? comment, + }) async { + try { + final dto = await _apiService.activityApi.createActivity( + ActivityCreateDto( + albumId: albumId, + type: type == ActivityType.comment + ? ReactionType.comment + : ReactionType.like, + assetId: assetId, + comment: comment, + ), + ); + if (dto != null) { + return Activity.fromDto(dto); + } + } catch (e) { + _log.severe( + "failed to add activity for albumId - $albumId; assetId - $assetId -> $e", + ); + } + return null; + } +} diff --git a/mobile/lib/modules/activities/views/activities_page.dart b/mobile/lib/modules/activities/views/activities_page.dart new file mode 100644 index 000000000..b744fc476 --- /dev/null +++ b/mobile/lib/modules/activities/views/activities_page.dart @@ -0,0 +1,317 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/modules/activities/models/activity.model.dart'; +import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; +import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; +import 'package:immich_mobile/extensions/datetime_extensions.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +class ActivitiesPage extends HookConsumerWidget { + final String albumId; + final String? assetId; + final bool withAssetThumbs; + final String appBarTitle; + final bool isOwner; + final bool isReadOnly; + const ActivitiesPage( + this.albumId, { + this.appBarTitle = "", + this.assetId, + this.withAssetThumbs = true, + this.isOwner = false, + this.isReadOnly = false, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = + activityStateProvider((albumId: albumId, assetId: assetId)); + final activities = ref.watch(provider); + final inputController = useTextEditingController(); + final inputFocusNode = useFocusNode(); + final listViewScrollController = useScrollController(); + final currentUser = Store.tryGet(StoreKey.currentUser); + + useEffect( + () { + inputFocusNode.requestFocus(); + return null; + }, + [], + ); + + buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) { + final textColor = context.isDarkTheme ? Colors.white : Colors.black; + final textStyle = context.textTheme.bodyMedium + ?.copyWith(color: textColor.withOpacity(0.6)); + + return Row( + mainAxisAlignment: leftAlign + ? MainAxisAlignment.start + : MainAxisAlignment.spaceBetween, + mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max, + children: [ + Text( + "${activity.user.firstName} ${activity.user.lastName}", + style: textStyle, + overflow: TextOverflow.ellipsis, + ), + if (leftAlign) + Text( + " • ", + style: textStyle, + ), + Expanded( + child: Text( + activity.createdAt.copyWith().timeAgo(), + style: textStyle, + overflow: TextOverflow.ellipsis, + textAlign: leftAlign ? TextAlign.left : TextAlign.right, + ), + ), + ], + ); + } + + buildAssetThumbnail(Activity activity) { + return withAssetThumbs && activity.assetId != null + ? Container( + width: 40, + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + image: DecorationImage( + image: CachedNetworkImageProvider( + getThumbnailUrlForRemoteId( + activity.assetId!, + ), + cacheKey: getThumbnailCacheKeyForRemoteId( + activity.assetId!, + ), + headers: { + "Authorization": + 'Bearer ${Store.get(StoreKey.accessToken)}', + }, + ), + fit: BoxFit.cover, + ), + ), + child: const SizedBox.shrink(), + ) + : null; + } + + buildTextField(String? likedId) { + final liked = likedId != null; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: TextField( + controller: inputController, + enabled: !isReadOnly, + focusNode: inputFocusNode, + textInputAction: TextInputAction.send, + autofocus: false, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + prefixIcon: currentUser != null + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: UserCircleAvatar( + user: currentUser, + size: 30, + radius: 15, + ), + ) + : null, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 10), + child: IconButton( + icon: Icon( + liked + ? Icons.favorite_rounded + : Icons.favorite_border_rounded, + ), + onPressed: () async { + liked + ? await ref + .read(provider.notifier) + .removeActivity(likedId) + : await ref.read(provider.notifier).addLike(); + }, + ), + ), + suffixIconColor: liked ? Colors.red[700] : null, + hintText: isReadOnly + ? 'shared_album_activities_input_disable'.tr() + : 'shared_album_activities_input_hint'.tr(), + hintStyle: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 14, + color: Colors.grey[600], + ), + ), + onEditingComplete: () async { + await ref.read(provider.notifier).addComment(inputController.text); + inputController.clear(); + inputFocusNode.unfocus(); + listViewScrollController.animateTo( + listViewScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 800), + curve: Curves.fastOutSlowIn, + ); + }, + onTapOutside: (_) => inputFocusNode.unfocus(), + ), + ); + } + + getDismissibleWidget( + Widget widget, + Activity activity, + bool canDelete, + ) { + return Dismissible( + key: Key(activity.id), + dismissThresholds: const { + DismissDirection.horizontal: 0.7, + }, + direction: DismissDirection.horizontal, + confirmDismiss: (direction) => canDelete + ? showDialog( + context: context, + builder: (context) => ConfirmDialog( + onOk: () {}, + title: "shared_album_activity_remove_title", + content: "shared_album_activity_remove_content", + ok: "delete_dialog_ok", + ), + ) + : Future.value(false), + onDismissed: (direction) async => + await ref.read(provider.notifier).removeActivity(activity.id), + background: Container( + color: canDelete ? Colors.red[400] : Colors.grey[600], + alignment: AlignmentDirectional.centerStart, + child: canDelete + ? const Padding( + padding: EdgeInsets.all(15), + child: Icon( + Icons.delete_sweep_rounded, + color: Colors.black, + ), + ) + : null, + ), + secondaryBackground: Container( + color: canDelete ? Colors.red[400] : Colors.grey[600], + alignment: AlignmentDirectional.centerEnd, + child: canDelete + ? const Padding( + padding: EdgeInsets.all(15), + child: Icon( + Icons.delete_sweep_rounded, + color: Colors.black, + ), + ) + : null, + ), + child: widget, + ); + } + + return Scaffold( + appBar: AppBar(title: Text(appBarTitle)), + body: activities.maybeWhen( + orElse: () { + return const Center(child: ImmichLoadingIndicator()); + }, + data: (data) { + final liked = data.firstWhereOrNull( + (a) => + a.type == ActivityType.like && + a.user.id == currentUser?.id && + a.assetId == assetId, + ); + + return SafeArea( + child: Stack( + children: [ + ListView.builder( + controller: listViewScrollController, + itemCount: data.length + 1, + itemBuilder: (context, index) { + // Vertical gap after the last element + if (index == data.length) { + return const SizedBox( + height: 80, + ); + } + + final activity = data[index]; + final canDelete = + activity.user.id == currentUser?.id || isOwner; + + return Padding( + padding: const EdgeInsets.all(5), + child: activity.type == ActivityType.comment + ? getDismissibleWidget( + ListTile( + minVerticalPadding: 15, + leading: UserCircleAvatar(user: activity.user), + title: buildTitleWithTimestamp( + activity, + leftAlign: withAssetThumbs && + activity.assetId != null, + ), + titleAlignment: ListTileTitleAlignment.top, + trailing: buildAssetThumbnail(activity), + subtitle: Text(activity.comment!), + ), + activity, + canDelete, + ) + : getDismissibleWidget( + ListTile( + minVerticalPadding: 15, + leading: Container( + width: 44, + alignment: Alignment.center, + child: Icon( + Icons.favorite_rounded, + color: Colors.red[700], + ), + ), + title: buildTitleWithTimestamp(activity), + trailing: buildAssetThumbnail(activity), + ), + activity, + canDelete, + ), + ); + }, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: context.scaffoldBackgroundColor, + child: buildTextField(liked?.id), + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/mobile/lib/modules/album/providers/shared_album.provider.dart b/mobile/lib/modules/album/providers/shared_album.provider.dart index 4f36c4633..f8084da00 100644 --- a/mobile/lib/modules/album/providers/shared_album.provider.dart +++ b/mobile/lib/modules/album/providers/shared_album.provider.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/asset.dart'; @@ -10,7 +11,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:isar/isar.dart'; class SharedAlbumNotifier extends StateNotifier> { - SharedAlbumNotifier(this._albumService, Isar db) : super([]) { + SharedAlbumNotifier(this._albumService, Isar db, this._ref) : super([]) { final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc(); query.findAll().then((value) => state = value); _streamSub = query.watch().listen((data) => state = data); @@ -18,6 +19,7 @@ class SharedAlbumNotifier extends StateNotifier> { final AlbumService _albumService; late final StreamSubscription> _streamSub; + final Ref _ref; Future createSharedAlbum( String albumName, @@ -66,6 +68,17 @@ class SharedAlbumNotifier extends StateNotifier> { return result; } + Future setActivityEnabled(Album album, bool activityEnabled) async { + final result = + await _albumService.setActivityEnabled(album, activityEnabled); + + if (result) { + _ref.invalidate(albumDetailProvider(album.id)); + } + + return result; + } + @override void dispose() { _streamSub.cancel(); @@ -78,5 +91,6 @@ final sharedAlbumProvider = return SharedAlbumNotifier( ref.watch(albumServiceProvider), ref.watch(dbProvider), + ref, ); }); diff --git a/mobile/lib/modules/album/services/album.service.dart b/mobile/lib/modules/album/services/album.service.dart index 4488eca23..fdb07f563 100644 --- a/mobile/lib/modules/album/services/album.service.dart +++ b/mobile/lib/modules/album/services/album.service.dart @@ -284,6 +284,23 @@ class AlbumService { return false; } + Future setActivityEnabled(Album album, bool enabled) async { + try { + final result = await _apiService.albumApi.updateAlbumInfo( + album.remoteId!, + UpdateAlbumDto(isActivityEnabled: enabled), + ); + if (result != null) { + album.activityEnabled = enabled; + await _db.writeTxn(() => _db.albums.put(album)); + return true; + } + } catch (e) { + debugPrint("Error setActivityEnabled ${e.toString()}"); + } + return false; + } + Future deleteAlbum(Album album) async { try { final userId = Store.get(StoreKey.currentUser).isarId; diff --git a/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart b/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart index 257dbdbaa..650d4da0d 100644 --- a/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart +++ b/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; -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/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; @@ -95,20 +95,19 @@ class AddToAlbumBottomSheet extends HookConsumerWidget { children: [ Text( 'common_add_to_album'.tr(), - style: Theme.of(context).textTheme.displayMedium, + style: context.textTheme.displayMedium, ), TextButton.icon( icon: Icon( Icons.add, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), label: Text( 'common_create_new_album'.tr(), - style: - TextStyle(color: Theme.of(context).primaryColor), + style: TextStyle(color: context.primaryColor), ), onPressed: () { - AutoRouter.of(context).push( + context.autoPush( CreateAlbumRoute( isSharedAlbum: false, initialAssets: assets, diff --git a/mobile/lib/modules/album/ui/album_action_outlined_button.dart b/mobile/lib/modules/album/ui/album_action_outlined_button.dart index 928a50794..eebaefc41 100644 --- a/mobile/lib/modules/album/ui/album_action_outlined_button.dart +++ b/mobile/lib/modules/album/ui/album_action_outlined_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class AlbumActionOutlinedButton extends StatelessWidget { final VoidCallback? onPressed; @@ -14,8 +15,6 @@ class AlbumActionOutlinedButton extends StatelessWidget { @override Widget build(BuildContext context) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; - return Padding( padding: const EdgeInsets.only(right: 8.0), child: OutlinedButton.icon( @@ -26,7 +25,7 @@ class AlbumActionOutlinedButton extends StatelessWidget { ), side: BorderSide( width: 1, - color: isDarkTheme + color: context.isDarkTheme ? const Color.fromARGB(255, 63, 63, 63) : const Color.fromARGB(255, 206, 206, 206), ), @@ -34,13 +33,13 @@ class AlbumActionOutlinedButton extends StatelessWidget { icon: Icon( iconData, size: 15, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), label: Text( labelText, - style: Theme.of(context).textTheme.labelSmall?.copyWith( - fontWeight: FontWeight.bold, - ), + style: context.textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + ), ), onPressed: onPressed, ), diff --git a/mobile/lib/modules/album/ui/album_thumbnail_card.dart b/mobile/lib/modules/album/ui/album_thumbnail_card.dart index f37751e3e..7d5e42b1c 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_card.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_card.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; @@ -22,7 +23,8 @@ class AlbumThumbnailCard extends StatelessWidget { @override Widget build(BuildContext context) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; + var isDarkTheme = context.isDarkTheme; + return LayoutBuilder( builder: (context, constraints) { var cardSize = constraints.maxWidth; @@ -32,7 +34,7 @@ class AlbumThumbnailCard extends StatelessWidget { height: cardSize, width: cardSize, decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[800] : Colors.grey[200], + color: isDarkTheme ? Colors.grey[800] : Colors.grey[200], ), child: Center( child: Icon( @@ -73,14 +75,14 @@ class AlbumThumbnailCard extends StatelessWidget { style: TextStyle( fontFamily: 'WorkSans', fontSize: 12, - color: isDarkMode ? Colors.white : Colors.black, + color: isDarkTheme ? Colors.white : Colors.black, ), ), if (owner != null) const TextSpan(text: ' · '), if (owner != null) TextSpan( text: owner, - style: Theme.of(context).textTheme.labelSmall, + style: context.textTheme.labelSmall, ), ], ), @@ -114,8 +116,8 @@ class AlbumThumbnailCard extends StatelessWidget { album.name, style: TextStyle( fontWeight: FontWeight.bold, - color: isDarkMode - ? Theme.of(context).primaryColor + color: isDarkTheme + ? context.primaryColor : Colors.black, ), ), diff --git a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart index c9237ea27..38208e88c 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/store.dart'; @@ -21,12 +21,11 @@ class AlbumThumbnailListTile extends StatelessWidget { @override Widget build(BuildContext context) { var cardSize = 68.0; - var isDarkMode = Theme.of(context).brightness == Brightness.dark; buildEmptyThumbnail() { return Container( decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[800] : Colors.grey[200], + color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[200], ), child: SizedBox( height: cardSize, @@ -61,7 +60,7 @@ class AlbumThumbnailListTile extends StatelessWidget { behavior: HitTestBehavior.opaque, onTap: onTap ?? () { - AutoRouter.of(context).push(AlbumViewerRoute(albumId: album.id)); + context.autoPush(AlbumViewerRoute(albumId: album.id)); }, child: Padding( padding: const EdgeInsets.only(bottom: 12.0), diff --git a/mobile/lib/modules/album/ui/album_title_text_field.dart b/mobile/lib/modules/album/ui/album_title_text_field.dart index 38c1f681a..57b82a80d 100644 --- a/mobile/lib/modules/album/ui/album_title_text_field.dart +++ b/mobile/lib/modules/album/ui/album_title_text_field.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; class AlbumTitleTextField extends ConsumerWidget { @@ -19,7 +20,7 @@ class AlbumTitleTextField extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; + final isDarkTheme = context.isDarkTheme; return TextField( onChanged: (v) { @@ -55,7 +56,7 @@ class AlbumTitleTextField extends ConsumerWidget { }, icon: Icon( Icons.cancel_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), splashRadius: 10, ) diff --git a/mobile/lib/modules/album/ui/album_viewer_appbar.dart b/mobile/lib/modules/album/ui/album_viewer_appbar.dart index 221603ed9..1ac64e957 100644 --- a/mobile/lib/modules/album/ui/album_viewer_appbar.dart +++ b/mobile/lib/modules/album/ui/album_viewer_appbar.dart @@ -1,8 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart'; @@ -26,6 +27,7 @@ class AlbumViewerAppbar extends HookConsumerWidget required this.titleFocusNode, this.onAddPhotos, this.onAddUsers, + required this.onActivities, }) : super(key: key); final Album album; @@ -35,11 +37,19 @@ class AlbumViewerAppbar extends HookConsumerWidget final FocusNode titleFocusNode; final Function(Album album)? onAddPhotos; final Function(Album album)? onAddUsers; + final Function(Album album) onActivities; @override Widget build(BuildContext context, WidgetRef ref) { final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; + final comments = album.shared + ? ref.watch( + activityStatisticsStateProvider( + (albumId: album.remoteId!, assetId: null), + ), + ) + : 0; deleteAlbum() async { ImmichLoadingOverlayController.appLoader.show(); @@ -48,12 +58,12 @@ class AlbumViewerAppbar extends HookConsumerWidget if (album.shared) { success = await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(album); - AutoRouter.of(context) - .navigate(const TabControllerRoute(children: [SharingRoute()])); + context + .autoNavigate(const TabControllerRoute(children: [SharingRoute()])); } else { success = await ref.watch(albumProvider.notifier).deleteAlbum(album); - AutoRouter.of(context) - .navigate(const TabControllerRoute(children: [LibraryRoute()])); + context + .autoNavigate(const TabControllerRoute(children: [LibraryRoute()])); } if (!success) { ImmichToast.show( @@ -83,7 +93,7 @@ class AlbumViewerAppbar extends HookConsumerWidget child: Text( 'Cancel', style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), @@ -97,9 +107,7 @@ class AlbumViewerAppbar extends HookConsumerWidget 'Confirm', style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).brightness == Brightness.light - ? Colors.red - : Colors.red[300], + color: !context.isDarkTheme ? Colors.red : Colors.red[300], ), ), ), @@ -120,8 +128,8 @@ class AlbumViewerAppbar extends HookConsumerWidget await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album); if (isSuccess) { - AutoRouter.of(context) - .navigate(const TabControllerRoute(children: [SharingRoute()])); + context + .autoNavigate(const TabControllerRoute(children: [SharingRoute()])); } else { Navigator.pop(context); ImmichToast.show( @@ -180,7 +188,7 @@ class AlbumViewerAppbar extends HookConsumerWidget gravity: ToastGravity.BOTTOM, ); } - Navigator.of(buildContext).pop(); + context.pop(); }, ); return const ShareDialog(); @@ -206,32 +214,36 @@ class AlbumViewerAppbar extends HookConsumerWidget ).tr(), onTap: () => onShareAssetsTo(), ), - album.ownerId == userId ? ListTile( - leading: const Icon(Icons.delete_sweep_rounded), - title: const Text( - 'album_viewer_appbar_share_remove', - style: TextStyle(fontWeight: FontWeight.bold), - ).tr(), - onTap: () => onRemoveFromAlbumPressed(), - ) : const SizedBox(), + album.ownerId == userId + ? ListTile( + leading: const Icon(Icons.delete_sweep_rounded), + title: const Text( + 'album_viewer_appbar_share_remove', + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + onTap: () => onRemoveFromAlbumPressed(), + ) + : const SizedBox(), ]; } else { return [ - album.ownerId == userId ? ListTile( - leading: const Icon(Icons.delete_forever_rounded), - title: const Text( - 'album_viewer_appbar_share_delete', - style: TextStyle(fontWeight: FontWeight.bold), - ).tr(), - onTap: () => onDeleteAlbumPressed(), - ) : ListTile( - leading: const Icon(Icons.person_remove_rounded), - title: const Text( - 'album_viewer_appbar_share_leave', - style: TextStyle(fontWeight: FontWeight.bold), - ).tr(), - onTap: () => onLeaveAlbumPressed(), - ), + album.ownerId == userId + ? ListTile( + leading: const Icon(Icons.delete_forever_rounded), + title: const Text( + 'album_viewer_appbar_share_delete', + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + onTap: () => onDeleteAlbumPressed(), + ) + : ListTile( + leading: const Icon(Icons.person_remove_rounded), + title: const Text( + 'album_viewer_appbar_share_leave', + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + onTap: () => onLeaveAlbumPressed(), + ), ]; } } @@ -252,8 +264,7 @@ class AlbumViewerAppbar extends HookConsumerWidget ListTile( leading: const Icon(Icons.share_rounded), onTap: () { - AutoRouter.of(context) - .push(SharedLinkEditRoute(albumId: album.remoteId)); + context.autoPush(SharedLinkEditRoute(albumId: album.remoteId)); Navigator.pop(context); }, title: const Text( @@ -263,8 +274,7 @@ class AlbumViewerAppbar extends HookConsumerWidget ), ListTile( leading: const Icon(Icons.settings_rounded), - onTap: () => - AutoRouter.of(context).navigate(AlbumOptionsRoute(album: album)), + onTap: () => context.autoNavigate(AlbumOptionsRoute(album: album)), title: const Text( "translated_text_options", style: TextStyle(fontWeight: FontWeight.bold), @@ -286,7 +296,7 @@ class AlbumViewerAppbar extends HookConsumerWidget ), ]; showModalBottomSheet( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: context.scaffoldBackgroundColor, isScrollControlled: false, context: context, builder: (context) { @@ -310,6 +320,33 @@ class AlbumViewerAppbar extends HookConsumerWidget ); } + Widget buildActivitiesButton() { + return IconButton( + onPressed: () { + onActivities(album); + }, + icon: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.mode_comment_outlined, + ), + if (comments != 0) + Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + comments.toString(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.primaryColor, + ), + ), + ), + ], + ), + ); + } + buildLeadingButton() { if (selected.isNotEmpty) { return IconButton( @@ -340,7 +377,7 @@ class AlbumViewerAppbar extends HookConsumerWidget ); } else { return IconButton( - onPressed: () async => await AutoRouter.of(context).pop(), + onPressed: () async => await context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), splashRadius: 25, ); @@ -353,6 +390,8 @@ class AlbumViewerAppbar extends HookConsumerWidget title: selected.isNotEmpty ? Text('${selected.length}') : null, centerTitle: false, actions: [ + if (album.shared && (album.activityEnabled || comments != 0)) + buildActivitiesButton(), if (album.isRemote) IconButton( splashRadius: 25, diff --git a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart index 8a7e46f8c..b73748f4a 100644 --- a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart +++ b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart'; import 'package:immich_mobile/shared/models/album.dart'; @@ -17,7 +18,6 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final titleTextEditController = useTextEditingController(text: album.name); - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; void onFocusModeChange() { if (!titleFocusNode.hasFocus && titleTextEditController.text.isEmpty) { @@ -65,7 +65,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { }, icon: Icon( Icons.cancel_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), splashRadius: 10, ) @@ -79,14 +79,14 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { borderRadius: BorderRadius.circular(10), ), focusColor: Colors.grey[300], - fillColor: isDarkTheme + fillColor: context.isDarkTheme ? const Color.fromARGB(255, 32, 33, 35) : Colors.grey[200], filled: titleFocusNode.hasFocus, hintText: 'share_add_title'.tr(), hintStyle: TextStyle( fontSize: 28, - color: isDarkTheme ? Colors.grey[300] : Colors.grey[700], + color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700], fontWeight: FontWeight.bold, ), ), diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart index eb08b6bda..88f0d7d3c 100644 --- a/mobile/lib/modules/album/views/album_options_part.dart +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -1,9 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -23,6 +23,7 @@ class AlbumOptionsPage extends HookConsumerWidget { final sharedUsers = useState(album.sharedUsers.toList()); final owner = album.owner.value; final userId = ref.watch(authenticationProvider).userId; + final activityEnabled = useState(album.activityEnabled); final isOwner = owner?.id == userId; void showErrorMessage() { @@ -43,8 +44,9 @@ class AlbumOptionsPage extends HookConsumerWidget { await ref.read(sharedAlbumProvider.notifier).leaveAlbum(album); if (isSuccess) { - AutoRouter.of(context) - .navigate(const TabControllerRoute(children: [SharingRoute()])); + context.autoNavigate( + const TabControllerRoute(children: [SharingRoute()]), + ); } else { showErrorMessage(); } @@ -96,7 +98,7 @@ class AlbumOptionsPage extends HookConsumerWidget { } showModalBottomSheet( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: context.scaffoldBackgroundColor, isScrollControlled: false, context: context, builder: (context) { @@ -176,7 +178,7 @@ class AlbumOptionsPage extends HookConsumerWidget { buildSectionTitle(String text) { return Padding( padding: const EdgeInsets.all(16.0), - child: Text(text, style: Theme.of(context).textTheme.bodySmall), + child: Text(text, style: context.textTheme.bodySmall), ); } @@ -185,7 +187,7 @@ class AlbumOptionsPage extends HookConsumerWidget { leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new_rounded), onPressed: () { - AutoRouter.of(context).pop(null); + context.autoPop(null); }, ), centerTitle: true, @@ -195,6 +197,29 @@ class AlbumOptionsPage extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (isOwner && album.shared) + SwitchListTile.adaptive( + value: activityEnabled.value, + onChanged: (bool value) async { + activityEnabled.value = value; + if (await ref + .read(sharedAlbumProvider.notifier) + .setActivityEnabled(album, value)) { + album.activityEnabled = value; + } + }, + activeColor: activityEnabled.value + ? context.primaryColor + : context.themeData.disabledColor, + dense: true, + title: Text( + "shared_album_activity_setting_title", + style: context.textTheme.labelLarge + ?.copyWith(fontWeight: FontWeight.bold), + ).tr(), + subtitle: + const Text("shared_album_activity_setting_subtitle").tr(), + ), buildSectionTitle("PEOPLE"), buildOwnerInfo(), buildSharedUsersList(), diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index 12ef332f4..b12336328 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; @@ -67,7 +67,7 @@ class AlbumViewerPage extends HookConsumerWidget { /// If they exist, add to selected asset state to show they are already selected. void onAddPhotosPressed(Album albumInfo) async { AssetSelectionPageResult? returnPayload = - await AutoRouter.of(context).push( + await context.autoPush( AssetSelectionRoute( existingAssets: albumInfo.assets, canDeselect: false, @@ -97,8 +97,7 @@ class AlbumViewerPage extends HookConsumerWidget { } void onAddUsersPressed(Album album) async { - List? sharedUserIds = - await AutoRouter.of(context).push?>( + List? sharedUserIds = await context.autoPush?>( SelectAdditionalUserForSharingRoute(album: album), ); @@ -171,11 +170,19 @@ class AlbumViewerPage extends HookConsumerWidget { return const SizedBox(); } - final String startDateText = (startDate.year == endDate.year - ? DateFormat.MMMd() - : DateFormat.yMMMd()) - .format(startDate); - final String endDateText = DateFormat.yMMMd().format(endDate); + final String dateRangeText; + if (startDate.day == endDate.day && + startDate.month == endDate.month && + startDate.year == endDate.year) { + dateRangeText = DateFormat.yMMMd().format(startDate); + } else { + final String startDateText = (startDate.year == endDate.year + ? DateFormat.MMMd() + : DateFormat.yMMMd()) + .format(startDate); + final String endDateText = DateFormat.yMMMd().format(endDate); + dateRangeText = "$startDateText - $endDateText"; + } return Padding( padding: EdgeInsets.only( @@ -183,7 +190,7 @@ class AlbumViewerPage extends HookConsumerWidget { bottom: album.shared ? 0.0 : 8.0, ), child: Text( - "$startDateText - $endDateText", + dateRangeText, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -195,7 +202,7 @@ class AlbumViewerPage extends HookConsumerWidget { Widget buildSharedUserIconsRow(Album album) { return GestureDetector( onTap: () async { - await AutoRouter.of(context).push(AlbumOptionsRoute(album: album)); + await context.autoPush(AlbumOptionsRoute(album: album)); ref.invalidate(albumDetailProvider(album.id)); }, child: SizedBox( @@ -232,6 +239,19 @@ class AlbumViewerPage extends HookConsumerWidget { ); } + onActivitiesPressed(Album album) { + if (album.remoteId != null) { + context.autoPush( + ActivitiesRoute( + albumId: album.remoteId!, + appBarTitle: album.name, + isOwner: userId == album.ownerId, + isReadOnly: !album.activityEnabled, + ), + ); + } + } + return Scaffold( appBar: album.when( data: (data) => AlbumViewerAppbar( @@ -242,6 +262,7 @@ class AlbumViewerPage extends HookConsumerWidget { selectionDisabled: disableSelection, onAddPhotos: onAddPhotosPressed, onAddUsers: onAddUsersPressed, + onActivities: onActivitiesPressed, ), error: (error, stackTrace) => AppBar(title: const Text("Error")), loading: () => AppBar(), @@ -266,6 +287,8 @@ class AlbumViewerPage extends HookConsumerWidget { ], ), isOwner: userId == data.ownerId, + sharedAlbumId: + data.shared && data.activityEnabled ? data.remoteId : null, ), ), ), diff --git a/mobile/lib/modules/album/views/asset_selection_page.dart b/mobile/lib/modules/album/views/asset_selection_page.dart index 9c30ba80f..c1870fe44 100644 --- a/mobile/lib/modules/album/views/asset_selection_page.dart +++ b/mobile/lib/modules/album/views/asset_selection_page.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; @@ -78,7 +79,7 @@ class AssetSelectionPage extends HookConsumerWidget { canDeselect ? "share_done" : "share_add", style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ).tr(), ), diff --git a/mobile/lib/modules/album/views/create_album_page.dart b/mobile/lib/modules/album/views/create_album_page.dart index 191ce1470..8f2c4e128 100644 --- a/mobile/lib/modules/album/views/create_album_page.dart +++ b/mobile/lib/modules/album/views/create_album_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; @@ -34,11 +34,11 @@ class CreateAlbumPage extends HookConsumerWidget { final selectedAssets = useState>( initialAssets != null ? Set.from(initialAssets!) : const {}, ); - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; showSelectUserPage() async { - final bool? ok = await AutoRouter.of(context) - .push(SelectUserForSharingRoute(assets: selectedAssets.value)); + final bool? ok = await context.autoPush( + SelectUserForSharingRoute(assets: selectedAssets.value), + ); if (ok == true) { selectedAssets.value = {}; } @@ -58,7 +58,7 @@ class CreateAlbumPage extends HookConsumerWidget { onSelectPhotosButtonPressed() async { AssetSelectionPageResult? selectedAsset = - await AutoRouter.of(context).push( + await context.autoPush( AssetSelectionRoute( existingAssets: selectedAssets.value, canDeselect: true, @@ -94,10 +94,10 @@ class CreateAlbumPage extends HookConsumerWidget { padding: const EdgeInsets.only(top: 200, left: 18), child: Text( 'create_shared_album_page_share_add_assets', - style: Theme.of(context).textTheme.displayMedium?.copyWith( - fontSize: 12, - fontWeight: FontWeight.normal, - ), + style: context.textTheme.displayMedium?.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal, + ), ).tr(), ), ); @@ -117,7 +117,7 @@ class CreateAlbumPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(vertical: 22, horizontal: 16), side: BorderSide( - color: isDarkTheme + color: context.isDarkTheme ? const Color.fromARGB(255, 63, 63, 63) : const Color.fromARGB(255, 206, 206, 206), ), @@ -128,16 +128,16 @@ class CreateAlbumPage extends HookConsumerWidget { onPressed: onSelectPhotosButtonPressed, icon: Icon( Icons.add_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), label: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( 'create_shared_album_page_share_select_photos', - style: Theme.of(context).textTheme.labelLarge?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: context.textTheme.labelLarge?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ).tr(), ), ), @@ -206,7 +206,7 @@ class CreateAlbumPage extends HookConsumerWidget { selectedAssets.value = {}; ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); - AutoRouter.of(context).replace(AlbumViewerRoute(albumId: newAlbum.id)); + context.autoReplace(AlbumViewerRoute(albumId: newAlbum.id)); } } @@ -214,19 +214,19 @@ class CreateAlbumPage extends HookConsumerWidget { appBar: AppBar( elevation: 0, centerTitle: false, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: context.scaffoldBackgroundColor, leading: IconButton( onPressed: () { selectedAssets.value = {}; - AutoRouter.of(context).pop(); + context.autoPop(); }, icon: const Icon(Icons.close_rounded), ), title: Text( 'share_create_album', - style: Theme.of(context).textTheme.displayMedium?.copyWith( - color: Theme.of(context).primaryColor, - ), + style: context.textTheme.displayMedium?.copyWith( + color: context.primaryColor, + ), ).tr(), actions: [ if (isSharedAlbum) @@ -239,8 +239,8 @@ class CreateAlbumPage extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.bold, color: albumTitleController.text.isEmpty - ? Theme.of(context).disabledColor - : Theme.of(context).primaryColor, + ? context.themeData.disabledColor + : context.primaryColor, ), ), ), @@ -254,7 +254,7 @@ class CreateAlbumPage extends HookConsumerWidget { 'create_shared_album_page_create'.tr(), style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), @@ -265,7 +265,7 @@ class CreateAlbumPage extends HookConsumerWidget { child: CustomScrollView( slivers: [ SliverAppBar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: context.scaffoldBackgroundColor, elevation: 5, automaticallyImplyLeading: false, pinned: true, diff --git a/mobile/lib/modules/album/views/library_page.dart b/mobile/lib/modules/album/views/library_page.dart index 90d095640..15229232b 100644 --- a/mobile/lib/modules/album/views/library_page.dart +++ b/mobile/lib/modules/album/views/library_page.dart @@ -1,9 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -21,7 +21,7 @@ class LibraryPage extends HookConsumerWidget { final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); final albums = ref.watch(albumProvider); - var isDarkMode = Theme.of(context).brightness == Brightness.dark; + var isDarkTheme = context.isDarkTheme; var settings = ref.watch(appSettingsServiceProvider); useEffect( @@ -96,15 +96,14 @@ class LibraryPage extends HookConsumerWidget { padding: const EdgeInsets.only(right: 12.0), child: Icon( Icons.check, - color: selected - ? Theme.of(context).primaryColor - : Colors.transparent, + color: + selected ? context.primaryColor : Colors.transparent, ), ), Text( option, style: TextStyle( - color: selected ? Theme.of(context).primaryColor : null, + color: selected ? context.primaryColor : null, fontSize: 12.0, ), ), @@ -122,13 +121,13 @@ class LibraryPage extends HookConsumerWidget { Icon( Icons.swap_vert_rounded, size: 18, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), Text( options[selectedAlbumSortOrder.value], style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontSize: 12.0, ), ), @@ -140,7 +139,7 @@ class LibraryPage extends HookConsumerWidget { Widget buildCreateAlbumButton() { return GestureDetector( onTap: () { - AutoRouter.of(context).push(CreateAlbumRoute(isSharedAlbum: false)); + context.autoPush(CreateAlbumRoute(isSharedAlbum: false)); }, child: Padding( padding: const EdgeInsets.only(bottom: 32), @@ -152,18 +151,18 @@ class LibraryPage extends HookConsumerWidget { child: Container( decoration: BoxDecoration( border: Border.all( - color: isDarkMode + color: isDarkTheme ? const Color.fromARGB(255, 53, 53, 53) : const Color.fromARGB(255, 203, 203, 203), ), - color: isDarkMode ? Colors.grey[900] : Colors.grey[50], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[50], borderRadius: BorderRadius.circular(20), ), child: Center( child: Icon( Icons.add_rounded, size: 28, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), @@ -201,21 +200,21 @@ class LibraryPage extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 13.0, - color: isDarkMode ? Colors.white : Colors.grey[800], + color: isDarkTheme ? Colors.white : Colors.grey[800], ), ), ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50], + backgroundColor: isDarkTheme ? Colors.grey[900] : Colors.grey[50], side: BorderSide( - color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!, + color: isDarkTheme ? Colors.grey[800]! : Colors.grey[300]!, ), alignment: Alignment.centerLeft, ), icon: Icon( icon, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ); @@ -228,7 +227,7 @@ class LibraryPage extends HookConsumerWidget { Widget? shareTrashButton() { return trashEnabled ? InkWell( - onTap: () => AutoRouter.of(context).push(const TrashRoute()), + onTap: () => context.autoPush(const TrashRoute()), borderRadius: BorderRadius.circular(12), child: const Icon( Icons.delete_rounded, @@ -257,12 +256,12 @@ class LibraryPage extends HookConsumerWidget { children: [ buildLibraryNavButton( "library_page_favorites".tr(), Icons.favorite_border, () { - AutoRouter.of(context).navigate(const FavoritesRoute()); + context.autoNavigate(const FavoritesRoute()); }), const SizedBox(width: 12.0), buildLibraryNavButton( "library_page_archive".tr(), Icons.archive_outlined, () { - AutoRouter.of(context).navigate(const ArchiveRoute()); + context.autoNavigate(const ArchiveRoute()); }), ], ), @@ -306,7 +305,7 @@ class LibraryPage extends HookConsumerWidget { return AlbumThumbnailCard( album: sorted[index - 1], - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( AlbumViewerRoute( albumId: sorted[index - 1].id, ), @@ -348,7 +347,7 @@ class LibraryPage extends HookConsumerWidget { childCount: local.length, (context, index) => AlbumThumbnailCard( album: local[index], - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( AlbumViewerRoute( albumId: local[index].id, ), diff --git a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart index e13637d23..b91197d28 100644 --- a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/user.dart'; @@ -22,14 +22,13 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { final sharedUsersList = useState>({}); addNewUsersHandler() { - AutoRouter.of(context) - .pop(sharedUsersList.value.map((e) => e.id).toList()); + context.autoPop(sharedUsersList.value.map((e) => e.id).toList()); } buildTileIcon(User user) { if (sharedUsersList.value.contains(user)) { return CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: context.primaryColor, child: const Icon( Icons.check_rounded, size: 25, @@ -50,7 +49,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Chip( - backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15), + backgroundColor: context.primaryColor.withOpacity(0.15), label: Text( user.email, style: const TextStyle( @@ -124,7 +123,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () { - AutoRouter.of(context).pop(null); + context.autoPop(null); }, ), actions: [ diff --git a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart index fe6cef401..61ced47e2 100644 --- a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; @@ -35,9 +35,9 @@ class SelectUserForSharingPage extends HookConsumerWidget { await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums(); // ref.watch(assetSelectionProvider.notifier).removeAll(); ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); - AutoRouter.of(context).pop(true); - AutoRouter.of(context) - .navigate(const TabControllerRoute(children: [SharingRoute()])); + context.autoPop(true); + context + .autoNavigate(const TabControllerRoute(children: [SharingRoute()])); } ScaffoldMessenger( @@ -50,7 +50,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { buildTileIcon(User user) { if (sharedUsersList.value.contains(user)) { return CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: context.primaryColor, child: const Icon( Icons.check_rounded, size: 25, @@ -71,7 +71,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Chip( - backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15), + backgroundColor: context.primaryColor.withOpacity(0.15), label: Text( user.email, style: const TextStyle( @@ -139,20 +139,20 @@ class SelectUserForSharingPage extends HookConsumerWidget { appBar: AppBar( title: Text( 'share_invite', - style: TextStyle(color: Theme.of(context).primaryColor), + style: TextStyle(color: context.primaryColor), ).tr(), elevation: 0, centerTitle: false, leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () async { - AutoRouter.of(context).pop(); + context.autoPop(); }, ), actions: [ TextButton( style: TextButton.styleFrom( - foregroundColor: Theme.of(context).primaryColor, + foregroundColor: context.primaryColor, ), onPressed: sharedUsersList.value.isEmpty ? null : createSharedAlbum, child: const Text( @@ -160,7 +160,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - // color: Theme.of(context).primaryColor, + // color: context.primaryColor, ), ).tr(), ), diff --git a/mobile/lib/modules/album/views/sharing_page.dart b/mobile/lib/modules/album/views/sharing_page.dart index 5e74aef67..3061289a9 100644 --- a/mobile/lib/modules/album/views/sharing_page.dart +++ b/mobile/lib/modules/album/views/sharing_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; @@ -21,7 +21,6 @@ class SharingPage extends HookConsumerWidget { final List sharedAlbums = ref.watch(sharedAlbumProvider); final userId = ref.watch(currentUserProvider)?.id; final partner = ref.watch(partnerSharedWithProvider); - var isDarkMode = Theme.of(context).brightness == Brightness.dark; useEffect( () { @@ -47,8 +46,9 @@ class SharingPage extends HookConsumerWidget { album: sharedAlbums[index], showOwner: true, onTap: () { - AutoRouter.of(context) - .push(AlbumViewerRoute(albumId: sharedAlbums[index].id)); + context.autoPush( + AlbumViewerRoute(albumId: sharedAlbums[index].id), + ); }, ); }, @@ -79,12 +79,11 @@ class SharingPage extends HookConsumerWidget { album.name, maxLines: 1, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - color: isDarkMode - ? Theme.of(context).primaryColor - : Colors.black, - ), + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + color: + context.isDarkTheme ? context.primaryColor : Colors.black, + ), ), subtitle: isOwner ? Text( @@ -103,8 +102,9 @@ class SharingPage extends HookConsumerWidget { ) : null, onTap: () { - AutoRouter.of(context) - .push(AlbumViewerRoute(albumId: sharedAlbums[index].id)); + context.autoPush( + AlbumViewerRoute(albumId: sharedAlbums[index].id), + ); }, ); }, @@ -127,8 +127,7 @@ class SharingPage extends HookConsumerWidget { Expanded( child: ElevatedButton.icon( onPressed: () { - AutoRouter.of(context) - .push(CreateAlbumRoute(isSharedAlbum: true)); + context.autoPush(CreateAlbumRoute(isSharedAlbum: true)); }, icon: const Icon( Icons.photo_album_outlined, @@ -147,8 +146,7 @@ class SharingPage extends HookConsumerWidget { const SizedBox(width: 12.0), Expanded( child: ElevatedButton.icon( - onPressed: () => - AutoRouter.of(context).push(const SharedLinkRoute()), + onPressed: () => context.autoPush(const SharedLinkRoute()), icon: const Icon( Icons.link, size: 20, @@ -191,21 +189,21 @@ class SharingPage extends HookConsumerWidget { child: Icon( Icons.insert_photo_rounded, size: 50, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( 'sharing_page_empty_list', - style: Theme.of(context).textTheme.displaySmall, + style: context.textTheme.displaySmall, ).tr(), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( 'sharing_page_description', - style: Theme.of(context).textTheme.bodyMedium, + style: context.textTheme.bodyMedium, ).tr(), ), ], @@ -218,7 +216,7 @@ class SharingPage extends HookConsumerWidget { Widget sharePartnerButton() { return InkWell( - onTap: () => AutoRouter.of(context).push(const PartnerRoute()), + onTap: () => context.autoPush(const PartnerRoute()), borderRadius: BorderRadius.circular(12), child: const Icon( Icons.swap_horizontal_circle_rounded, diff --git a/mobile/lib/modules/archive/views/archive_page.dart b/mobile/lib/modules/archive/views/archive_page.dart index 2e1c1cd4a..06270afc1 100644 --- a/mobile/lib/modules/archive/views/archive_page.dart +++ b/mobile/lib/modules/archive/views/archive_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/shared/models/asset.dart'; @@ -30,7 +30,7 @@ class ArchivePage extends HookConsumerWidget { AppBar buildAppBar(String count) { return AppBar( leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), centerTitle: true, diff --git a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart index 6df633f12..b7f6ee095 100644 --- a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart +++ b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart'; import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart'; @@ -67,7 +68,7 @@ class ImageViewerStateNotifier extends StateNotifier { gravity: ToastGravity.BOTTOM, ); } - Navigator.of(buildContext).pop(); + context.pop(); }, ); return const ShareDialog(); diff --git a/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart index 441492c39..97b955b5f 100644 --- a/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/asset.dart'; class AdvancedBottomSheet extends HookConsumerWidget { @@ -11,8 +12,6 @@ class AdvancedBottomSheet extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; - return SingleChildScrollView( child: Card( shape: const RoundedRectangleBorder( @@ -40,7 +39,9 @@ class AdvancedBottomSheet extends HookConsumerWidget { const SizedBox(height: 32.0), Container( decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[200], + color: context.isDarkTheme + ? Colors.grey[900] + : Colors.grey[200], borderRadius: BorderRadius.circular(15.0), ), child: Padding( @@ -70,7 +71,7 @@ class AdvancedBottomSheet extends HookConsumerWidget { icon: Icon( Icons.copy, size: 16.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), diff --git a/mobile/lib/modules/asset_viewer/ui/description_input.dart b/mobile/lib/modules/asset_viewer/ui/description_input.dart index e00739faf..a8cb62524 100644 --- a/mobile/lib/modules/asset_viewer/ui/description_input.dart +++ b/mobile/lib/modules/asset_viewer/ui/description_input.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/asset_description.provider.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; @@ -19,8 +20,7 @@ class DescriptionInput extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; - final textColor = isDarkTheme ? Colors.white : Colors.black; + final textColor = context.isDarkTheme ? Colors.white : Colors.black; final controller = useTextEditingController(); final focusNode = useFocusNode(); final isFocus = useState(false); diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart index f194738a2..4f2fbf50d 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:timezone/timezone.dart'; import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart'; import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart'; @@ -28,7 +29,7 @@ class ExifBottomSheet extends HookConsumerWidget { exifInfo.longitude != 0; String formatTimeZone(Duration d) => - "GMT${d.isNegative ? '-': '+'}${d.inHours.abs().toString().padLeft(2, '0')}:${d.inMinutes.abs().remainder(60).toString().padLeft(2, '0')}"; + "GMT${d.isNegative ? '-' : '+'}${d.inHours.abs().toString().padLeft(2, '0')}:${d.inMinutes.abs().remainder(60).toString().padLeft(2, '0')}"; String get formattedDateTime { DateTime dt = asset.fileCreatedAt.toLocal(); @@ -41,10 +42,16 @@ class ExifBottomSheet extends HookConsumerWidget { final location = getLocation(asset.exifInfo!.timeZone!); dt = TZDateTime.from(dt, location); } on LocationNotFoundException { - RegExp re = RegExp(r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$', caseSensitive: false); + RegExp re = RegExp( + r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$', + caseSensitive: false, + ); final m = re.firstMatch(asset.exifInfo!.timeZone!); if (m != null) { - final duration = Duration(hours: int.parse(m.group(1) ?? '0'), minutes: int.parse(m.group(2) ?? '0')); + final duration = Duration( + hours: int.parse(m.group(1) ?? '0'), + minutes: int.parse(m.group(2) ?? '0'), + ); dt = dt.add(duration); timeZone = formatTimeZone(duration); } @@ -105,8 +112,7 @@ class ExifBottomSheet extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final assetWithExif = ref.watch(assetDetailProvider(asset)); final exifInfo = (assetWithExif.value ?? asset).exifInfo; - var isDarkTheme = Theme.of(context).brightness == Brightness.dark; - var textColor = isDarkTheme ? Colors.white : Colors.black; + var textColor = context.isDarkTheme ? Colors.white : Colors.black; buildMap() { return Padding( @@ -322,9 +328,14 @@ class ExifBottomSheet extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ), - subtitle: exifInfo.f != null || exifInfo.exposureSeconds != null || exifInfo.mm != null || exifInfo.iso != null ? Text( - "ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO ${exifInfo.iso ?? ''} ", - ) : null, + subtitle: exifInfo.f != null || + exifInfo.exposureSeconds != null || + exifInfo.mm != null || + exifInfo.iso != null + ? Text( + "ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO ${exifInfo.iso ?? ''} ", + ) + : null, ), ], ); @@ -393,7 +404,7 @@ class ExifBottomSheet extends HookConsumerWidget { data: (data) => DescriptionInput(asset: data), error: (error, stackTrace) => Icon( Icons.image_not_supported_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), loading: () => const SizedBox( width: 75, diff --git a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart index 69d2be9f1..3e6dfe2ee 100644 --- a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart +++ b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart @@ -1,6 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; @@ -16,6 +17,8 @@ class TopControlAppBar extends HookConsumerWidget { required this.onFavorite, required this.onUploadPressed, required this.isOwner, + required this.shareAlbumId, + required this.onActivitiesPressed, }) : super(key: key); final Asset asset; @@ -24,14 +27,23 @@ class TopControlAppBar extends HookConsumerWidget { final VoidCallback? onDownloadPressed; final VoidCallback onToggleMotionVideo; final VoidCallback onAddToAlbumPressed; + final VoidCallback onActivitiesPressed; final Function(Asset) onFavorite; final bool isPlayingMotionVideo; final bool isOwner; + final String? shareAlbumId; @override Widget build(BuildContext context, WidgetRef ref) { const double iconSize = 22.0; final a = ref.watch(assetWatcher(asset)).value ?? asset; + final comments = shareAlbumId != null + ? ref.watch( + activityStatisticsStateProvider( + (albumId: shareAlbumId!, assetId: asset.remoteId), + ), + ) + : 0; Widget buildFavoriteButton(a) { return IconButton( @@ -94,6 +106,34 @@ class TopControlAppBar extends HookConsumerWidget { ); } + Widget buildActivitiesButton() { + return IconButton( + onPressed: () { + onActivitiesPressed(); + }, + icon: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.mode_comment_outlined, + color: Colors.grey[200], + ), + if (comments != 0) + Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + comments.toString(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.grey[200], + ), + ), + ), + ], + ), + ); + } + Widget buildUploadButton() { return IconButton( onPressed: onUploadPressed, @@ -107,7 +147,7 @@ class TopControlAppBar extends HookConsumerWidget { Widget buildBackButton() { return IconButton( onPressed: () { - AutoRouter.of(context).pop(); + context.autoPop(); }, icon: Icon( Icons.arrow_back_ios_new_rounded, @@ -130,6 +170,7 @@ class TopControlAppBar extends HookConsumerWidget { if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(), if (asset.isRemote && isOwner) buildAddToAlbumButtom(), + if (shareAlbumId != null) buildActivitiesButton(), buildMoreInfoButton(), ], ); diff --git a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart index 6a355a64e..bdb2bb50c 100644 --- a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart +++ b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart'; @@ -49,6 +50,7 @@ class GalleryViewerPage extends HookConsumerWidget { final int heroOffset; final bool showStack; final bool isOwner; + final String? sharedAlbumId; GalleryViewerPage({ super.key, @@ -58,6 +60,7 @@ class GalleryViewerPage extends HookConsumerWidget { this.heroOffset = 0, this.showStack = false, this.isOwner = true, + this.sharedAlbumId, }) : controller = PageController(initialPage: initialIndex); final PageController controller; @@ -207,7 +210,7 @@ class GalleryViewerPage extends HookConsumerWidget { if (isDeleted && isParent) { if (totalAssets == 1) { // Handle only one asset - AutoRouter.of(context).pop(); + context.autoPop(); } else { // Go to next page otherwise controller.nextPage( @@ -291,7 +294,7 @@ class GalleryViewerPage extends HookConsumerWidget { final ratio = d.dy / max(d.dx.abs(), 1); if (d.dy > sensitivity && ratio > ratioThreshold) { - AutoRouter.of(context).pop(); + context.autoPop(); } else if (d.dy < -sensitivity && ratio < -ratioThreshold) { showInfo(); } @@ -306,7 +309,7 @@ class GalleryViewerPage extends HookConsumerWidget { .watch(assetProvider.notifier) .toggleArchive([asset], !asset.isArchived); if (isParent) { - AutoRouter.of(context).pop(); + context.autoPop(); return; } removeAssetFromStack(); @@ -327,6 +330,19 @@ class GalleryViewerPage extends HookConsumerWidget { ); } + handleActivities() { + if (sharedAlbumId != null) { + context.autoPush( + ActivitiesRoute( + albumId: sharedAlbumId!, + assetId: asset().remoteId, + withAssetThumbs: false, + isOwner: isOwner, + ), + ); + } + } + buildAppBar() { return IgnorePointer( ignoring: !ref.watch(showControlsProvider), @@ -355,6 +371,8 @@ class GalleryViewerPage extends HookConsumerWidget { isPlayingMotionVideo.value = !isPlayingMotionVideo.value; }), onAddToAlbumPressed: () => addToAlbum(asset()), + shareAlbumId: sharedAlbumId, + onActivitiesPressed: handleActivities, ), ), ), @@ -497,7 +515,7 @@ class GalleryViewerPage extends HookConsumerWidget { stackElements.elementAt(stackIndex.value), ); Navigator.pop(ctx); - AutoRouter.of(context).pop(); + context.autoPop(); }, title: const Text( "viewer_stack_use_as_main_asset", @@ -524,7 +542,7 @@ class GalleryViewerPage extends HookConsumerWidget { childrenToRemove: [currentAsset], ); Navigator.pop(ctx); - AutoRouter.of(context).pop(); + context.autoPop(); } else { await ref.read(assetStackServiceProvider).updateStack( currentAsset, @@ -552,7 +570,7 @@ class GalleryViewerPage extends HookConsumerWidget { childrenToRemove: stack, ); Navigator.pop(ctx); - AutoRouter.of(context).pop(); + context.autoPop(); }, title: const Text( "viewer_unstack", @@ -812,8 +830,8 @@ class GalleryViewerPage extends HookConsumerWidget { placeholder: Image( image: provider, fit: BoxFit.fitWidth, - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, + height: context.height, + width: context.width, alignment: Alignment.center, ), onVideoEnded: () { diff --git a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart index a56d65595..ad929a8c5 100644 --- a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart +++ b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:chewie/chewie.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/ui/video_player_controls.dart'; @@ -44,7 +45,7 @@ class VideoViewerPage extends HookConsumerWidget { ), error: (error, stackTrace) => Icon( Icons.image_not_supported_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), loading: () => const Center( child: SizedBox( @@ -74,8 +75,8 @@ class VideoViewerPage extends HookConsumerWidget { ), if (downloadAssetStatus == DownloadAssetStatus.loading) SizedBox( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, + height: context.height, + width: context.width, child: const Center( child: ImmichLoadingIndicator(), ), @@ -205,8 +206,8 @@ class _VideoPlayerState extends State { ); } else { return SizedBox( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, + height: context.height, + width: context.width, child: Center( child: Stack( children: [ diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index 673991c5d..0df8bc90d 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -40,7 +40,7 @@ class BackupNotifier extends StateNotifier { progressInPercentage: 0, cancelToken: CancellationToken(), autoBackup: Store.get(StoreKey.autoBackup, false), - backgroundBackup: false, + backgroundBackup: Store.get(StoreKey.backgroundBackup, false), backupRequireWifi: Store.get(StoreKey.backupRequireWifi, true), backupRequireCharging: Store.get(StoreKey.backupRequireCharging, false), @@ -171,6 +171,7 @@ class BackupNotifier extends StateNotifier { state.backupRequireCharging, ); await Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay); + await Store.put(StoreKey.backgroundBackup, state.backgroundBackup); } else { state = state.copyWith( backgroundBackup: wasEnabled, @@ -383,6 +384,9 @@ class BackupNotifier extends StateNotifier { final isEnabled = await _backgroundService.isBackgroundBackupEnabled(); state = state.copyWith(backgroundBackup: isEnabled); + if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) { + Store.put(StoreKey.backgroundBackup, isEnabled); + } if (state.backupProgress != BackUpProgressEnum.inBackground) { await _getBackupAlbumsInfo(); diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart index eaace503e..4df62090b 100644 --- a/mobile/lib/modules/backup/ui/album_info_card.dart +++ b/mobile/lib/modules/backup/ui/album_info_card.dart @@ -1,9 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -22,10 +22,10 @@ class AlbumInfoCard extends HookConsumerWidget { ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo); final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo); - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; + final isDarkTheme = context.isDarkTheme; ColorFilter selectedFilter = ColorFilter.mode( - Theme.of(context).primaryColor.withAlpha(100), + context.primaryColor.withAlpha(100), BlendMode.darken, ); ColorFilter excludedFilter = @@ -46,7 +46,7 @@ class AlbumInfoCard extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ).tr(), - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: context.primaryColor, ); } else if (isExcluded) { return Chip( @@ -194,7 +194,7 @@ class AlbumInfoCard extends HookConsumerWidget { albumInfo.name, style: TextStyle( fontSize: 14, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), @@ -224,13 +224,13 @@ class AlbumInfoCard extends HookConsumerWidget { ), IconButton( onPressed: () { - AutoRouter.of(context).push( + context.autoPush( AlbumPreviewRoute(album: albumInfo.albumEntity), ); }, icon: Icon( Icons.image_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, size: 24, ), splashRadius: 25, diff --git a/mobile/lib/modules/backup/ui/album_info_list_tile.dart b/mobile/lib/modules/backup/ui/album_info_list_tile.dart index 88141a015..e19827bf9 100644 --- a/mobile/lib/modules/backup/ui/album_info_list_tile.dart +++ b/mobile/lib/modules/backup/ui/album_info_list_tile.dart @@ -1,10 +1,10 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -25,14 +25,13 @@ class AlbumInfoListTile extends HookConsumerWidget { ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo); ColorFilter selectedFilter = ColorFilter.mode( - Theme.of(context).primaryColor.withAlpha(100), + context.primaryColor.withAlpha(100), BlendMode.darken, ); ColorFilter excludedFilter = ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken); ColorFilter unselectedFilter = const ColorFilter.mode(Colors.black, BlendMode.color); - var isDarkTheme = Theme.of(context).brightness == Brightness.dark; var assetCount = useState(0); @@ -56,11 +55,11 @@ class AlbumInfoListTile extends HookConsumerWidget { buildTileColor() { if (isSelected) { - return isDarkTheme - ? Theme.of(context).primaryColor.withAlpha(100) - : Theme.of(context).primaryColor.withAlpha(25); + return context.isDarkTheme + ? context.primaryColor.withAlpha(100) + : context.primaryColor.withAlpha(25); } else if (isExcluded) { - return isDarkTheme + return context.isDarkTheme ? Colors.red[300]?.withAlpha(150) : Colors.red[100]?.withAlpha(150); } else { @@ -159,13 +158,13 @@ class AlbumInfoListTile extends HookConsumerWidget { subtitle: Text(assetCount.value.toString()), trailing: IconButton( onPressed: () { - AutoRouter.of(context).push( + context.autoPush( AlbumPreviewRoute(album: albumInfo.albumEntity), ); }, icon: Icon( Icons.image_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, size: 24, ), splashRadius: 25, diff --git a/mobile/lib/modules/backup/ui/backup_info_card.dart b/mobile/lib/modules/backup/ui/backup_info_card.dart index bf52c79e6..a398bc83e 100644 --- a/mobile/lib/modules/backup/ui/backup_info_card.dart +++ b/mobile/lib/modules/backup/ui/backup_info_card.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class BackupInfoCard extends StatelessWidget { final String title; @@ -14,13 +15,11 @@ class BackupInfoCard extends StatelessWidget { @override Widget build(BuildContext context) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; - return Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), // if you need this side: BorderSide( - color: isDarkMode + color: context.isDarkTheme ? const Color.fromARGB(255, 56, 56, 56) : Colors.black12, width: 1, diff --git a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart index 23bdf11ed..0ddc6beaf 100644 --- a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart +++ b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart @@ -1,9 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; @@ -53,7 +53,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { ), backgroundColor: Colors.white, onPressed: () { - AutoRouter.of(context).push(const FailedBackupStatusRoute()); + context.autoPush(const FailedBackupStatusRoute()); }, ); } @@ -61,7 +61,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { Widget buildAssetInfoTable() { return Table( border: TableBorder.all( - color: Theme.of(context).primaryColorLight, + color: context.themeData.primaryColorLight, width: 1, ), children: [ @@ -176,7 +176,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { onTap: () => isShowThumbnail.value = true, child: Icon( Icons.image_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, size: 30, ), ), @@ -206,7 +206,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { minHeight: 10.0, value: uploadProgress / 100.0, backgroundColor: Colors.grey, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), Text( diff --git a/mobile/lib/modules/backup/ui/ios_debug_info_tile.dart b/mobile/lib/modules/backup/ui/ios_debug_info_tile.dart index 9ab92104e..283c329b6 100644 --- a/mobile/lib/modules/backup/ui/ios_debug_info_tile.dart +++ b/mobile/lib/modules/backup/ui/ios_debug_info_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart'; import 'package:intl/intl.dart'; @@ -43,7 +44,7 @@ class IosDebugInfoTile extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), subtitle: Text( @@ -54,7 +55,7 @@ class IosDebugInfoTile extends HookConsumerWidget { ), leading: Icon( Icons.bug_report, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ); } diff --git a/mobile/lib/modules/backup/views/album_preview_page.dart b/mobile/lib/modules/backup/views/album_preview_page.dart index 27ca79082..cdb0204ec 100644 --- a/mobile/lib/modules/backup/views/album_preview_page.dart +++ b/mobile/lib/modules/backup/views/album_preview_page.dart @@ -1,9 +1,9 @@ import 'dart:typed_data'; -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/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -53,7 +53,7 @@ class AlbumPreviewPage extends HookConsumerWidget { ], ), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_new_rounded), ), ), diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart index 9e4118038..e259bf137 100644 --- a/mobile/lib/modules/backup/views/backup_album_selection_page.dart +++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart @@ -1,10 +1,10 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/ui/album_info_card.dart'; import 'package:immich_mobile/modules/backup/ui/album_info_list_tile.dart'; @@ -18,7 +18,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { // final availableAlbums = ref.watch(backupProvider).availableAlbums; final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums; final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums; - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; + final isDarkTheme = context.isDarkTheme; final allAlbums = ref.watch(backupProvider).availableAlbums; // Albums which are displayed to the user @@ -118,7 +118,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ), - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: context.primaryColor, deleteIconColor: isDarkTheme ? Colors.black : Colors.white, deleteIcon: const Icon( Icons.cancel_rounded, @@ -211,7 +211,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), title: const Text( @@ -315,7 +315,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { "backup_album_selection_page_albums_tap", style: TextStyle( fontSize: 12, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ).tr(), @@ -325,7 +325,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { icon: Icon( Icons.info, size: 20, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), onPressed: () { // show the dialog @@ -342,7 +342,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ).tr(), content: SingleChildScrollView( diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index 9dc40eced..fa8fac1d6 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -1,11 +1,11 @@ import 'dart:io'; -import 'package:auto_route/auto_route.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart'; @@ -49,7 +49,6 @@ class BackupControllerPage extends HookConsumerWidget { !hasExclusiveAccess ? false : true; - var isDarkMode = Theme.of(context).brightness == Brightness.dark; final checkInProgress = useState(false); useEffect( @@ -151,7 +150,7 @@ class BackupControllerPage extends HookConsumerWidget { return ListTile( leading: Icon( Icons.warning_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), title: const Text( "Check for corrupt asset backups", @@ -187,7 +186,7 @@ class BackupControllerPage extends HookConsumerWidget { leading: isAutoBackup ? Icon( Icons.cloud_done_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ) : const Icon(Icons.cloud_off_rounded), title: Text( @@ -266,7 +265,7 @@ class BackupControllerPage extends HookConsumerWidget { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), ).tr(), onPressed: () { - Navigator.of(context).pop(); + context.pop(); }, ), ], @@ -279,7 +278,7 @@ class BackupControllerPage extends HookConsumerWidget { final bool isBackgroundEnabled = backupState.backgroundBackup; final bool isWifiRequired = backupState.backupRequireWifi; final bool isChargingRequired = backupState.backupRequireCharging; - final Color activeColor = Theme.of(context).primaryColor; + final Color activeColor = context.primaryColor; String formatBackupDelaySliderValue(double v) { if (v == 0.0) { @@ -410,7 +409,7 @@ class BackupControllerPage extends HookConsumerWidget { max: 3.0, divisions: 3, label: formatBackupDelaySliderValue(triggerDelay.value), - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, ), ), ElevatedButton( @@ -511,7 +510,7 @@ class BackupControllerPage extends HookConsumerWidget { child: Text( text.trim().substring(0, text.length - 2), style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontSize: 12, fontWeight: FontWeight.bold, ), @@ -523,7 +522,7 @@ class BackupControllerPage extends HookConsumerWidget { child: Text( "backup_controller_page_none_selected".tr(), style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontSize: 12, fontWeight: FontWeight.bold, ), @@ -562,7 +561,7 @@ class BackupControllerPage extends HookConsumerWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide( - color: isDarkMode + color: context.isDarkTheme ? const Color.fromARGB(255, 56, 56, 56) : Colors.black12, width: 1, @@ -592,7 +591,7 @@ class BackupControllerPage extends HookConsumerWidget { ), trailing: ElevatedButton( onPressed: () { - AutoRouter.of(context).push(const BackupAlbumSelectionRoute()); + context.autoPush(const BackupAlbumSelectionRoute()); }, child: const Text( "backup_controller_page_select", @@ -678,7 +677,7 @@ class BackupControllerPage extends HookConsumerWidget { leading: IconButton( onPressed: () { ref.watch(websocketProvider.notifier).listenUploadEvent(); - AutoRouter.of(context).pop(true); + context.autoPop(true); }, splashRadius: 24, icon: const Icon( diff --git a/mobile/lib/modules/backup/views/failed_backup_status_page.dart b/mobile/lib/modules/backup/views/failed_backup_status_page.dart index c55383cf3..433ed3420 100644 --- a/mobile/lib/modules/backup/views/failed_backup_status_page.dart +++ b/mobile/lib/modules/backup/views/failed_backup_status_page.dart @@ -1,6 +1,6 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; import 'package:intl/intl.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -20,7 +20,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { ), leading: IconButton( onPressed: () { - AutoRouter.of(context).pop(true); + context.autoPop(true); }, splashRadius: 24, icon: const Icon( @@ -114,7 +114,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 12, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), diff --git a/mobile/lib/modules/favorite/views/favorites_page.dart b/mobile/lib/modules/favorite/views/favorites_page.dart index 62f8763bb..163611396 100644 --- a/mobile/lib/modules/favorite/views/favorites_page.dart +++ b/mobile/lib/modules/favorite/views/favorites_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/shared/models/asset.dart'; @@ -28,7 +28,7 @@ class FavoritesPage extends HookConsumerWidget { AppBar buildAppBar() { return AppBar( leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), centerTitle: true, diff --git a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart index d63b0631e..acb176aaa 100644 --- a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart +++ b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class GroupDividerTitle extends ConsumerWidget { const GroupDividerTitle({ @@ -51,7 +52,7 @@ class GroupDividerTitle extends ConsumerWidget { child: multiselectEnabled && selected ? Icon( Icons.check_circle_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ) : const Icon( Icons.check_circle_outline_rounded, diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart index 50f6f3f71..2c0f63394 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart @@ -34,6 +34,7 @@ class ImmichAssetGrid extends HookConsumerWidget { final bool showDragScroll; final bool showStack; final bool isOwner; + final String? sharedAlbumId; const ImmichAssetGrid({ super.key, @@ -55,6 +56,7 @@ class ImmichAssetGrid extends HookConsumerWidget { this.showDragScroll = true, this.showStack = false, this.isOwner = true, + this.sharedAlbumId, }); @override @@ -120,6 +122,7 @@ class ImmichAssetGrid extends HookConsumerWidget { showDragScroll: showDragScroll, showStack: showStack, isOwner: isOwner, + sharedAlbumId: sharedAlbumId, ), ); } diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 822a6329a..27b9f9d3d 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -4,10 +4,11 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/shared/models/asset.dart'; -import 'package:immich_mobile/utils/builtin_extensions.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'asset_grid_data_structure.dart'; import 'group_divider_title.dart'; @@ -39,6 +40,7 @@ class ImmichAssetGridView extends StatefulWidget { final bool showDragScroll; final bool showStack; final bool isOwner; + final String? sharedAlbumId; const ImmichAssetGridView({ super.key, @@ -60,6 +62,7 @@ class ImmichAssetGridView extends StatefulWidget { this.showDragScroll = true, this.showStack = false, this.isOwner = true, + this.sharedAlbumId, }); @override @@ -141,6 +144,7 @@ class ImmichAssetGridViewState extends State { heroOffset: widget.heroOffset, showStack: widget.showStack, isOwner: widget.isOwner, + sharedAlbumId: widget.sharedAlbumId, ); } @@ -221,7 +225,7 @@ class ImmichAssetGridViewState extends State { style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.displayLarge?.color, + color: context.textTheme.displayLarge?.color, ), ), ); @@ -369,7 +373,7 @@ class ImmichAssetGridViewState extends State { scrollStateListener: dragScrolling, itemPositionsListener: _itemPositionsListener, controller: _itemScrollController, - backgroundColor: Theme.of(context).hintColor, + backgroundColor: context.themeData.hintColor, labelTextBuilder: _labelBuilder, labelConstraints: const BoxConstraints(maxHeight: 28), scrollbarAnimationDuration: const Duration(milliseconds: 300), diff --git a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart index 2450e4439..c99e08fb3 100644 --- a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart +++ b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart @@ -1,6 +1,6 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; @@ -21,6 +21,7 @@ class ThumbnailImage extends StatelessWidget { final Function? onSelect; final Function? onDeselect; final int heroOffset; + final String? sharedAlbumId; const ThumbnailImage({ Key? key, @@ -31,6 +32,7 @@ class ThumbnailImage extends StatelessWidget { this.showStorageIndicator = true, this.showStack = false, this.isOwner = true, + this.sharedAlbumId, this.useGrayBoxPlaceholder = false, this.isSelected = false, this.multiselectEnabled = false, @@ -41,9 +43,9 @@ class ThumbnailImage extends StatelessWidget { @override Widget build(BuildContext context) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; - final assetContainerColor = - isDarkTheme ? Colors.blueGrey : Theme.of(context).primaryColorLight; + final assetContainerColor = context.isDarkTheme + ? Colors.blueGrey + : context.themeData.primaryColorLight; // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id final isFromDto = asset.id == Isar.autoIncrement; @@ -56,7 +58,7 @@ class ThumbnailImage extends StatelessWidget { ), child: Icon( Icons.check_circle_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ); } else { @@ -176,7 +178,7 @@ class ThumbnailImage extends StatelessWidget { onSelect?.call(); } } else { - AutoRouter.of(context).push( + context.autoPush( GalleryViewerRoute( initialIndex: index, loadAsset: loadAsset, @@ -184,6 +186,7 @@ class ThumbnailImage extends StatelessWidget { heroOffset: heroOffset, showStack: showStack, isOwner: isOwner, + sharedAlbumId: sharedAlbumId, ), ); } diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index a3c93c9d6..3ee26642d 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart'; import 'package:immich_mobile/modules/home/models/selection_state.dart'; import 'package:immich_mobile/modules/home/ui/delete_dialog.dart'; @@ -44,7 +45,6 @@ class ControlBottomAppBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; var hasRemote = selectionAssetState.hasRemote || selectionAssetState.hasMerged; var hasLocal = @@ -161,7 +161,7 @@ class ControlBottomAppBar extends ConsumerWidget { ScrollController scrollController, ) { return Card( - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100], surfaceTintColor: Colors.transparent, elevation: 18.0, shape: const RoundedRectangleBorder( @@ -244,12 +244,12 @@ class AddToAlbumTitleRow extends StatelessWidget { onPressed: onCreateNewAlbum, icon: Icon( Icons.add, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), label: Text( "common_create_new_album", style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14, ), diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index a5ac3de7e..64cb36791 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; @@ -106,8 +106,7 @@ class HomePage extends HookConsumerWidget { handleShareAssets(ref, context, selection.value.toList()); } else { final ids = remoteOnlySelection().map((e) => e.remoteId!); - AutoRouter.of(context) - .push(SharedLinkEditRoute(assetsList: ids.toList())); + context.autoPush(SharedLinkEditRoute(assetsList: ids.toList())); } processing.value = false; selectionEnabledHook.value = false; @@ -265,7 +264,7 @@ class HomePage extends HookConsumerWidget { ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums(); selectionEnabledHook.value = false; - AutoRouter.of(context).push(AlbumViewerRoute(albumId: result.id)); + context.autoPush(AlbumViewerRoute(albumId: result.id)); } } finally { processing.value = false; @@ -322,7 +321,7 @@ class HomePage extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ).tr(), ), diff --git a/mobile/lib/modules/login/ui/change_password_form.dart b/mobile/lib/modules/login/ui/change_password_form.dart index b7db83b3f..884a26b6d 100644 --- a/mobile/lib/modules/login/ui/change_password_form.dart +++ b/mobile/lib/modules/login/ui/change_password_form.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; @@ -37,7 +38,7 @@ class ChangePasswordForm extends HookConsumerWidget { style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), Padding( @@ -191,7 +192,7 @@ class ChangePasswordButton extends ConsumerWidget { return ElevatedButton( style: ElevatedButton.styleFrom( visualDensity: VisualDensity.standard, - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: context.primaryColor, foregroundColor: Colors.grey[50], elevation: 2, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25), diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart index 58c7feec2..5cdcbe034 100644 --- a/mobile/lib/modules/login/ui/login_form.dart +++ b/mobile/lib/modules/login/ui/login_form.dart @@ -1,9 +1,9 @@ import 'dart:io'; -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/login/providers/oauth.provider.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -150,7 +150,7 @@ class LoginForm extends HookConsumerWidget { // Resume backup (if enable) then navigate if (ref.read(authenticationProvider).shouldChangePassword && !ref.read(authenticationProvider).isAdmin) { - AutoRouter.of(context).push(const ChangePasswordRoute()); + context.autoPush(const ChangePasswordRoute()); } else { final hasPermission = await ref .read(galleryPermissionNotifier.notifier) @@ -159,7 +159,7 @@ class LoginForm extends HookConsumerWidget { // Don't resume the backup until we have gallery permission ref.read(backupProvider.notifier).resumeBackup(); } - AutoRouter.of(context).replace(const TabControllerRoute()); + context.autoReplace(const TabControllerRoute()); } } else { ImmichToast.show( @@ -212,9 +212,7 @@ class LoginForm extends HookConsumerWidget { if (permission.isGranted || permission.isLimited) { ref.watch(backupProvider.notifier).resumeBackup(); } - AutoRouter.of(context).replace( - const TabControllerRoute(), - ); + context.autoReplace(const TabControllerRoute()); } else { ImmichToast.show( context: context, @@ -260,8 +258,7 @@ class LoginForm extends HookConsumerWidget { ), ), ), - onPressed: () => - AutoRouter.of(context).push(const SettingsRoute()), + onPressed: () => context.autoPush(const SettingsRoute()), icon: const Icon(Icons.settings_rounded), label: const SizedBox.shrink(), ), @@ -303,7 +300,7 @@ class LoginForm extends HookConsumerWidget { children: [ Text( serverEndpointController.text, - style: Theme.of(context).textTheme.displaySmall, + style: context.textTheme.displaySmall, textAlign: TextAlign.center, ), if (isPasswordLoginEnable.value) ...[ @@ -339,8 +336,7 @@ class LoginForm extends HookConsumerWidget { horizontal: 16.0, ), child: Divider( - color: Brightness.dark == - Theme.of(context).brightness + color: context.isDarkTheme ? Colors.white : Colors.black, ), @@ -588,7 +584,7 @@ class OAuthLoginButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor.withAlpha(230), + backgroundColor: context.primaryColor.withAlpha(230), padding: const EdgeInsets.symmetric(vertical: 12), ), onPressed: onPressed, diff --git a/mobile/lib/modules/login/views/login_page.dart b/mobile/lib/modules/login/views/login_page.dart index 98778736e..4e1b9a6df 100644 --- a/mobile/lib/modules/login/views/login_page.dart +++ b/mobile/lib/modules/login/views/login_page.dart @@ -1,7 +1,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/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/login/ui/login_form.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -47,13 +47,13 @@ class LoginPage extends HookConsumerWidget { child: Text( 'Logs', style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontFamily: "Inconsolata", ), ), onTap: () { - AutoRouter.of(context).push(const AppLogRoute()); + context.autoPush(const AppLogRoute()); }, ), ], diff --git a/mobile/lib/modules/map/models/map_state.model.dart b/mobile/lib/modules/map/models/map_state.model.dart index 7aab8efab..d606f1005 100644 --- a/mobile/lib/modules/map/models/map_state.model.dart +++ b/mobile/lib/modules/map/models/map_state.model.dart @@ -1,14 +1,20 @@ +import 'package:vector_map_tiles/vector_map_tiles.dart'; + class MapState { final bool isDarkTheme; final bool showFavoriteOnly; final bool includeArchived; final int relativeTime; + final Style? mapStyle; + final bool isLoading; MapState({ this.isDarkTheme = false, this.showFavoriteOnly = false, this.includeArchived = false, this.relativeTime = 0, + this.mapStyle, + this.isLoading = false, }); MapState copyWith({ @@ -16,18 +22,22 @@ class MapState { bool? showFavoriteOnly, bool? includeArchived, int? relativeTime, + Style? mapStyle, + bool? isLoading, }) { return MapState( isDarkTheme: isDarkTheme ?? this.isDarkTheme, showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly, includeArchived: includeArchived ?? this.includeArchived, relativeTime: relativeTime ?? this.relativeTime, + mapStyle: mapStyle ?? this.mapStyle, + isLoading: isLoading ?? this.isLoading, ); } @override String toString() { - return 'MapSettingsState(isDarkTheme: $isDarkTheme, showFavoriteOnly: $showFavoriteOnly, relativeTime: $relativeTime, includeArchived: $includeArchived)'; + return 'MapSettingsState(isDarkTheme: $isDarkTheme, showFavoriteOnly: $showFavoriteOnly, relativeTime: $relativeTime, includeArchived: $includeArchived, mapStyle: $mapStyle, isLoading: $isLoading)'; } @override @@ -38,7 +48,9 @@ class MapState { other.isDarkTheme == isDarkTheme && other.showFavoriteOnly == showFavoriteOnly && other.relativeTime == relativeTime && - other.includeArchived == includeArchived; + other.includeArchived == includeArchived && + other.mapStyle == mapStyle && + other.isLoading == isLoading; } @override @@ -46,6 +58,8 @@ class MapState { return isDarkTheme.hashCode ^ showFavoriteOnly.hashCode ^ relativeTime.hashCode ^ - includeArchived.hashCode; + includeArchived.hashCode ^ + mapStyle.hashCode ^ + isLoading.hashCode; } } diff --git a/mobile/lib/modules/map/providers/map_state.provider.dart b/mobile/lib/modules/map/providers/map_state.provider.dart index 8f4356198..3296b09c0 100644 --- a/mobile/lib/modules/map/providers/map_state.provider.dart +++ b/mobile/lib/modules/map/providers/map_state.provider.dart @@ -1,10 +1,23 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/map/models/map_state.model.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; +import 'package:immich_mobile/shared/providers/api.provider.dart'; +import 'package:immich_mobile/shared/services/api.service.dart'; +import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; +import 'package:vector_map_tiles/vector_map_tiles.dart'; class MapStateNotifier extends StateNotifier { - MapStateNotifier(this._appSettingsProvider) + MapStateNotifier(this._appSettingsProvider, this._apiService) : super( MapState( isDarkTheme: _appSettingsProvider @@ -15,17 +28,69 @@ class MapStateNotifier extends StateNotifier { .getSetting(AppSettingsEnum.mapIncludeArchived), relativeTime: _appSettingsProvider .getSetting(AppSettingsEnum.mapRelativeDate), + isLoading: true, ), - ); + ) { + _fetchStyleFromServer( + _appSettingsProvider.getSetting(AppSettingsEnum.mapThemeMode), + ); + } final AppSettingsService _appSettingsProvider; + final ApiService _apiService; + final Logger _log = Logger("MapStateNotifier"); + + bool get isRaster => + state.mapStyle != null && state.mapStyle!.rasterTileProvider != null; + + double get maxZoom => + (isRaster ? state.mapStyle!.rasterTileProvider!.maximumZoom : 14) + .toDouble(); void switchTheme(bool isDarkTheme) { + _updateThemeMode(isDarkTheme); + _fetchStyleFromServer(isDarkTheme); + } + + void _updateThemeMode(bool isDarkTheme) { _appSettingsProvider.setSetting( AppSettingsEnum.mapThemeMode, isDarkTheme, ); - state = state.copyWith(isDarkTheme: isDarkTheme); + state = state.copyWith(isDarkTheme: isDarkTheme, isLoading: true); + } + + void _fetchStyleFromServer(bool isDarkTheme) async { + final styleResponse = await _apiService.systemConfigApi + .getMapStyleWithHttpInfo(isDarkTheme ? MapTheme.dark : MapTheme.light); + if (styleResponse.statusCode >= HttpStatus.badRequest) { + throw ApiException(styleResponse.statusCode, styleResponse.body); + } + final styleJsonString = styleResponse.body.isNotEmpty && + styleResponse.statusCode != HttpStatus.noContent + ? styleResponse.body + : null; + + if (styleJsonString == null) { + _log.severe('Style JSON from server is empty'); + return; + } + final styleJson = await compute(jsonDecode, styleJsonString); + if (styleJson is! Map) { + _log.severe('Style JSON from server is invalid'); + return; + } + final styleReader = StyleReader(uri: ''); + Style? style; + try { + style = await styleReader.readFromMap(styleJson); + } finally { + // Consume all error + } + state = state.copyWith( + mapStyle: style, + isLoading: false, + ); } void switchFavoriteOnly(bool isFavoriteOnly) { @@ -51,9 +116,44 @@ class MapStateNotifier extends StateNotifier { ); state = state.copyWith(relativeTime: relativeTime); } + + Widget getTileLayer([bool forceDark = false]) { + if (isRaster) { + final rasterProvider = state.mapStyle!.rasterTileProvider; + final rasterLayer = TileLayer( + urlTemplate: rasterProvider!.url, + maxNativeZoom: rasterProvider.maximumZoom, + maxZoom: rasterProvider.maximumZoom.toDouble(), + ); + return state.isDarkTheme || forceDark + ? InvertionFilter( + child: SaturationFilter( + saturation: -1, + child: BrightnessFilter( + brightness: -1, + child: rasterLayer, + ), + ), + ) + : rasterLayer; + } + if (state.mapStyle != null && !isRaster) { + return VectorTileLayer( + // Tiles and themes will be set for vector providers + tileProviders: state.mapStyle!.providers!, + theme: state.mapStyle!.theme!, + sprites: state.mapStyle!.sprites, + concurrency: 6, + ); + } + return const Center(child: ImmichLoadingIndicator()); + } } final mapStateNotifier = StateNotifierProvider((ref) { - return MapStateNotifier(ref.watch(appSettingsServiceProvider)); + return MapStateNotifier( + ref.watch(appSettingsServiceProvider), + ref.watch(apiServiceProvider), + ); }); diff --git a/mobile/lib/modules/map/ui/map_page_app_bar.dart b/mobile/lib/modules/map/ui/map_page_app_bar.dart index e9ed75cb1..ce426cf03 100644 --- a/mobile/lib/modules/map/ui/map_page_app_bar.dart +++ b/mobile/lib/modules/map/ui/map_page_app_bar.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/disable_multi_select_button.dart'; import 'package:immich_mobile/modules/map/ui/map_settings_dialog.dart'; @@ -30,7 +30,7 @@ class MapAppBar extends HookWidget implements PreferredSizeWidget { Padding( padding: const EdgeInsets.only(left: 15, top: 15), child: ElevatedButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), style: ElevatedButton.styleFrom( shape: const CircleBorder(), padding: const EdgeInsets.all(12), diff --git a/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart index c9c3cb8aa..0d0dfb412 100644 --- a/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart +++ b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; @@ -15,7 +16,6 @@ import 'package:immich_mobile/shared/ui/drag_sheet.dart'; import 'package:immich_mobile/utils/color_filter_generator.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; -import 'package:url_launcher/url_launcher.dart'; class MapPageBottomSheet extends StatefulHookConsumerWidget { final Stream mapPageEventStream; @@ -57,10 +57,10 @@ class AssetsInBoundBottomSheetState extends ConsumerState { @override Widget build(BuildContext context) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final isDarkTheme = context.isDarkTheme; final bottomPadding = Platform.isAndroid ? MediaQuery.of(context).padding.bottom - 10 : 0.0; - final maxHeight = MediaQuery.of(context).size.height - bottomPadding; + final maxHeight = context.height - bottomPadding; final isSheetScrolled = useState(false); final isSheetExpanded = useState(false); final assetsInBound = useState([]); @@ -137,7 +137,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { SizedBox( height: 150, width: 150, - child: isDarkMode + child: isDarkTheme ? const InvertionFilter( child: SaturationFilter( saturation: -1, @@ -156,7 +156,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { "map_zoom_to_see_photos".tr(), style: TextStyle( fontSize: 20, - color: Theme.of(context).textTheme.displayLarge?.color, + color: context.textTheme.displayLarge?.color, ), ), ], @@ -182,7 +182,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { height: 60, width: double.infinity, decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[100], ), child: Stack( children: [ @@ -197,17 +197,14 @@ class AssetsInBoundBottomSheetState extends ConsumerState { textToDisplay, style: TextStyle( fontSize: 16, - color: Theme.of(context).textTheme.displayLarge?.color, + color: context.textTheme.displayLarge?.color, fontWeight: FontWeight.bold, ), ), Divider( height: 10, - color: Theme.of(context) - .textTheme - .displayLarge - ?.color - ?.withOpacity(0.5), + color: + context.textTheme.displayLarge?.color?.withOpacity(0.5), ), ], ), @@ -218,7 +215,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { child: IconButton( icon: Icon( Icons.map_outlined, - color: Theme.of(context).textTheme.displayLarge?.color, + color: context.textTheme.displayLarge?.color, ), iconSize: 20, tooltip: 'Zoom to bounds', @@ -266,7 +263,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { ScrollController scrollController, ) { return Card( - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[100], surfaceTintColor: Colors.transparent, elevation: 18.0, margin: const EdgeInsets.all(0), @@ -320,24 +317,18 @@ class AssetsInBoundBottomSheetState extends ConsumerState { Positioned( bottom: maxHeight * currentExtend.value, left: 0, - child: GestureDetector( - onTap: () => launchUrl( - Uri.parse('https://openstreetmap.org/copyright'), - ), - child: ColoredBox( - color: (widget.isDarkTheme - ? Colors.grey[900] - : Colors.grey[100])!, - child: Padding( - padding: const EdgeInsets.all(3), - child: Text( - '© OpenStreetMap contributors', - style: TextStyle( - fontSize: 6, - color: !widget.isDarkTheme - ? Colors.grey[900] - : Colors.grey[100], - ), + child: ColoredBox( + color: + (widget.isDarkTheme ? Colors.grey[900] : Colors.grey[100])!, + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + 'OpenStreetMap contributors', + style: TextStyle( + fontSize: 6, + color: !widget.isDarkTheme + ? Colors.grey[900] + : Colors.grey[100], ), ), ), diff --git a/mobile/lib/modules/map/ui/map_settings_dialog.dart b/mobile/lib/modules/map/ui/map_settings_dialog.dart index f8a308b35..b7503cd52 100644 --- a/mobile/lib/modules/map/ui/map_settings_dialog.dart +++ b/mobile/lib/modules/map/ui/map_settings_dialog.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; class MapSettingsDialog extends HookConsumerWidget { @@ -15,7 +16,7 @@ class MapSettingsDialog extends HookConsumerWidget { final showFavoriteOnly = useState(mapSettings.showFavoriteOnly); final showIncludeArchived = useState(mapSettings.includeArchived); final showRelativeDate = useState(mapSettings.relativeTime); - final ThemeData theme = Theme.of(context); + final ThemeData theme = context.themeData; Widget buildMapThemeSetting() { return SwitchListTile.adaptive( @@ -125,7 +126,7 @@ class MapSettingsDialog extends HookConsumerWidget { List getDialogActions() { return [ TextButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () => context.pop(), style: TextButton.styleFrom( backgroundColor: mapSettings.isDarkTheme ? Colors.grey[100] : Colors.grey[700], @@ -146,7 +147,7 @@ class MapSettingsDialog extends HookConsumerWidget { mapSettingsNotifier.setRelativeTime(showRelativeDate.value); mapSettingsNotifier .switchIncludeArchived(showIncludeArchived.value); - Navigator.of(context).pop(); + context.pop(); }, style: TextButton.styleFrom( backgroundColor: theme.primaryColor, @@ -178,7 +179,7 @@ class MapSettingsDialog extends HookConsumerWidget { width: double.maxFinite, child: ConstrainedBox( constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.6, + maxHeight: context.height * 0.6, ), child: ListView( shrinkWrap: true, diff --git a/mobile/lib/modules/map/ui/map_thumbnail.dart b/mobile/lib/modules/map/ui/map_thumbnail.dart index 14cc2a83b..d42d99de1 100644 --- a/mobile/lib/modules/map/ui/map_thumbnail.dart +++ b/mobile/lib/modules/map/ui/map_thumbnail.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/plugin_api.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/shared/providers/server_info.provider.dart'; -import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; import 'package:latlong2/latlong.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -29,11 +28,7 @@ class MapThumbnail extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final tileLayer = TileLayer( - urlTemplate: ref.watch( - serverInfoProvider.select((v) => v.serverConfig.mapTileUrl), - ), - ); + ref.watch(mapStateNotifier.select((s) => s.mapStyle)); return SizedBox( height: height, @@ -55,20 +50,14 @@ class MapThumbnail extends HookConsumerWidget { 'OpenStreetMap contributors', onTap: () => launchUrl( Uri.parse('https://openstreetmap.org/copyright'), + mode: LaunchMode.externalApplication, ), ), ], ), ], children: [ - isDarkTheme - ? InvertionFilter( - child: SaturationFilter( - saturation: -1, - child: tileLayer, - ), - ) - : tileLayer, + ref.read(mapStateNotifier.notifier).getTileLayer(isDarkTheme), if (markers.isNotEmpty) MarkerLayer(markers: markers), ], ), diff --git a/mobile/lib/modules/map/views/map_page.dart b/mobile/lib/modules/map/views/map_page.dart index ffa18d37a..b03c13e36 100644 --- a/mobile/lib/modules/map/views/map_page.dart +++ b/mobile/lib/modules/map/views/map_page.dart @@ -1,6 +1,6 @@ import 'dart:async'; +import 'dart:math' as math; -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ import 'package:flutter_map_heatmap/flutter_map_heatmap.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:geolocator/geolocator.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/map/models/map_page_event.model.dart'; import 'package:immich_mobile/modules/map/providers/map_marker.provider.dart'; import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; @@ -20,12 +21,10 @@ import 'package:immich_mobile/modules/map/ui/map_page_bottom_sheet.dart'; import 'package:immich_mobile/modules/map/ui/map_page_app_bar.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; -import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; -import 'package:immich_mobile/utils/color_filter_generator.dart'; import 'package:immich_mobile/utils/debounce.dart'; -import 'package:immich_mobile/utils/flutter_map_extensions.dart'; +import 'package:immich_mobile/extensions/flutter_map_extensions.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/selection_handlers.dart'; import 'package:latlong2/latlong.dart'; @@ -79,26 +78,30 @@ class MapPageState extends ConsumerState { Set? assetMarkers, { bool forceReload = false, }) { - final bounds = mapController.bounds; - if (bounds != null) { - final oldAssetsInBounds = assetsInBounds.toSet(); - assetsInBounds = - assetMarkers?.where((e) => bounds.contains(e.point)).toSet() ?? {}; - final shouldReload = forceReload || - assetsInBounds.difference(oldAssetsInBounds).isNotEmpty || - assetsInBounds.length != oldAssetsInBounds.length; - if (shouldReload) { - mapPageEventSC.add( - MapPageAssetsInBoundUpdated( - assetsInBounds.map((e) => e.asset).toList(), - ), - ); + try { + final bounds = mapController.bounds; + if (bounds != null) { + final oldAssetsInBounds = assetsInBounds.toSet(); + assetsInBounds = + assetMarkers?.where((e) => bounds.contains(e.point)).toSet() ?? {}; + final shouldReload = forceReload || + assetsInBounds.difference(oldAssetsInBounds).isNotEmpty || + assetsInBounds.length != oldAssetsInBounds.length; + if (shouldReload) { + mapPageEventSC.add( + MapPageAssetsInBoundUpdated( + assetsInBounds.map((e) => e.asset).toList(), + ), + ); + } } + } finally { + // Consume all error } } void openAssetInViewer(Asset asset) { - AutoRouter.of(context).push( + context.autoPush( GalleryViewerRoute( initialIndex: 0, loadAsset: (index) => asset, @@ -120,6 +123,10 @@ class MapPageState extends ConsumerState { final selectedAssets = useState({}); final showLoadingIndicator = useState(false); final refetchMarkers = useState(true); + final isLoading = + ref.watch(mapStateNotifier.select((state) => state.isLoading)); + final maxZoom = ref.read(mapStateNotifier.notifier).maxZoom; + final zoomLevel = math.min(maxZoom, 14.0); if (refetchMarkers.value) { mapMarkerData.value = ref.watch(mapMarkersProvider).when( @@ -168,7 +175,6 @@ class MapPageState extends ConsumerState { final mapMarker = mapMarkerData.value .firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id); if (mapMarker != null) { - const zoomLevel = 16.0; LatLng? newCenter = mapController.centerBoundsWithPadding( mapMarker.point, const Offset(0, -120), @@ -230,7 +236,7 @@ class MapPageState extends ConsumerState { forceAssetUpdate = true; mapController.move( LatLng(currentUserLocation.latitude, currentUserLocation.longitude), - 12, + zoomLevel, ); } catch (error) { log.severe( @@ -359,24 +365,6 @@ class MapPageState extends ConsumerState { selectedAssets.value = selection; } - final tileLayer = TileLayer( - urlTemplate: ref.watch( - serverInfoProvider.select((v) => v.serverConfig.mapTileUrl), - ), - maxNativeZoom: 19, - maxZoom: 19, - ); - - final darkTileLayer = InvertionFilter( - child: SaturationFilter( - saturation: -1, - child: BrightnessFilter( - brightness: -1, - child: tileLayer, - ), - ), - ); - final markerLayer = MarkerLayer( markers: [ if (closestAssetMarker.value != null) @@ -451,41 +439,43 @@ class MapPageState extends ConsumerState { extendBodyBehindAppBar: true, body: Stack( children: [ - FlutterMap( - mapController: mapController, - options: MapOptions( - maxBounds: - LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)), - interactiveFlags: InteractiveFlag.doubleTapZoom | - InteractiveFlag.drag | - InteractiveFlag.flingAnimation | - InteractiveFlag.pinchMove | - InteractiveFlag.pinchZoom, - center: LatLng(20, 20), - zoom: 2, - minZoom: 1, - maxZoom: 18, // max level supported by OSM, - onMapReady: () { - mapController.mapEventStream.listen(onMapEvent); - }, + if (!isLoading) + FlutterMap( + mapController: mapController, + options: MapOptions( + maxBounds: + LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)), + interactiveFlags: InteractiveFlag.doubleTapZoom | + InteractiveFlag.drag | + InteractiveFlag.flingAnimation | + InteractiveFlag.pinchMove | + InteractiveFlag.pinchZoom, + center: LatLng(20, 20), + zoom: 2, + minZoom: 1, + maxZoom: maxZoom, + onMapReady: () { + mapController.mapEventStream.listen(onMapEvent); + }, + ), + children: [ + ref.read(mapStateNotifier.notifier).getTileLayer(), + heatMapLayer, + markerLayer, + ], ), - children: [ - isDarkTheme ? darkTileLayer : tileLayer, - heatMapLayer, - markerLayer, - ], - ), - MapPageBottomSheet( - mapPageEventStream: mapPageEventSC.stream, - bottomSheetEventSC: bottomSheetEventSC, - selectionEnabled: selectionEnabledHook.value, - selectionlistener: selectionListener, - isDarkTheme: isDarkTheme, - ), - if (showLoadingIndicator.value) + if (!isLoading) + MapPageBottomSheet( + mapPageEventStream: mapPageEventSC.stream, + bottomSheetEventSC: bottomSheetEventSC, + selectionEnabled: selectionEnabledHook.value, + selectionlistener: selectionListener, + isDarkTheme: isDarkTheme, + ), + if (showLoadingIndicator.value || isLoading) Positioned( - top: MediaQuery.of(context).size.height * 0.35, - left: MediaQuery.of(context).size.width * 0.425, + top: context.height * 0.35, + left: context.width * 0.425, child: const ImmichLoadingIndicator(), ), ], diff --git a/mobile/lib/modules/memories/ui/memory_lane.dart b/mobile/lib/modules/memories/ui/memory_lane.dart index dcd803651..65f0c6525 100644 --- a/mobile/lib/modules/memories/ui/memory_lane.dart +++ b/mobile/lib/modules/memories/ui/memory_lane.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/memories/providers/memory.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; @@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget { child: GestureDetector( onTap: () { HapticFeedback.heavyImpact(); - AutoRouter.of(context).push( + context.autoPush( MemoryRoute( memories: memories, memoryIndex: index, diff --git a/mobile/lib/modules/memories/views/memory_page.dart b/mobile/lib/modules/memories/views/memory_page.dart index a1aae5156..ca88ed04d 100644 --- a/mobile/lib/modules/memories/views/memory_page.dart +++ b/mobile/lib/modules/memories/views/memory_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/memories/models/memory.dart'; import 'package:immich_mobile/modules/memories/ui/memory_card.dart'; import 'package:immich_mobile/shared/models/asset.dart'; @@ -182,14 +182,14 @@ class MemoryPage extends HookConsumerWidget { currentMemory.value.assets.length; if (isLastAsset && (offset > notification.metrics.maxScrollExtent + 150)) { - AutoRouter.of(context).pop(); + context.autoPop(); return true; } } // Horizontal scroll handling if (notification.depth == 1 && (offset > notification.metrics.maxScrollExtent + 100)) { - AutoRouter.of(context).pop(); + context.autoPop(); return true; } } @@ -244,7 +244,7 @@ class MemoryPage extends HookConsumerWidget { child: MemoryCard( asset: asset, onTap: () => toNextAsset(index), - onClose: () => AutoRouter.of(context).pop(), + onClose: () => context.autoPop(), rightCornerText: assetProgress.value, title: memories[mIndex].title, showTitle: index == 0, diff --git a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart index efbbc78a3..e1fd05dab 100644 --- a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart +++ b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; @@ -11,7 +11,6 @@ import 'package:immich_mobile/shared/ui/immich_title_text.dart'; import 'package:permission_handler/permission_handler.dart'; class PermissionOnboardingPage extends HookConsumerWidget { - const PermissionOnboardingPage({super.key}); @override @@ -21,13 +20,10 @@ class PermissionOnboardingPage extends HookConsumerWidget { // Navigate to the main Tab Controller when permission is granted void goToHome() { // Resume backup (if enable) then navigate - ref.watch(backupProvider.notifier).resumeBackup() - .catchError((error) { + ref.watch(backupProvider.notifier).resumeBackup().catchError((error) { debugPrint('PermissionOnboardingPage error: $error'); }); - AutoRouter.of(context).replace( - const TabControllerRoute(), - ); + context.autoReplace(const TabControllerRoute()); } // When the permission is denied, we show a request permission page @@ -38,21 +34,21 @@ class PermissionOnboardingPage extends HookConsumerWidget { children: [ Text( 'permission_onboarding_request', - style: Theme.of(context).textTheme.titleMedium, + style: context.textTheme.titleMedium, textAlign: TextAlign.center, ).tr(), const SizedBox(height: 18), ElevatedButton( onPressed: () => ref - .read(galleryPermissionNotifier.notifier) - .requestGalleryPermission() - .then((permission) async { - if (permission.isGranted) { - // If permission is limited, we will show the limited - // permission page - goToHome(); - } - }), + .read(galleryPermissionNotifier.notifier) + .requestGalleryPermission() + .then((permission) async { + if (permission.isGranted) { + // If permission is limited, we will show the limited + // permission page + goToHome(); + } + }), child: const Text( 'permission_onboarding_grant_permission', ).tr(), @@ -70,7 +66,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { children: [ Text( 'permission_onboarding_permission_granted', - style: Theme.of(context).textTheme.titleMedium, + style: context.textTheme.titleMedium, textAlign: TextAlign.center, ).tr(), const SizedBox(height: 18), @@ -90,14 +86,15 @@ class PermissionOnboardingPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.warning_outlined, + const Icon( + Icons.warning_outlined, color: Colors.yellow, size: 48, ), const SizedBox(height: 8), Text( 'permission_onboarding_permission_limited', - style: Theme.of(context).textTheme.titleMedium, + style: context.textTheme.titleMedium, textAlign: TextAlign.center, ).tr(), const SizedBox(height: 18), @@ -123,14 +120,15 @@ class PermissionOnboardingPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.warning_outlined, + const Icon( + Icons.warning_outlined, color: Colors.red, size: 48, ), const SizedBox(height: 8), Text( 'permission_onboarding_permission_denied', - style: Theme.of(context).textTheme.titleMedium, + style: context.textTheme.titleMedium, textAlign: TextAlign.center, ).tr(), const SizedBox(height: 18), @@ -186,13 +184,10 @@ class PermissionOnboardingPage extends HookConsumerWidget { child: const Text('permission_onboarding_log_out').tr(), onPressed: () { ref.read(authenticationProvider.notifier).logout(); - AutoRouter.of(context).replace( - const LoginRoute(), - ); + context.autoReplace(const LoginRoute()); }, ), ], - ), ), ), diff --git a/mobile/lib/modules/partner/ui/partner_list.dart b/mobile/lib/modules/partner/ui/partner_list.dart index 64db045b1..b1111fdcf 100644 --- a/mobile/lib/modules/partner/ui/partner_list.dart +++ b/mobile/lib/modules/partner/ui/partner_list.dart @@ -1,6 +1,6 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/ui/user_avatar.dart'; @@ -28,10 +28,10 @@ class PartnerList extends HookConsumerWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), - onTap: () => AutoRouter.of(context).push(PartnerDetailRoute(partner: p)), + onTap: () => context.autoPush((PartnerDetailRoute(partner: p))), ); } } diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart index 8a65c25f7..aec6188d4 100644 --- a/mobile/lib/modules/search/ui/curated_people_row.dart +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/shared/models/store.dart'; @@ -85,7 +86,7 @@ class CuratedPeopleRow extends StatelessWidget { "Add name", style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), diff --git a/mobile/lib/modules/search/ui/curated_places_row.dart b/mobile/lib/modules/search/ui/curated_places_row.dart index d62607a80..9c6324daa 100644 --- a/mobile/lib/modules/search/ui/curated_places_row.dart +++ b/mobile/lib/modules/search/ui/curated_places_row.dart @@ -1,5 +1,5 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart'; import 'package:immich_mobile/modules/search/ui/curated_row.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; @@ -25,7 +25,7 @@ class CuratedPlacesRow extends CuratedRow { final int actualContentIndex = isMapEnabled ? 1 : 0; Widget buildMapThumbnail() { return GestureDetector( - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( const MapRoute(), ), child: SizedBox( @@ -43,21 +43,24 @@ class CuratedPlacesRow extends CuratedRow { ), height: imageSize, showAttribution: false, - isDarkTheme: Theme.of(context).brightness == Brightness.dark, + isDarkTheme: context.isDarkTheme, ), ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.black, - gradient: LinearGradient( - begin: FractionalOffset.topCenter, - end: FractionalOffset.bottomCenter, - colors: [ - Colors.blueGrey.withOpacity(0.0), - Colors.black.withOpacity(0.4), - ], - stops: const [0.0, 1.0], + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black, + gradient: LinearGradient( + begin: FractionalOffset.topCenter, + end: FractionalOffset.bottomCenter, + colors: [ + Colors.blueGrey.withOpacity(0.0), + Colors.black.withOpacity(0.4), + ], + stops: const [0.0, 0.4], + ), ), ), ), diff --git a/mobile/lib/modules/search/ui/explore_grid.dart b/mobile/lib/modules/search/ui/explore_grid.dart index 12f8ec6a1..984f65a40 100644 --- a/mobile/lib/modules/search/ui/explore_grid.dart +++ b/mobile/lib/modules/search/ui/explore_grid.dart @@ -1,5 +1,5 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -50,13 +50,13 @@ class ExploreGrid extends StatelessWidget { borderRadius: 0, onTap: () { isPeople - ? AutoRouter.of(context).push( + ? context.autoPush( PersonResultRoute( personId: content.id, personName: content.label, ), ) - : AutoRouter.of(context).push( + : context.autoPush( SearchResultRoute(searchTerm: 'm:${content.label}'), ); }, diff --git a/mobile/lib/modules/search/ui/immich_search_bar.dart b/mobile/lib/modules/search/ui/immich_search_bar.dart index 5ab4bc964..d34a78a77 100644 --- a/mobile/lib/modules/search/ui/immich_search_bar.dart +++ b/mobile/lib/modules/search/ui/immich_search_bar.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; class ImmichSearchBar extends HookConsumerWidget @@ -57,11 +58,11 @@ class ImmichSearchBar extends HookConsumerWidget }, decoration: InputDecoration( hintText: 'search_bar_hint'.tr(), - hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5), - fontWeight: FontWeight.w500, - fontSize: 14, - ), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: context.themeData.colorScheme.onSurface.withOpacity(0.5), + fontWeight: FontWeight.w500, + fontSize: 14, + ), enabledBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Colors.transparent), ), diff --git a/mobile/lib/modules/search/ui/person_name_edit_form.dart b/mobile/lib/modules/search/ui/person_name_edit_form.dart index f60824b5f..0dbb03825 100644 --- a/mobile/lib/modules/search/ui/person_name_edit_form.dart +++ b/mobile/lib/modules/search/ui/person_name_edit_form.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; class PersonNameEditFormResult { @@ -71,7 +72,7 @@ class PersonNameEditForm extends HookConsumerWidget { child: Text( "Save", style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), diff --git a/mobile/lib/modules/search/ui/search_row_title.dart b/mobile/lib/modules/search/ui/search_row_title.dart index 5448874e3..df0f902f9 100644 --- a/mobile/lib/modules/search/ui/search_row_title.dart +++ b/mobile/lib/modules/search/ui/search_row_title.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class SearchRowTitle extends StatelessWidget { final Function() onViewAllPressed; @@ -26,14 +27,14 @@ class SearchRowTitle extends StatelessWidget { children: [ Text( title, - style: Theme.of(context).textTheme.titleSmall, + style: context.textTheme.titleSmall, ), TextButton( onPressed: onViewAllPressed, child: Text( 'search_page_view_all_button', style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14.0, ), diff --git a/mobile/lib/modules/search/ui/search_suggestion_list.dart b/mobile/lib/modules/search/ui/search_suggestion_list.dart index b66be410f..b5f5029c3 100644 --- a/mobile/lib/modules/search/ui/search_suggestion_list.dart +++ b/mobile/lib/modules/search/ui/search_suggestion_list.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; class SearchSuggestionList extends ConsumerWidget { @@ -13,17 +14,16 @@ class SearchSuggestionList extends ConsumerWidget { final searchTerm = ref.watch(searchPageStateProvider).searchTerm; final searchSuggestion = ref.watch(searchPageStateProvider).searchSuggestion; - var isDarkTheme = Theme.of(context).brightness == Brightness.dark; return Container( color: searchTerm.isEmpty ? Colors.black.withOpacity(0.5) - : Theme.of(context).scaffoldBackgroundColor, + : context.scaffoldBackgroundColor, child: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Container( - color: isDarkTheme ? Colors.grey[800] : Colors.grey[100], + color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[100], child: Padding( padding: const EdgeInsets.all(16.0), child: RichText( @@ -31,14 +31,14 @@ class SearchSuggestionList extends ConsumerWidget { children: [ TextSpan( text: 'search_suggestion_list_smart_search_hint_1'.tr(), - style: Theme.of(context).textTheme.bodyMedium, + style: context.textTheme.bodyMedium, ), TextSpan( text: 'search_suggestion_list_smart_search_hint_2'.tr(), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - ), + style: context.textTheme.bodyMedium?.copyWith( + color: context.primaryColor, + fontWeight: FontWeight.bold, + ), ), ], ), diff --git a/mobile/lib/modules/search/ui/thumbnail_with_info.dart b/mobile/lib/modules/search/ui/thumbnail_with_info.dart index bbb7e6834..16714d830 100644 --- a/mobile/lib/modules/search/ui/thumbnail_with_info.dart +++ b/mobile/lib/modules/search/ui/thumbnail_with_info.dart @@ -1,7 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; -import 'package:immich_mobile/utils/capitalize.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; // ignore: must_be_immutable class ThumbnailWithInfo extends StatelessWidget { @@ -22,8 +23,8 @@ class ThumbnailWithInfo extends StatelessWidget { @override Widget build(BuildContext context) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; - var textAndIconColor = isDarkMode ? Colors.grey[100] : Colors.grey[700]; + var textAndIconColor = + context.isDarkTheme ? Colors.grey[100] : Colors.grey[700]; return GestureDetector( onTap: () { onTap(); @@ -34,7 +35,7 @@ class ThumbnailWithInfo extends StatelessWidget { Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderRadius), - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100], ), child: imageUrl != null ? ClipRRect( diff --git a/mobile/lib/modules/search/views/all_motion_videos_page.dart b/mobile/lib/modules/search/views/all_motion_videos_page.dart index ba990a7da..2ad53ec45 100644 --- a/mobile/lib/modules/search/views/all_motion_videos_page.dart +++ b/mobile/lib/modules/search/views/all_motion_videos_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; @@ -17,7 +17,7 @@ class AllMotionPhotosPage extends HookConsumerWidget { appBar: AppBar( title: const Text('motion_photos_page_title').tr(), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), ), diff --git a/mobile/lib/modules/search/views/all_people_page.dart b/mobile/lib/modules/search/views/all_people_page.dart index d3361fc30..3cbedc949 100644 --- a/mobile/lib/modules/search/views/all_people_page.dart +++ b/mobile/lib/modules/search/views/all_people_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; @@ -19,13 +19,13 @@ class AllPeoplePage extends HookConsumerWidget { title: Text( 'all_people_page_title', style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16.0, ), ).tr(), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), ), diff --git a/mobile/lib/modules/search/views/all_videos_page.dart b/mobile/lib/modules/search/views/all_videos_page.dart index 9461869ae..beb604fd0 100644 --- a/mobile/lib/modules/search/views/all_videos_page.dart +++ b/mobile/lib/modules/search/views/all_videos_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; @@ -17,7 +17,7 @@ class AllVideosPage extends HookConsumerWidget { appBar: AppBar( title: const Text('all_videos_page_title').tr(), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), ), diff --git a/mobile/lib/modules/search/views/curated_location_page.dart b/mobile/lib/modules/search/views/curated_location_page.dart index 59e4c3a87..d9176e178 100644 --- a/mobile/lib/modules/search/views/curated_location_page.dart +++ b/mobile/lib/modules/search/views/curated_location_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; @@ -21,13 +21,13 @@ class CuratedLocationPage extends HookConsumerWidget { title: Text( 'curated_location_page_title', style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16.0, ), ).tr(), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), ), diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart index 01483f0bd..bec5e4f1b 100644 --- a/mobile/lib/modules/search/views/person_result_page.dart +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -1,7 +1,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/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; @@ -40,7 +40,7 @@ class PersonResultPage extends HookConsumerWidget { void buildBottomSheet() { showModalBottomSheet( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: context.scaffoldBackgroundColor, isScrollControlled: false, context: context, useSafeArea: true, @@ -73,13 +73,13 @@ class PersonResultPage extends HookConsumerWidget { children: [ Text( 'Add a name', - style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), + style: context.textTheme.titleSmall?.copyWith( + color: context.themeData.colorScheme.secondary, + ), ), Text( 'Find them fast by name with search', - style: Theme.of(context).textTheme.labelSmall, + style: context.textTheme.labelSmall, ), ], ), @@ -91,7 +91,7 @@ class PersonResultPage extends HookConsumerWidget { children: [ Text( name.value, - style: Theme.of(context).textTheme.titleLarge, + style: context.textTheme.titleLarge, ), ], ); @@ -101,7 +101,7 @@ class PersonResultPage extends HookConsumerWidget { appBar: AppBar( title: Text(name.value), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), actions: [ diff --git a/mobile/lib/modules/search/views/recently_added_page.dart b/mobile/lib/modules/search/views/recently_added_page.dart index fc079fbfc..55e920615 100644 --- a/mobile/lib/modules/search/views/recently_added_page.dart +++ b/mobile/lib/modules/search/views/recently_added_page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/recently_added.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; @@ -17,7 +17,7 @@ class RecentlyAddedPage extends HookConsumerWidget { appBar: AppBar( title: const Text('recently_added_page_title').tr(), leading: IconButton( - onPressed: () => AutoRouter.of(context).pop(), + onPressed: () => context.autoPop(), icon: const Icon(Icons.arrow_back_ios_rounded), ), ), diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index 1110555a3..c06db53a3 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -1,9 +1,9 @@ import 'dart:math' as math; -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; @@ -30,15 +30,14 @@ class SearchPage extends HookConsumerWidget { final curatedPeople = ref.watch(getCuratedPeopleProvider); final isMapEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.map)); - var isDarkTheme = Theme.of(context).brightness == Brightness.dark; - double imageSize = math.min(MediaQuery.of(context).size.width / 3, 150); + double imageSize = math.min(context.width / 3, 150); TextStyle categoryTitleStyle = const TextStyle( fontWeight: FontWeight.bold, fontSize: 14.0, ); - Color categoryIconColor = isDarkTheme ? Colors.white : Colors.black; + Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black; useEffect( () { @@ -52,7 +51,7 @@ class SearchPage extends HookConsumerWidget { searchFocusNode.unfocus(); ref.watch(searchPageStateProvider.notifier).disableSearch(); - AutoRouter.of(context).push( + context.autoPush( SearchResultRoute( searchTerm: searchTerm, ), @@ -88,7 +87,7 @@ class SearchPage extends HookConsumerWidget { .take(12) .toList(), onTap: (content, index) { - AutoRouter.of(context).push( + context.autoPush( PersonResultRoute( personId: content.id, personName: content.label, @@ -121,7 +120,7 @@ class SearchPage extends HookConsumerWidget { .toList(), imageSize: imageSize, onTap: (content, index) { - AutoRouter.of(context).push( + context.autoPush( SearchResultRoute( searchTerm: 'm:${content.label}', ), @@ -148,16 +147,14 @@ class SearchPage extends HookConsumerWidget { children: [ SearchRowTitle( title: "search_page_people".tr(), - onViewAllPressed: () => AutoRouter.of(context).push( - const AllPeopleRoute(), - ), + onViewAllPressed: () => + context.autoPush(const AllPeopleRoute()), ), buildPeople(), SearchRowTitle( title: "search_page_places".tr(), - onViewAllPressed: () => AutoRouter.of(context).push( - const CuratedLocationRoute(), - ), + onViewAllPressed: () => + context.autoPush(const CuratedLocationRoute()), top: 0, ), const SizedBox(height: 10.0), @@ -167,20 +164,18 @@ class SearchPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( 'search_page_your_activity', - style: Theme.of(context).textTheme.titleSmall, + style: context.textTheme.titleSmall, ).tr(), ), ListTile( leading: Icon( - Icons.star_outline, + Icons.favorite_border_rounded, color: categoryIconColor, ), title: Text('search_page_favorites', style: categoryTitleStyle) .tr(), - onTap: () => AutoRouter.of(context).push( - const FavoritesRoute(), - ), + onTap: () => context.autoPush(const FavoritesRoute()), ), const CategoryDivider(), ListTile( @@ -192,16 +187,14 @@ class SearchPage extends HookConsumerWidget { 'search_page_recently_added', style: categoryTitleStyle, ).tr(), - onTap: () => AutoRouter.of(context).push( - const RecentlyAddedRoute(), - ), + onTap: () => context.autoPush(const RecentlyAddedRoute()), ), const SizedBox(height: 24.0), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( 'search_page_categories', - style: Theme.of(context).textTheme.titleSmall, + style: context.textTheme.titleSmall, ).tr(), ), ListTile( @@ -210,7 +203,7 @@ class SearchPage extends HookConsumerWidget { Icons.screenshot, color: categoryIconColor, ), - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( SearchResultRoute( searchTerm: 'screenshots', ), @@ -224,7 +217,7 @@ class SearchPage extends HookConsumerWidget { Icons.photo_camera_front_outlined, color: categoryIconColor, ), - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( SearchResultRoute( searchTerm: 'selfies', ), @@ -238,9 +231,7 @@ class SearchPage extends HookConsumerWidget { Icons.play_circle_outline, color: categoryIconColor, ), - onTap: () => AutoRouter.of(context).push( - const AllVideosRoute(), - ), + onTap: () => context.autoPush(const AllVideosRoute()), ), const CategoryDivider(), ListTile( @@ -252,9 +243,7 @@ class SearchPage extends HookConsumerWidget { Icons.motion_photos_on_outlined, color: categoryIconColor, ), - onTap: () => AutoRouter.of(context).push( - const AllMotionPhotosRoute(), - ), + onTap: () => context.autoPush(const AllMotionPhotosRoute()), ), ], ), diff --git a/mobile/lib/modules/search/views/search_result_page.dart b/mobile/lib/modules/search/views/search_result_page.dart index d6b1ea9a9..fd16c2c06 100644 --- a/mobile/lib/modules/search/views/search_result_page.dart +++ b/mobile/lib/modules/search/views/search_result_page.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_result_page.provider.dart'; @@ -38,7 +38,6 @@ class SearchResultPage extends HookConsumerWidget { final searchTermController = useTextEditingController(text: ""); final isNewSearch = useState(false); final currentSearchTerm = useState(searchTerm); - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; final isDisplayDateGroup = useState(true); FocusNode? searchFocusNode; @@ -112,8 +111,9 @@ class SearchResultPage extends HookConsumerWidget { hintStyle: TextStyle( fontWeight: FontWeight.bold, fontSize: 16.0, - color: - isDarkTheme ? Colors.grey[500] : Colors.black.withOpacity(0.5), + color: context.isDarkTheme + ? Colors.grey[500] + : Colors.black.withOpacity(0.5), ), ), ); @@ -130,7 +130,7 @@ class SearchResultPage extends HookConsumerWidget { Text( currentSearchTerm.value, style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontSize: 13, fontWeight: FontWeight.bold, ), @@ -138,12 +138,12 @@ class SearchResultPage extends HookConsumerWidget { ), Icon( Icons.close_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, size: 20, ), ], ), - backgroundColor: Theme.of(context).primaryColor.withAlpha(50), + backgroundColor: context.primaryColor.withAlpha(50), ); } @@ -185,7 +185,7 @@ class SearchResultPage extends HookConsumerWidget { if (isNewSearch.value) { isNewSearch.value = false; } else { - AutoRouter.of(context).pop(true); + context.autoPop(true); } }, icon: const Icon(Icons.arrow_back_ios_rounded), diff --git a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart index 3c8d47d1e..83bf4bb94 100644 --- a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart +++ b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; @@ -43,7 +44,7 @@ class AdvancedSettings extends HookConsumerWidget { final logLevel = Level.LEVELS[levelId.value].name; return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( "advanced_settings_tile_title", style: TextStyle( @@ -86,7 +87,7 @@ class AdvancedSettings extends HookConsumerWidget { min: 1.0, divisions: 7, label: logLevel, - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, ), ), SettingsSwitchListTile( diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_layout_settings.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_layout_settings.dart index 8ff719da3..856935ccb 100644 --- a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_layout_settings.dart +++ b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_layout_settings.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; @@ -50,12 +51,10 @@ class LayoutSettings extends HookConsumerWidget { return Column( children: [ SwitchListTile.adaptive( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( "asset_list_layout_settings_dynamic_layout_title", - style: Theme.of(context) - .textTheme - .labelLarge + style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), ).tr(), onChanged: switchChanged, @@ -75,10 +74,10 @@ class LayoutSettings extends HookConsumerWidget { ).tr(), ), RadioListTile( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( "asset_list_layout_settings_group_by_month_day", - style: Theme.of(context).textTheme.labelLarge, + style: context.textTheme.labelLarge, ).tr(), value: GroupAssetsBy.day, groupValue: groupBy.value, @@ -86,10 +85,10 @@ class LayoutSettings extends HookConsumerWidget { controlAffinity: ListTileControlAffinity.trailing, ), RadioListTile( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( "asset_list_layout_settings_group_by_month", - style: Theme.of(context).textTheme.labelLarge, + style: context.textTheme.labelLarge, ).tr(), value: GroupAssetsBy.month, groupValue: groupBy.value, @@ -97,10 +96,10 @@ class LayoutSettings extends HookConsumerWidget { controlAffinity: ListTileControlAffinity.trailing, ), RadioListTile( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( "asset_list_layout_settings_group_automatically", - style: Theme.of(context).textTheme.labelLarge, + style: context.textTheme.labelLarge, ).tr(), value: GroupAssetsBy.auto, groupValue: groupBy.value, diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart index ef7afb043..a7a2f8b95 100644 --- a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart +++ b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_layout_settings.dart'; import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart'; import 'asset_list_tiles_per_row.dart'; @@ -12,7 +13,7 @@ class AssetListSettings extends StatelessWidget { @override Widget build(BuildContext context) { return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( 'asset_list_settings_title', style: TextStyle( diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart index ae0e02148..50896fe0b 100644 --- a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart +++ b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; @@ -33,13 +34,11 @@ class StorageIndicator extends HookConsumerWidget { ); return SwitchListTile.adaptive( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( "theme_setting_asset_list_storage_indicator_title", - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(fontWeight: FontWeight.bold), + style: + context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), ).tr(), onChanged: switchChanged, value: showStorageIndicator.value, diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart index 89ac79133..5693e2a5f 100644 --- a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart +++ b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; @@ -51,7 +52,7 @@ class TilesPerRow extends HookConsumerWidget { max: 6, divisions: 4, label: "${itemsValue.value.toInt()}", - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, ), ], ); diff --git a/mobile/lib/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart b/mobile/lib/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart index 123c7cd00..d0b86a186 100644 --- a/mobile/lib/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart +++ b/mobile/lib/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart'; @@ -26,7 +27,7 @@ class ImageViewerQualitySetting extends HookConsumerWidget { ); return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( 'theme_setting_image_viewer_quality_title', style: TextStyle( diff --git a/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart b/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart index a9321a56e..cd753de31 100644 --- a/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart +++ b/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; @@ -25,7 +26,7 @@ class LocalStorageSettings extends HookConsumerWidget { } return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( "cache_settings_tile_title", style: TextStyle( @@ -42,9 +43,7 @@ class LocalStorageSettings extends HookConsumerWidget { ListTile( title: Text( "Duplicated Assets (${cacheItemCount.value})", - style: Theme.of(context) - .textTheme - .labelLarge + style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), ).tr(), subtitle: const Text( diff --git a/mobile/lib/modules/settings/ui/notification_setting/notification_setting.dart b/mobile/lib/modules/settings/ui/notification_setting/notification_setting.dart index 5f00cd0d9..747c541d4 100644 --- a/mobile/lib/modules/settings/ui/notification_setting/notification_setting.dart +++ b/mobile/lib/modules/settings/ui/notification_setting/notification_setting.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; @@ -49,12 +50,12 @@ class NotificationSetting extends HookConsumerWidget { actions: [ TextButton( child: const Text('notification_permission_dialog_cancel').tr(), - onPressed: () => Navigator.of(context).pop(), + onPressed: () => context.pop(), ), TextButton( child: const Text('notification_permission_dialog_settings').tr(), onPressed: () { - Navigator.of(context).pop(); + context.pop(); openAppSettings(); }, ), @@ -65,7 +66,7 @@ class NotificationSetting extends HookConsumerWidget { final String formattedValue = _formatSliderValue(sliderValue.value); return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( 'setting_notifications_title', style: TextStyle( @@ -84,9 +85,7 @@ class NotificationSetting extends HookConsumerWidget { leading: const Icon(Icons.notifications_outlined), title: Text( 'notification_permission_list_tile_title', - style: Theme.of(context) - .textTheme - .labelLarge + style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), ).tr(), subtitle: Column( @@ -94,7 +93,7 @@ class NotificationSetting extends HookConsumerWidget { children: [ Text( 'notification_permission_list_tile_content', - style: Theme.of(context).textTheme.labelMedium, + style: context.textTheme.labelMedium, ).tr(), const SizedBox(height: 8), ElevatedButton( @@ -149,7 +148,7 @@ class NotificationSetting extends HookConsumerWidget { max: 5.0, divisions: 5, label: formattedValue, - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, ), ), ], diff --git a/mobile/lib/modules/settings/ui/settings_switch_list_tile.dart b/mobile/lib/modules/settings/ui/settings_switch_list_tile.dart index c6fff19f6..e66e6319c 100644 --- a/mobile/lib/modules/settings/ui/settings_switch_list_tile.dart +++ b/mobile/lib/modules/settings/ui/settings_switch_list_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; class SettingsSwitchListTile extends StatelessWidget { @@ -23,7 +24,7 @@ class SettingsSwitchListTile extends StatelessWidget { @override Widget build(BuildContext context) { return SwitchListTile.adaptive( - selectedTileColor: enabled ? null : Theme.of(context).disabledColor, + selectedTileColor: enabled ? null : context.themeData.disabledColor, value: valueNotifier.value, onChanged: (bool value) { if (enabled) { @@ -34,16 +35,13 @@ class SettingsSwitchListTile extends StatelessWidget { onChanged!(value); } }, - activeColor: enabled - ? Theme.of(context).primaryColor - : Theme.of(context).disabledColor, + activeColor: + enabled ? context.primaryColor : context.themeData.disabledColor, dense: true, title: Text( title, - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(fontWeight: FontWeight.bold), + style: + context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), ), subtitle: subtitle != null ? Text(subtitle!) : null, ); diff --git a/mobile/lib/modules/settings/ui/theme_setting/theme_setting.dart b/mobile/lib/modules/settings/ui/theme_setting/theme_setting.dart index 2e3b7134c..fd25873cd 100644 --- a/mobile/lib/modules/settings/ui/theme_setting/theme_setting.dart +++ b/mobile/lib/modules/settings/ui/theme_setting/theme_setting.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart'; @@ -24,7 +25,7 @@ class ThemeSetting extends HookConsumerWidget { ); return ExpansionTile( - textColor: Theme.of(context).primaryColor, + textColor: context.primaryColor, title: const Text( 'theme_setting_theme_title', style: TextStyle( @@ -39,12 +40,10 @@ class ThemeSetting extends HookConsumerWidget { ).tr(), children: [ SwitchListTile.adaptive( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( 'theme_setting_system_theme_switch', - style: Theme.of(context) - .textTheme - .labelLarge + style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), ).tr(), value: currentTheme.value == ThemeMode.system, @@ -77,12 +76,10 @@ class ThemeSetting extends HookConsumerWidget { ), if (currentTheme.value != ThemeMode.system) SwitchListTile.adaptive( - activeColor: Theme.of(context).primaryColor, + activeColor: context.primaryColor, title: Text( 'theme_setting_dark_mode_switch', - style: Theme.of(context) - .textTheme - .labelLarge + style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), ).tr(), value: ref.watch(immichThemeProvider) == ThemeMode.dark, diff --git a/mobile/lib/modules/shared_link/ui/shared_link_item.dart b/mobile/lib/modules/shared_link/ui/shared_link_item.dart index 907006f77..5b14c4ab1 100644 --- a/mobile/lib/modules/shared_link/ui/shared_link_item.dart +++ b/mobile/lib/modules/shared_link/ui/shared_link_item.dart @@ -1,9 +1,9 @@ import 'dart:math' as math; -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; @@ -58,12 +58,12 @@ class SharedLinkItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final themeData = Theme.of(context); + final themeData = context.themeData; final isDarkMode = themeData.brightness == Brightness.dark; final thumbnailUrl = sharedLink.thumbAssetId != null ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!) : null; - final imageSize = math.min(MediaQuery.of(context).size.width / 4, 100.0); + final imageSize = math.min(context.width / 4, 100.0); void copyShareLinkToClipboard() { final serverUrl = getServerUrl(); @@ -194,8 +194,8 @@ class SharedLinkItem extends ConsumerWidget { tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part ), - onPressed: () => AutoRouter.of(context) - .push(SharedLinkEditRoute(existingLink: sharedLink)), + onPressed: () => + context.autoPush(SharedLinkEditRoute(existingLink: sharedLink)), ), IconButton( splashRadius: 25, diff --git a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart index 499b2c29d..71f2648c0 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart @@ -1,10 +1,10 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/services/shared_link.service.dart'; @@ -26,7 +26,7 @@ class SharedLinkEditPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { const padding = 20.0; - final themeData = Theme.of(context); + final themeData = context.themeData; final descriptionController = useTextEditingController(text: existingLink?.description ?? ""); final descriptionFocusNode = useFocusNode(); @@ -206,9 +206,16 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildExpiryAfterButton() { return DropdownMenu( + label: Text( + "shared_link_edit_expire_after", + style: TextStyle( + fontWeight: FontWeight.bold, + color: themeData.primaryColor, + ), + ).tr(), enableSearch: false, enableFilter: false, - width: MediaQuery.of(context).size.width - 40, + width: context.width - 40, initialSelection: expiryAfter.value, enabled: newShareLink.value.isEmpty && (existingLink == null || editExpiry.value), @@ -300,7 +307,7 @@ class SharedLinkEditPage extends HookConsumerWidget { alignment: Alignment.bottomRight, child: ElevatedButton( onPressed: () { - AutoRouter.of(context).pop(); + context.autoPop(); }, child: const Text( "Done", @@ -396,7 +403,7 @@ class SharedLinkEditPage extends HookConsumerWidget { changeExpiry: changeExpiry, ); ref.invalidate(sharedLinksStateProvider); - AutoRouter.of(context).pop(); + context.autoPop(); } return Scaffold( diff --git a/mobile/lib/modules/shared_link/views/shared_link_page.dart b/mobile/lib/modules/shared_link/views/shared_link_page.dart index 19bede4bd..04f57a48b 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_page.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/ui/shared_link_item.dart'; @@ -52,7 +53,7 @@ class SharedLinkPage extends HookConsumerWidget { child: Icon( Icons.link_off, size: 100, - color: Theme.of(context).iconTheme.color?.withOpacity(0.5), + color: context.themeData.iconTheme.color?.withOpacity(0.5), ), ), ), diff --git a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart index 2d85625a4..d0ad41b5f 100644 --- a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart +++ b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart @@ -37,6 +37,7 @@ class TrashNotifier extends StateNotifier { .remoteIdProperty() .findAll(); + // TODO: handle local asset removal on emptyTrash _ref .read(syncServiceProvider) .handleRemoteAssetRemoval(idsToRemove.cast().toList()); diff --git a/mobile/lib/modules/trash/views/trash_page.dart b/mobile/lib/modules/trash/views/trash_page.dart index 8c128b61d..ad365189e 100644 --- a/mobile/lib/modules/trash/views/trash_page.dart +++ b/mobile/lib/modules/trash/views/trash_page.dart @@ -1,9 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/delete_dialog.dart'; import 'package:immich_mobile/modules/trash/providers/trashed_asset.provider.dart'; @@ -137,7 +137,7 @@ class TrashPage extends HookConsumerWidget { return AppBar( leading: IconButton( onPressed: !selectionEnabledHook.value - ? () => AutoRouter.of(context).pop() + ? () => context.autoPop() : () { selectionEnabledHook.value = false; selection.value = {}; @@ -177,7 +177,7 @@ class TrashPage extends HookConsumerWidget { child: SizedBox( height: 64, child: Container( - color: Theme.of(context).canvasColor, + color: context.themeData.canvasColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index a4cc2401f..01d54082e 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/activities/views/activities_page.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/views/album_options_part.dart'; import 'package:immich_mobile/modules/album/views/album_viewer_page.dart'; @@ -160,6 +161,12 @@ part 'router.gr.dart'; AutoRoute(page: TrashPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SharedLinkPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SharedLinkEditPage, guards: [AuthGuard, DuplicateGuard]), + CustomRoute( + page: ActivitiesPage, + guards: [AuthGuard, DuplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + durationInMilliseconds: 200, + ), ], ) class AppRouter extends _$AppRouter { diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index b5b5b773a..c6a54ffcd 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -73,6 +73,7 @@ class _$AppRouter extends RootStackRouter { heroOffset: args.heroOffset, showStack: args.showStack, isOwner: args.isOwner, + sharedAlbumId: args.sharedAlbumId, ), ); }, @@ -337,6 +338,25 @@ class _$AppRouter extends RootStackRouter { ), ); }, + ActivitiesRoute.name: (routeData) { + final args = routeData.argsAs(); + return CustomPage( + routeData: routeData, + child: ActivitiesPage( + args.albumId, + appBarTitle: args.appBarTitle, + assetId: args.assetId, + withAssetThumbs: args.withAssetThumbs, + isOwner: args.isOwner, + isReadOnly: args.isReadOnly, + key: args.key, + ), + transitionsBuilder: TransitionsBuilders.slideLeft, + durationInMilliseconds: 200, + opaque: true, + barrierDismissible: false, + ); + }, HomeRoute.name: (routeData) { return MaterialPageX( routeData: routeData, @@ -674,6 +694,14 @@ class _$AppRouter extends RootStackRouter { duplicateGuard, ], ), + RouteConfig( + ActivitiesRoute.name, + path: '/activities-page', + guards: [ + authGuard, + duplicateGuard, + ], + ), ]; } @@ -749,6 +777,7 @@ class GalleryViewerRoute extends PageRouteInfo { int heroOffset = 0, bool showStack = false, bool isOwner = true, + String? sharedAlbumId, }) : super( GalleryViewerRoute.name, path: '/gallery-viewer-page', @@ -760,6 +789,7 @@ class GalleryViewerRoute extends PageRouteInfo { heroOffset: heroOffset, showStack: showStack, isOwner: isOwner, + sharedAlbumId: sharedAlbumId, ), ); @@ -775,6 +805,7 @@ class GalleryViewerRouteArgs { this.heroOffset = 0, this.showStack = false, this.isOwner = true, + this.sharedAlbumId, }); final Key? key; @@ -791,9 +822,11 @@ class GalleryViewerRouteArgs { final bool isOwner; + final String? sharedAlbumId; + @override String toString() { - return 'GalleryViewerRouteArgs{key: $key, initialIndex: $initialIndex, loadAsset: $loadAsset, totalAssets: $totalAssets, heroOffset: $heroOffset, showStack: $showStack, isOwner: $isOwner}'; + return 'GalleryViewerRouteArgs{key: $key, initialIndex: $initialIndex, loadAsset: $loadAsset, totalAssets: $totalAssets, heroOffset: $heroOffset, showStack: $showStack, isOwner: $isOwner, sharedAlbumId: $sharedAlbumId}'; } } @@ -1527,6 +1560,65 @@ class SharedLinkEditRouteArgs { } } +/// generated route for +/// [ActivitiesPage] +class ActivitiesRoute extends PageRouteInfo { + ActivitiesRoute({ + required String albumId, + String appBarTitle = "", + String? assetId, + bool withAssetThumbs = true, + bool isOwner = false, + bool isReadOnly = false, + Key? key, + }) : super( + ActivitiesRoute.name, + path: '/activities-page', + args: ActivitiesRouteArgs( + albumId: albumId, + appBarTitle: appBarTitle, + assetId: assetId, + withAssetThumbs: withAssetThumbs, + isOwner: isOwner, + isReadOnly: isReadOnly, + key: key, + ), + ); + + static const String name = 'ActivitiesRoute'; +} + +class ActivitiesRouteArgs { + const ActivitiesRouteArgs({ + required this.albumId, + this.appBarTitle = "", + this.assetId, + this.withAssetThumbs = true, + this.isOwner = false, + this.isReadOnly = false, + this.key, + }); + + final String albumId; + + final String appBarTitle; + + final String? assetId; + + final bool withAssetThumbs; + + final bool isOwner; + + final bool isReadOnly; + + final Key? key; + + @override + String toString() { + return 'ActivitiesRouteArgs{albumId: $albumId, appBarTitle: $appBarTitle, assetId: $assetId, withAssetThumbs: $withAssetThumbs, isOwner: $isOwner, isReadOnly: $isReadOnly, key: $key}'; + } +} + /// generated route for /// [HomePage] class HomeRoute extends PageRouteInfo { diff --git a/mobile/lib/shared/models/album.dart b/mobile/lib/shared/models/album.dart index f338abca3..ceba60742 100644 --- a/mobile/lib/shared/models/album.dart +++ b/mobile/lib/shared/models/album.dart @@ -22,6 +22,7 @@ class Album { this.endDate, this.lastModifiedAssetTimestamp, required this.shared, + required this.activityEnabled, }); Id id = Isar.autoIncrement; @@ -36,6 +37,7 @@ class Album { DateTime? endDate; DateTime? lastModifiedAssetTimestamp; bool shared; + bool activityEnabled; final IsarLink owner = IsarLink(); final IsarLink thumbnail = IsarLink(); final IsarLinks sharedUsers = IsarLinks(); @@ -77,7 +79,8 @@ class Album { } Stream watchRenderList(GroupAssetsBy groupAssetsBy) async* { - final query = assets.filter().sortByFileCreatedAtDesc(); + final query = + assets.filter().isTrashedEqualTo(false).sortByFileCreatedAtDesc(); _renderList = await RenderList.fromQuery(query, groupAssetsBy); yield _renderList; await for (final _ in query.watchLazy()) { @@ -105,6 +108,7 @@ class Album { modifiedAt.isAtSameMomentAs(other.modifiedAt) && lastModifiedAssetTimestampIsSetAndEqual && shared == other.shared && + activityEnabled == other.activityEnabled && owner.value == other.owner.value && thumbnail.value == other.thumbnail.value && sharedUsers.length == other.sharedUsers.length && @@ -122,6 +126,7 @@ class Album { modifiedAt.hashCode ^ lastModifiedAssetTimestamp.hashCode ^ shared.hashCode ^ + activityEnabled.hashCode ^ owner.value.hashCode ^ thumbnail.value.hashCode ^ sharedUsers.length.hashCode ^ @@ -133,6 +138,7 @@ class Album { createdAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(), modifiedAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(), shared: false, + activityEnabled: false, ); a.owner.value = Store.get(StoreKey.currentUser); a.localId = ape.id; @@ -150,6 +156,7 @@ class Album { shared: dto.shared, startDate: dto.startDate, endDate: dto.endDate, + activityEnabled: dto.isActivityEnabled, ); a.owner.value = await db.users.getById(dto.ownerId); if (dto.albumThumbnailAssetId != null) { diff --git a/mobile/lib/shared/models/album.g.dart b/mobile/lib/shared/models/album.g.dart index 9cdb59a5e..e9fcc49aa 100644 --- a/mobile/lib/shared/models/album.g.dart +++ b/mobile/lib/shared/models/album.g.dart @@ -17,48 +17,53 @@ const AlbumSchema = CollectionSchema( name: r'Album', id: -1355968412107120937, properties: { - r'createdAt': PropertySchema( + r'activityEnabled': PropertySchema( id: 0, + name: r'activityEnabled', + type: IsarType.bool, + ), + r'createdAt': PropertySchema( + id: 1, name: r'createdAt', type: IsarType.dateTime, ), r'endDate': PropertySchema( - id: 1, + id: 2, name: r'endDate', type: IsarType.dateTime, ), r'lastModifiedAssetTimestamp': PropertySchema( - id: 2, + id: 3, name: r'lastModifiedAssetTimestamp', type: IsarType.dateTime, ), r'localId': PropertySchema( - id: 3, + id: 4, name: r'localId', type: IsarType.string, ), r'modifiedAt': PropertySchema( - id: 4, + id: 5, name: r'modifiedAt', type: IsarType.dateTime, ), r'name': PropertySchema( - id: 5, + id: 6, name: r'name', type: IsarType.string, ), r'remoteId': PropertySchema( - id: 6, + id: 7, name: r'remoteId', type: IsarType.string, ), r'shared': PropertySchema( - id: 7, + id: 8, name: r'shared', type: IsarType.bool, ), r'startDate': PropertySchema( - id: 8, + id: 9, name: r'startDate', type: IsarType.dateTime, ) @@ -157,15 +162,16 @@ void _albumSerialize( List offsets, Map> allOffsets, ) { - writer.writeDateTime(offsets[0], object.createdAt); - writer.writeDateTime(offsets[1], object.endDate); - writer.writeDateTime(offsets[2], object.lastModifiedAssetTimestamp); - writer.writeString(offsets[3], object.localId); - writer.writeDateTime(offsets[4], object.modifiedAt); - writer.writeString(offsets[5], object.name); - writer.writeString(offsets[6], object.remoteId); - writer.writeBool(offsets[7], object.shared); - writer.writeDateTime(offsets[8], object.startDate); + writer.writeBool(offsets[0], object.activityEnabled); + writer.writeDateTime(offsets[1], object.createdAt); + writer.writeDateTime(offsets[2], object.endDate); + writer.writeDateTime(offsets[3], object.lastModifiedAssetTimestamp); + writer.writeString(offsets[4], object.localId); + writer.writeDateTime(offsets[5], object.modifiedAt); + writer.writeString(offsets[6], object.name); + writer.writeString(offsets[7], object.remoteId); + writer.writeBool(offsets[8], object.shared); + writer.writeDateTime(offsets[9], object.startDate); } Album _albumDeserialize( @@ -175,15 +181,16 @@ Album _albumDeserialize( Map> allOffsets, ) { final object = Album( - createdAt: reader.readDateTime(offsets[0]), - endDate: reader.readDateTimeOrNull(offsets[1]), - lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[2]), - localId: reader.readStringOrNull(offsets[3]), - modifiedAt: reader.readDateTime(offsets[4]), - name: reader.readString(offsets[5]), - remoteId: reader.readStringOrNull(offsets[6]), - shared: reader.readBool(offsets[7]), - startDate: reader.readDateTimeOrNull(offsets[8]), + activityEnabled: reader.readBool(offsets[0]), + createdAt: reader.readDateTime(offsets[1]), + endDate: reader.readDateTimeOrNull(offsets[2]), + lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[3]), + localId: reader.readStringOrNull(offsets[4]), + modifiedAt: reader.readDateTime(offsets[5]), + name: reader.readString(offsets[6]), + remoteId: reader.readStringOrNull(offsets[7]), + shared: reader.readBool(offsets[8]), + startDate: reader.readDateTimeOrNull(offsets[9]), ); object.id = id; return object; @@ -197,22 +204,24 @@ P _albumDeserializeProp

( ) { switch (propertyId) { case 0: - return (reader.readDateTime(offset)) as P; + return (reader.readBool(offset)) as P; case 1: - return (reader.readDateTimeOrNull(offset)) as P; + return (reader.readDateTime(offset)) as P; case 2: return (reader.readDateTimeOrNull(offset)) as P; case 3: - return (reader.readStringOrNull(offset)) as P; + return (reader.readDateTimeOrNull(offset)) as P; case 4: - return (reader.readDateTime(offset)) as P; - case 5: - return (reader.readString(offset)) as P; - case 6: return (reader.readStringOrNull(offset)) as P; + case 5: + return (reader.readDateTime(offset)) as P; + case 6: + return (reader.readString(offset)) as P; case 7: - return (reader.readBool(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 8: + return (reader.readBool(offset)) as P; + case 9: return (reader.readDateTimeOrNull(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -442,6 +451,16 @@ extension AlbumQueryWhere on QueryBuilder { } extension AlbumQueryFilter on QueryBuilder { + QueryBuilder activityEnabledEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'activityEnabled', + value: value, + )); + }); + } + QueryBuilder createdAtEqualTo( DateTime value) { return QueryBuilder.apply(this, (query) { @@ -1385,6 +1404,18 @@ extension AlbumQueryLinks on QueryBuilder { } extension AlbumQuerySortBy on QueryBuilder { + QueryBuilder sortByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.asc); + }); + } + + QueryBuilder sortByActivityEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.desc); + }); + } + QueryBuilder sortByCreatedAt() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'createdAt', Sort.asc); @@ -1496,6 +1527,18 @@ extension AlbumQuerySortBy on QueryBuilder { } extension AlbumQuerySortThenBy on QueryBuilder { + QueryBuilder thenByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.asc); + }); + } + + QueryBuilder thenByActivityEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.desc); + }); + } + QueryBuilder thenByCreatedAt() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'createdAt', Sort.asc); @@ -1619,6 +1662,12 @@ extension AlbumQuerySortThenBy on QueryBuilder { } extension AlbumQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'activityEnabled'); + }); + } + QueryBuilder distinctByCreatedAt() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'createdAt'); @@ -1684,6 +1733,12 @@ extension AlbumQueryProperty on QueryBuilder { }); } + QueryBuilder activityEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'activityEnabled'); + }); + } + QueryBuilder createdAtProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'createdAt'); diff --git a/mobile/lib/shared/models/asset.dart b/mobile/lib/shared/models/asset.dart index e1a3f24cd..31b243e4d 100644 --- a/mobile/lib/shared/models/asset.dart +++ b/mobile/lib/shared/models/asset.dart @@ -6,7 +6,7 @@ import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; import 'package:openapi/api.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:immich_mobile/utils/builtin_extensions.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:path/path.dart' as p; part 'asset.g.dart'; @@ -419,12 +419,12 @@ class Asset { "type": "$type", "fileName": "$fileName", "isFavorite": $isFavorite, - "isRemote: $isRemote, + "isRemote": $isRemote, "storage": "$storage", "width": ${width ?? "N/A"}, "height": ${height ?? "N/A"}, "isArchived": $isArchived, - "isTrashed": $isTrashed, + "isTrashed": $isTrashed }"""; } } diff --git a/mobile/lib/shared/models/server_info/server_config.model.dart b/mobile/lib/shared/models/server_info/server_config.model.dart index 227625dcb..cdb99987e 100644 --- a/mobile/lib/shared/models/server_info/server_config.model.dart +++ b/mobile/lib/shared/models/server_info/server_config.model.dart @@ -2,43 +2,35 @@ import 'package:openapi/api.dart'; class ServerConfig { final int trashDays; - final String mapTileUrl; const ServerConfig({ required this.trashDays, - required this.mapTileUrl, }); ServerConfig copyWith({ int? trashDays, - String? mapTileUrl, }) { return ServerConfig( trashDays: trashDays ?? this.trashDays, - mapTileUrl: mapTileUrl ?? this.mapTileUrl, ); } @override String toString() { - return 'ServerConfig(trashDays: $trashDays, mapTileUrl: $mapTileUrl)'; + return 'ServerConfig(trashDays: $trashDays)'; } - ServerConfig.fromDto(ServerConfigDto dto) - : trashDays = dto.trashDays, - mapTileUrl = dto.mapTileUrl; + ServerConfig.fromDto(ServerConfigDto dto) : trashDays = dto.trashDays; @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is ServerConfig && - other.trashDays == trashDays && - other.mapTileUrl == mapTileUrl; + return other is ServerConfig && other.trashDays == trashDays; } @override int get hashCode { - return trashDays.hashCode ^ mapTileUrl.hashCode; + return trashDays.hashCode; } } diff --git a/mobile/lib/shared/models/store.dart b/mobile/lib/shared/models/store.dart index 8f24899f1..40258f304 100644 --- a/mobile/lib/shared/models/store.dart +++ b/mobile/lib/shared/models/store.dart @@ -156,6 +156,7 @@ enum StoreKey { accessToken(11, type: String), serverEndpoint(12, type: String), autoBackup(13, type: bool), + backgroundBackup(14, type: bool), // user settings from [AppSettingsEnum] below: loadPreview(100, type: bool), loadOriginal(101, type: bool), diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index b0bcf89b1..3c66fe76d 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -23,7 +23,6 @@ class ServerInfoNotifier extends StateNotifier { trash: true, ), serverConfig: const ServerConfig( - mapTileUrl: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", trashDays: 30, ), serverDiskInfo: const ServerDiskInfo( diff --git a/mobile/lib/shared/services/api.service.dart b/mobile/lib/shared/services/api.service.dart index 7c1dfc8fc..656de826c 100644 --- a/mobile/lib/shared/services/api.service.dart +++ b/mobile/lib/shared/services/api.service.dart @@ -22,6 +22,8 @@ class ApiService { late PersonApi personApi; late AuditApi auditApi; late SharedLinkApi sharedLinkApi; + late SystemConfigApi systemConfigApi; + late ActivityApi activityApi; ApiService() { final endpoint = Store.tryGet(StoreKey.serverEndpoint); @@ -47,6 +49,8 @@ class ApiService { personApi = PersonApi(_apiClient); auditApi = AuditApi(_apiClient); sharedLinkApi = SharedLinkApi(_apiClient); + systemConfigApi = SystemConfigApi(_apiClient); + activityApi = ActivityApi(_apiClient); } Future resolveAndSetEndpoint(String serverUrl) async { diff --git a/mobile/lib/shared/services/asset.service.dart b/mobile/lib/shared/services/asset.service.dart index 488395b16..8b1ee6a33 100644 --- a/mobile/lib/shared/services/asset.service.dart +++ b/mobile/lib/shared/services/asset.service.dart @@ -62,20 +62,31 @@ class AssetService { /// Returns `null` if the server state did not change, else list of assets Future?> _getRemoteAssets(User user) async { + const int chunkSize = 5000; try { - final List? assets = - await _apiService.assetApi.getAllAssets( - userId: user.id, - ); - if (assets == null) { - return null; - } else if (assets.isNotEmpty && assets.first.ownerId != user.id) { - log.warning("Make sure that server and app versions match!" - " The server returned assets for user ${assets.first.ownerId}" - " while requesting assets of user ${user.id}"); - return null; + final DateTime now = DateTime.now().toUtc(); + final List allAssets = []; + for (int i = 0;; i += chunkSize) { + final List? assets = + await _apiService.assetApi.getAllAssets( + userId: user.id, + // updatedBefore is important! without it we could + // a) get the same Asset multiple times in different versions (when + // the asset is modified while the chunks are loaded from the server) + // b) miss assets when new assets are inserted in between the calls + updatedBefore: now, + skip: i, + take: chunkSize, + ); + if (assets == null) { + return null; + } + allAssets.addAll(assets.map(Asset.remote)); + if (assets.length < chunkSize) { + break; + } } - return assets.map(Asset.remote).toList(); + return allAssets; } catch (error, stack) { log.severe( 'Error while getting remote assets: ${error.toString()}', diff --git a/mobile/lib/shared/services/hash.service.dart b/mobile/lib/shared/services/hash.service.dart index ee272cf5f..914c8bc28 100644 --- a/mobile/lib/shared/services/hash.service.dart +++ b/mobile/lib/shared/services/hash.service.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/device_asset.dart'; import 'package:immich_mobile/shared/models/ios_device_asset.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; -import 'package:immich_mobile/utils/builtin_extensions.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index 34d84401a..fb9888258 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/services/hash.service.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; -import 'package:immich_mobile/utils/builtin_extensions.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; @@ -197,7 +197,7 @@ class SyncService { User user, FutureOr?> Function(User user) loadAssets, ) async { - final DateTime now = DateTime.now(); + final DateTime now = DateTime.now().toUtc(); final List? remote = await loadAssets(user); if (remote == null) { return false; @@ -210,6 +210,10 @@ class SyncService { assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); remote.sort(Asset.compareByChecksum); + + // filter our duplicates that might be introduced by the chunked retrieval + remote.uniqueConsecutive(compare: Asset.compareByChecksum); + final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true); if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) { await _updateUserAssetsETag(user, now); @@ -759,6 +763,12 @@ class SyncService { final List toAdd = []; final List toUpdate = []; final List toRemove = []; + if (assets.isEmpty || inDb.isEmpty) { + // fast path for trivial cases: halfes memory usage during initial sync + return assets.isEmpty + ? (toAdd, toUpdate, inDb) // remove all from DB + : (assets, toUpdate, toRemove); // add all assets + } diffSortedListsSync( inDb, assets, diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart index ede113837..60ee135b8 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart @@ -1,8 +1,8 @@ -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; @@ -22,9 +22,8 @@ class ImmichAppBarDialog extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { BackUpState backupState = ref.watch(backupProvider); - final theme = Theme.of(context); - bool isDarkTheme = theme.brightness == Brightness.dark; - bool isHorizontal = MediaQuery.of(context).size.width > 600; + final theme = context.themeData; + bool isHorizontal = !context.isMobile; final horizontalPadding = isHorizontal ? 100.0 : 20.0; final user = ref.watch(currentUserProvider); @@ -40,7 +39,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { return Row( children: [ InkWell( - onTap: () => Navigator.of(context).pop(), + onTap: () => context.pop(), child: const Icon( Icons.close, size: 20, @@ -54,7 +53,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { style: TextStyle( fontFamily: 'SnowburstOne', fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontSize: 15, ), ), @@ -90,7 +89,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { return buildActionButton( Icons.settings_rounded, "profile_drawer_settings", - () => AutoRouter.of(context).push(const SettingsRoute()), + () => context.autoPush(const SettingsRoute()), ); } @@ -98,7 +97,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { return buildActionButton( Icons.assignment_outlined, "profile_drawer_app_logs", - () => AutoRouter.of(context).push(const AppLogRoute()), + () => context.autoPush(const AppLogRoute()), ); } @@ -121,7 +120,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { ref.watch(backupProvider.notifier).cancelBackup(); ref.watch(assetProvider.notifier).clearAllAsset(); ref.watch(websocketProvider.notifier).disconnect(); - AutoRouter.of(context).replace(const LoginRoute()); + context.autoReplace(const LoginRoute()); }, ); }, @@ -136,8 +135,8 @@ class ImmichAppBarDialog extends HookConsumerWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 4), decoration: BoxDecoration( - color: isDarkTheme - ? Theme.of(context).scaffoldBackgroundColor + color: context.isDarkTheme + ? context.scaffoldBackgroundColor : const Color.fromARGB(255, 225, 229, 240), ), child: ListTile( @@ -191,7 +190,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { children: [ InkWell( onTap: () { - Navigator.of(context).pop(); + context.pop(); launchUrl( Uri.parse('https://immich.app'), mode: LaunchMode.externalApplication, @@ -199,7 +198,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { }, child: Text( "profile_drawer_documentation", - style: Theme.of(context).textTheme.bodySmall, + style: context.textTheme.bodySmall, ).tr(), ), const SizedBox( @@ -211,7 +210,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { ), InkWell( onTap: () { - Navigator.of(context).pop(); + context.pop(); launchUrl( Uri.parse('https://github.com/immich-app/immich'), mode: LaunchMode.externalApplication, @@ -219,7 +218,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { }, child: Text( "profile_drawer_github", - style: Theme.of(context).textTheme.bodySmall, + style: context.textTheme.bodySmall, ).tr(), ), ], diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart index d58699d5c..4f1f7db86 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; @@ -18,7 +19,6 @@ class AppBarProfileInfoBox extends HookConsumerWidget { AuthenticationState authState = ref.watch(authenticationProvider); final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; - final isDarkMode = Theme.of(context).brightness == Brightness.dark; final user = Store.tryGet(StoreKey.currentUser); buildUserProfileImage() { @@ -91,8 +91,8 @@ class AppBarProfileInfoBox extends HookConsumerWidget { child: Container( width: double.infinity, decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).scaffoldBackgroundColor + color: context.isDarkTheme + ? context.scaffoldBackgroundColor : const Color.fromARGB(255, 225, 229, 240), borderRadius: const BorderRadius.only( topLeft: Radius.circular(10), @@ -111,7 +111,9 @@ class AppBarProfileInfoBox extends HookConsumerWidget { bottom: -5, right: -8, child: Material( - color: isDarkMode ? Colors.blueGrey[800] : Colors.white, + color: context.isDarkTheme + ? Colors.blueGrey[800] + : Colors.white, elevation: 3, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50.0), @@ -120,7 +122,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { padding: const EdgeInsets.all(5.0), child: Icon( Icons.camera_alt_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, size: 14, ), ), @@ -132,16 +134,16 @@ class AppBarProfileInfoBox extends HookConsumerWidget { title: Text( "${authState.firstName} ${authState.lastName}", style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16, ), ), subtitle: Text( authState.userEmail, - style: Theme.of(context).textTheme.labelMedium?.copyWith( - fontSize: 12, - ), + style: context.textTheme.labelMedium?.copyWith( + fontSize: 12, + ), ), ), ), diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart index fa4a73536..fef9e1daa 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/server_info/server_info.model.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; @@ -39,8 +40,8 @@ class AppBarServerInfo extends HookConsumerWidget { padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), child: Container( decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).scaffoldBackgroundColor + color: context.isDarkTheme + ? context.scaffoldBackgroundColor : const Color.fromARGB(255, 225, 229, 240), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(10), @@ -61,7 +62,7 @@ class AppBarServerInfo extends HookConsumerWidget { textAlign: TextAlign.center, style: TextStyle( fontSize: 11, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.w600, ), ), @@ -83,7 +84,7 @@ class AppBarServerInfo extends HookConsumerWidget { "server_info_box_app_version".tr(), style: TextStyle( fontSize: 11, - color: Theme.of(context).textTheme.labelSmall?.color, + color: context.textTheme.labelSmall?.color, fontWeight: FontWeight.bold, ), ), @@ -97,10 +98,7 @@ class AppBarServerInfo extends HookConsumerWidget { "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}", style: TextStyle( fontSize: 11, - color: Theme.of(context) - .textTheme - .labelSmall - ?.color + color: context.textTheme.labelSmall?.color ?.withOpacity(0.5), fontWeight: FontWeight.bold, ), @@ -126,7 +124,7 @@ class AppBarServerInfo extends HookConsumerWidget { "server_info_box_server_version".tr(), style: TextStyle( fontSize: 11, - color: Theme.of(context).textTheme.labelSmall?.color, + color: context.textTheme.labelSmall?.color, fontWeight: FontWeight.bold, ), ), @@ -142,10 +140,7 @@ class AppBarServerInfo extends HookConsumerWidget { : "?", style: TextStyle( fontSize: 11, - color: Theme.of(context) - .textTheme - .labelSmall - ?.color + color: context.textTheme.labelSmall?.color ?.withOpacity(0.5), fontWeight: FontWeight.bold, ), @@ -171,7 +166,7 @@ class AppBarServerInfo extends HookConsumerWidget { "server_info_box_server_url".tr(), style: TextStyle( fontSize: 11, - color: Theme.of(context).textTheme.labelSmall?.color, + color: context.textTheme.labelSmall?.color, fontWeight: FontWeight.bold, ), ), @@ -185,14 +180,12 @@ class AppBarServerInfo extends HookConsumerWidget { child: Tooltip( verticalOffset: 0, decoration: BoxDecoration( - color: - Theme.of(context).primaryColor.withOpacity(0.9), + color: context.primaryColor.withOpacity(0.9), borderRadius: BorderRadius.circular(10), ), textStyle: TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.black - : Colors.white, + color: + context.isDarkTheme ? Colors.black : Colors.white, fontWeight: FontWeight.bold, ), message: getServerUrl() ?? '--', @@ -202,10 +195,7 @@ class AppBarServerInfo extends HookConsumerWidget { getServerUrl() ?? '--', style: TextStyle( fontSize: 11, - color: Theme.of(context) - .textTheme - .labelSmall - ?.color + color: context.textTheme.labelSmall?.color ?.withOpacity(0.5), fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, diff --git a/mobile/lib/shared/ui/confirm_dialog.dart b/mobile/lib/shared/ui/confirm_dialog.dart index ca5fee7ec..1fd255a15 100644 --- a/mobile/lib/shared/ui/confirm_dialog.dart +++ b/mobile/lib/shared/ui/confirm_dialog.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class ConfirmDialog extends StatelessWidget { final Function onOk; @@ -30,11 +31,11 @@ class ConfirmDialog extends StatelessWidget { ).tr(), actions: [ TextButton( - onPressed: () => Navigator.of(context).pop(false), + onPressed: () => context.pop(false), child: Text( cancel, style: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ).tr(), @@ -42,7 +43,7 @@ class ConfirmDialog extends StatelessWidget { TextButton( onPressed: () { onOk(); - Navigator.of(context).pop(true); + context.pop(true); }, child: Text( ok, diff --git a/mobile/lib/shared/ui/immich_app_bar.dart b/mobile/lib/shared/ui/immich_app_bar.dart index 3510931f6..bbf5a48a0 100644 --- a/mobile/lib/shared/ui/immich_app_bar.dart +++ b/mobile/lib/shared/ui/immich_app_bar.dart @@ -1,6 +1,6 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; @@ -28,7 +28,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { final ServerInfo serverInfoState = ref.watch(serverInfoProvider); AuthenticationState authState = ref.watch(authenticationProvider); final user = Store.tryGet(StoreKey.currentUser); - final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; buildProfileIndicator() { @@ -70,7 +70,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { } getBackupBadgeIcon() { - final iconColor = isDarkMode ? Colors.white : Colors.black; + final iconColor = isDarkTheme ? Colors.white : Colors.black; if (isEnableAutoBackup) { if (backupState.backupProgress == BackUpProgressEnum.inProgress) { @@ -104,10 +104,10 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { buildBackupIndicator() { final indicatorIcon = getBackupBadgeIcon(); - final badgeBackground = isDarkMode ? Colors.blueGrey[800] : Colors.white; + final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white; return InkWell( - onTap: () => AutoRouter.of(context).push(const BackupControllerRoute()), + onTap: () => context.autoPush(const BackupControllerRoute()), borderRadius: BorderRadius.circular(12), child: Badge( label: Container( @@ -116,7 +116,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { decoration: BoxDecoration( color: badgeBackground, border: Border.all( - color: isDarkMode ? Colors.black : Colors.grey, + color: isDarkTheme ? Colors.black : Colors.grey, ), borderRadius: BorderRadius.circular(widgetSize / 2), ), @@ -129,14 +129,14 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { child: Icon( Icons.backup_rounded, size: widgetSize, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ); } return AppBar( - backgroundColor: Theme.of(context).appBarTheme.backgroundColor, + backgroundColor: context.themeData.appBarTheme.backgroundColor, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(5), diff --git a/mobile/lib/shared/ui/immich_image.dart b/mobile/lib/shared/ui/immich_image.dart index 8b505f556..985219d6e 100644 --- a/mobile/lib/shared/ui/immich_image.dart +++ b/mobile/lib/shared/ui/immich_image.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -86,7 +87,7 @@ class ImmichImage extends StatelessWidget { } return Icon( Icons.image_not_supported_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ); }, ); @@ -137,7 +138,7 @@ class ImmichImage extends StatelessWidget { } return Icon( Icons.image_not_supported_outlined, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ); }, ); diff --git a/mobile/lib/shared/ui/immich_loading_indicator.dart b/mobile/lib/shared/ui/immich_loading_indicator.dart index 98ddb8f47..db5dd3c19 100644 --- a/mobile/lib/shared/ui/immich_loading_indicator.dart +++ b/mobile/lib/shared/ui/immich_loading_indicator.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class ImmichLoadingIndicator extends StatelessWidget { final double? borderRadius; @@ -14,7 +15,7 @@ class ImmichLoadingIndicator extends StatelessWidget { height: 60, width: 60, decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withAlpha(200), + color: context.primaryColor.withAlpha(200), borderRadius: BorderRadius.circular(borderRadius ?? 10), ), padding: const EdgeInsets.all(15), diff --git a/mobile/lib/shared/ui/immich_title_text.dart b/mobile/lib/shared/ui/immich_title_text.dart index 7f633a0e6..3ef0501dd 100644 --- a/mobile/lib/shared/ui/immich_title_text.dart +++ b/mobile/lib/shared/ui/immich_title_text.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; class ImmichTitleText extends StatelessWidget { final double fontSize; @@ -18,9 +19,8 @@ class ImmichTitleText extends StatelessWidget { fontFamily: 'SnowburstOne', fontWeight: FontWeight.bold, fontSize: fontSize, - color: color ?? Theme.of(context).primaryColor, + color: color ?? context.primaryColor, ), ); } - } diff --git a/mobile/lib/shared/ui/immich_toast.dart b/mobile/lib/shared/ui/immich_toast.dart index 3f15c13a2..25a0e65fa 100644 --- a/mobile/lib/shared/ui/immich_toast.dart +++ b/mobile/lib/shared/ui/immich_toast.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; enum ToastType { info, success, error } @@ -11,14 +12,13 @@ class ImmichToast { ToastGravity gravity = ToastGravity.TOP, int durationInSecond = 3, }) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; final fToast = FToast(); fToast.init(context); Color getColor(ToastType type, BuildContext context) { switch (type) { case ToastType.info: - return Theme.of(context).primaryColor; + return context.primaryColor; case ToastType.success: return const Color.fromARGB(255, 78, 140, 124); case ToastType.error: @@ -31,7 +31,7 @@ class ImmichToast { case ToastType.info: return Icon( Icons.info_outline_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ); case ToastType.success: return const Icon( @@ -51,7 +51,7 @@ class ImmichToast { padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5.0), - color: isDarkTheme ? Colors.grey[900] : Colors.grey[50], + color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[50], border: Border.all( color: Colors.black12, width: 1, diff --git a/mobile/lib/shared/ui/user_avatar.dart b/mobile/lib/shared/ui/user_avatar.dart index c736e8f90..382e44eff 100644 --- a/mobile/lib/shared/ui/user_avatar.dart +++ b/mobile/lib/shared/ui/user_avatar.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; @@ -10,7 +11,7 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) { final lastNameFirstLetter = u.lastName.isNotEmpty ? u.lastName[0] : ""; return CircleAvatar( radius: radius, - backgroundColor: Theme.of(context).primaryColor.withAlpha(50), + backgroundColor: context.primaryColor.withAlpha(50), foregroundImage: CachedNetworkImageProvider( url, headers: {"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"}, diff --git a/mobile/lib/shared/ui/user_circle_avatar.dart b/mobile/lib/shared/ui/user_circle_avatar.dart index b70566d88..98df36265 100644 --- a/mobile/lib/shared/ui/user_circle_avatar.dart +++ b/mobile/lib/shared/ui/user_circle_avatar.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/ui/transparent_image.dart'; @@ -40,19 +41,21 @@ class UserCircleAvatar extends ConsumerWidget { final profileImageUrl = '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; + + final textIcon = Text( + user.firstName[0].toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.isDarkTheme ? Colors.black : Colors.white, + ), + ); return CircleAvatar( backgroundColor: useRandomBackgroundColor ? randomColors[Random().nextInt(randomColors.length)] - : Theme.of(context).primaryColor, + : context.primaryColor, radius: radius, child: user.profileImagePath == "" - ? Text( - user.firstName[0].toUpperCase(), - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ) + ? textIcon : ClipRRect( borderRadius: BorderRadius.circular(50), child: CachedNetworkImage( @@ -66,8 +69,7 @@ class UserCircleAvatar extends ConsumerWidget { "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", }, fadeInDuration: const Duration(milliseconds: 300), - errorWidget: (context, error, stackTrace) => - Image.memory(kTransparentImage), + errorWidget: (context, error, stackTrace) => textIcon, ), ), ); diff --git a/mobile/lib/shared/views/app_log_detail_page.dart b/mobile/lib/shared/views/app_log_detail_page.dart index 0963605b4..f8ddf5191 100644 --- a/mobile/lib/shared/views/app_log_detail_page.dart +++ b/mobile/lib/shared/views/app_log_detail_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart'; import 'package:flutter/services.dart'; @@ -10,7 +11,7 @@ class AppLogDetailPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var isDarkMode = Theme.of(context).brightness == Brightness.dark; + var isDarkTheme = context.isDarkTheme; buildStackMessage(String stackTrace) { return Padding( @@ -28,7 +29,7 @@ class AppLogDetailPage extends HookConsumerWidget { "STACK TRACES", style: TextStyle( fontSize: 12.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), @@ -45,14 +46,14 @@ class AppLogDetailPage extends HookConsumerWidget { icon: Icon( Icons.copy, size: 16.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ], ), Container( decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[200], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[200], borderRadius: BorderRadius.circular(15.0), ), child: Padding( @@ -88,7 +89,7 @@ class AppLogDetailPage extends HookConsumerWidget { "MESSAGE", style: TextStyle( fontSize: 12.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), @@ -104,14 +105,14 @@ class AppLogDetailPage extends HookConsumerWidget { icon: Icon( Icons.copy, size: 16.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ], ), Container( decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[200], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[200], borderRadius: BorderRadius.circular(15.0), ), child: Padding( @@ -143,14 +144,14 @@ class AppLogDetailPage extends HookConsumerWidget { "FROM", style: TextStyle( fontSize: 12.0, - color: Theme.of(context).primaryColor, + color: context.primaryColor, fontWeight: FontWeight.bold, ), ), ), Container( decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[200], + color: isDarkTheme ? Colors.grey[900] : Colors.grey[200], borderRadius: BorderRadius.circular(15.0), ), child: Padding( diff --git a/mobile/lib/shared/views/app_log_page.dart b/mobile/lib/shared/views/app_log_page.dart index a8dfc6ec4..9d1dca19f 100644 --- a/mobile/lib/shared/views/app_log_page.dart +++ b/mobile/lib/shared/views/app_log_page.dart @@ -1,7 +1,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/extensions/build_context_extensions.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'; @@ -16,6 +16,7 @@ class AppLogPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final immichLogger = ImmichLogger(); final logMessages = useState(immichLogger.messages); + final isDarkTheme = context.isDarkTheme; Widget colorStatusIndicator(Color color) { return Column( @@ -36,7 +37,7 @@ class AppLogPage extends HookConsumerWidget { Widget buildLeadingIcon(LogLevel level) { switch (level) { case LogLevel.INFO: - return colorStatusIndicator(Theme.of(context).primaryColor); + return colorStatusIndicator(context.primaryColor); case LogLevel.SEVERE: return colorStatusIndicator(Colors.redAccent); @@ -52,15 +53,15 @@ class AppLogPage extends HookConsumerWidget { case LogLevel.INFO: return Colors.transparent; case LogLevel.SEVERE: - return Theme.of(context).brightness == Brightness.dark + return isDarkTheme ? Colors.redAccent.withOpacity(0.25) : Colors.redAccent.withOpacity(0.075); case LogLevel.WARNING: - return Theme.of(context).brightness == Brightness.dark + return isDarkTheme ? Colors.orangeAccent.withOpacity(0.25) : Colors.orangeAccent.withOpacity(0.075); default: - return Theme.of(context).primaryColor.withOpacity(0.1); + return context.primaryColor.withOpacity(0.1); } } @@ -79,7 +80,7 @@ class AppLogPage extends HookConsumerWidget { IconButton( icon: Icon( Icons.delete_outline_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, semanticLabel: "Clear logs", size: 20.0, ), @@ -91,7 +92,7 @@ class AppLogPage extends HookConsumerWidget { IconButton( icon: Icon( Icons.share_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, semanticLabel: "Share logs", size: 20.0, ), @@ -102,7 +103,7 @@ class AppLogPage extends HookConsumerWidget { ], leading: IconButton( onPressed: () { - AutoRouter.of(context).pop(); + context.autoPop(); }, icon: const Icon( Icons.arrow_back_ios_new_rounded, @@ -115,16 +116,14 @@ class AppLogPage extends HookConsumerWidget { separatorBuilder: (context, index) { return Divider( height: 0, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white70 - : Colors.grey[600], + color: isDarkTheme ? Colors.white70 : Colors.grey[600], ); }, itemCount: logMessages.value.length, itemBuilder: (context, index) { var logMessage = logMessages.value[index]; return ListTile( - onTap: () => AutoRouter.of(context).push( + onTap: () => context.autoPush( AppLogDetailRoute( logMessage: logMessage, ), @@ -140,9 +139,7 @@ class AppLogPage extends HookConsumerWidget { TextSpan( text: "#$index ", style: TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white70 - : Colors.grey[600], + color: isDarkTheme ? Colors.white70 : Colors.grey[600], fontSize: 14.0, fontWeight: FontWeight.bold, ), @@ -170,7 +167,7 @@ class AppLogPage extends HookConsumerWidget { ), ); } - + /// Truncate the log message to a certain number of lines /// @param int maxLines - Max number of lines to truncate String truncateLogMessage(String message, int maxLines) { diff --git a/mobile/lib/shared/views/splash_screen.dart b/mobile/lib/shared/views/splash_screen.dart index bd419bc02..2f493d16a 100644 --- a/mobile/lib/shared/views/splash_screen.dart +++ b/mobile/lib/shared/views/splash_screen.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; @@ -51,7 +51,7 @@ class SplashScreenPage extends HookConsumerWidget { // If the device is offline and there is a currentUser stored locallly // Proceed into the app if (deviceIsOffline && Store.tryGet(StoreKey.currentUser) != null) { - AutoRouter.of(context).replace(const TabControllerRoute()); + context.autoReplace(const TabControllerRoute()); } else if (isSuccess) { // If device was able to login through the internet successfully final hasPermission = @@ -60,10 +60,10 @@ class SplashScreenPage extends HookConsumerWidget { // Resume backup (if enable) then navigate ref.watch(backupProvider.notifier).resumeBackup(); } - AutoRouter.of(context).replace(const TabControllerRoute()); + context.autoReplace(const TabControllerRoute()); } else { // User was unable to login through either offline or online methods - AutoRouter.of(context).replace(const LoginRoute()); + context.autoReplace(const LoginRoute()); } } @@ -72,7 +72,7 @@ class SplashScreenPage extends HookConsumerWidget { if (serverUrl != null && accessToken != null) { performLoggingIn(); } else { - AutoRouter.of(context).replace(const LoginRoute()); + context.autoReplace(const LoginRoute()); } return null; }, diff --git a/mobile/lib/shared/views/tab_controller_page.dart b/mobile/lib/shared/views/tab_controller_page.dart index 8e72ce900..2b9d62565 100644 --- a/mobile/lib/shared/views/tab_controller_page.dart +++ b/mobile/lib/shared/views/tab_controller_page.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -31,7 +32,7 @@ class TabControllerPage extends HookConsumerWidget { child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( - Theme.of(context).primaryColor, + context.primaryColor, ), ), ), @@ -55,10 +56,10 @@ class TabControllerPage extends HookConsumerWidget { ref.read(tabProvider.notifier).state = TabEnum.values[index]; }, selectedIconTheme: IconThemeData( - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), selectedLabelTextStyle: TextStyle( - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), useIndicator: false, destinations: [ @@ -116,7 +117,7 @@ class TabControllerPage extends HookConsumerWidget { selectedIcon: buildIcon( Icon( Icons.photo_library, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), @@ -127,7 +128,7 @@ class TabControllerPage extends HookConsumerWidget { ), selectedIcon: Icon( Icons.search, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), NavigationDestination( @@ -137,7 +138,7 @@ class TabControllerPage extends HookConsumerWidget { ), selectedIcon: Icon( Icons.group, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), NavigationDestination( @@ -148,7 +149,7 @@ class TabControllerPage extends HookConsumerWidget { selectedIcon: buildIcon( Icon( Icons.photo_album_rounded, - color: Theme.of(context).primaryColor, + color: context.primaryColor, ), ), ), diff --git a/mobile/lib/utils/capitalize.dart b/mobile/lib/utils/capitalize.dart deleted file mode 100644 index 80b10cc58..000000000 --- a/mobile/lib/utils/capitalize.dart +++ /dev/null @@ -1,9 +0,0 @@ -extension StringExtension on String { - String capitalize() { - return split(" ") - .map( - (str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1), - ) - .join(" "); - } -} diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index 511dcf81e..5d13d20a2 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/services/share.service.dart'; @@ -26,7 +27,7 @@ void handleShareAssets( gravity: ToastGravity.BOTTOM, ); } - Navigator.of(buildContext).pop(); + context.pop(); }, ); return const ShareDialog(); diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 52b863a6f..57854e1b7 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -13,7 +13,6 @@ doc/ActivityCreateDto.md doc/ActivityResponseDto.md doc/ActivityStatisticsResponseDto.md doc/AddUsersDto.md -doc/AdminSignupResponseDto.md doc/AlbumApi.md doc/AlbumCountResponseDto.md doc/AlbumResponseDto.md @@ -82,6 +81,7 @@ doc/LoginCredentialDto.md doc/LoginResponseDto.md doc/LogoutResponseDto.md doc/MapMarkerResponseDto.md +doc/MapTheme.md doc/MemoryLaneResponseDto.md doc/MergePersonDto.md doc/ModelType.md @@ -101,6 +101,7 @@ doc/PersonResponseDto.md doc/PersonStatisticsResponseDto.md doc/PersonUpdateDto.md doc/QueueStatusDto.md +doc/ReactionLevel.md doc/ReactionType.md doc/RecognitionConfig.md doc/ScanLibraryDto.md @@ -198,7 +199,6 @@ lib/model/activity_create_dto.dart lib/model/activity_response_dto.dart lib/model/activity_statistics_response_dto.dart lib/model/add_users_dto.dart -lib/model/admin_signup_response_dto.dart lib/model/album_count_response_dto.dart lib/model/album_response_dto.dart lib/model/all_job_status_response_dto.dart @@ -265,6 +265,7 @@ lib/model/login_credential_dto.dart lib/model/login_response_dto.dart lib/model/logout_response_dto.dart lib/model/map_marker_response_dto.dart +lib/model/map_theme.dart lib/model/memory_lane_response_dto.dart lib/model/merge_person_dto.dart lib/model/model_type.dart @@ -281,6 +282,7 @@ lib/model/person_response_dto.dart lib/model/person_statistics_response_dto.dart lib/model/person_update_dto.dart lib/model/queue_status_dto.dart +lib/model/reaction_level.dart lib/model/reaction_type.dart lib/model/recognition_config.dart lib/model/scan_library_dto.dart @@ -347,7 +349,6 @@ test/activity_create_dto_test.dart test/activity_response_dto_test.dart test/activity_statistics_response_dto_test.dart test/add_users_dto_test.dart -test/admin_signup_response_dto_test.dart test/album_api_test.dart test/album_count_response_dto_test.dart test/album_response_dto_test.dart @@ -421,6 +422,7 @@ test/login_credential_dto_test.dart test/login_response_dto_test.dart test/logout_response_dto_test.dart test/map_marker_response_dto_test.dart +test/map_theme_test.dart test/memory_lane_response_dto_test.dart test/merge_person_dto_test.dart test/model_type_test.dart @@ -440,6 +442,7 @@ test/person_response_dto_test.dart test/person_statistics_response_dto_test.dart test/person_update_dto_test.dart test/queue_status_dto_test.dart +test/reaction_level_test.dart test/reaction_type_test.dart test/recognition_config_test.dart test/scan_library_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 70ef217a5..0bc2cc1e3 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.84.0 +- API version: 1.85.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements @@ -181,6 +181,7 @@ Class | Method | HTTP request | Description *SharedLinkApi* | [**updateSharedLink**](doc//SharedLinkApi.md#updatesharedlink) | **PATCH** /shared-link/{id} | *SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | *SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | +*SystemConfigApi* | [**getMapStyle**](doc//SystemConfigApi.md#getmapstyle) | **GET** /system-config/map/style.json | *SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | *SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | *TagApi* | [**createTag**](doc//TagApi.md#createtag) | **POST** /tag | @@ -212,7 +213,6 @@ Class | Method | HTTP request | Description - [ActivityResponseDto](doc//ActivityResponseDto.md) - [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md) - [AddUsersDto](doc//AddUsersDto.md) - - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) - [AlbumResponseDto](doc//AlbumResponseDto.md) - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md) @@ -275,6 +275,7 @@ Class | Method | HTTP request | Description - [LoginResponseDto](doc//LoginResponseDto.md) - [LogoutResponseDto](doc//LogoutResponseDto.md) - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) + - [MapTheme](doc//MapTheme.md) - [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md) - [MergePersonDto](doc//MergePersonDto.md) - [ModelType](doc//ModelType.md) @@ -291,6 +292,7 @@ Class | Method | HTTP request | Description - [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md) - [PersonUpdateDto](doc//PersonUpdateDto.md) - [QueueStatusDto](doc//QueueStatusDto.md) + - [ReactionLevel](doc//ReactionLevel.md) - [ReactionType](doc//ReactionType.md) - [RecognitionConfig](doc//RecognitionConfig.md) - [ScanLibraryDto](doc//ScanLibraryDto.md) diff --git a/mobile/openapi/doc/ActivityApi.md b/mobile/openapi/doc/ActivityApi.md index 1af3f1f49..6221ef5e4 100644 --- a/mobile/openapi/doc/ActivityApi.md +++ b/mobile/openapi/doc/ActivityApi.md @@ -125,7 +125,7 @@ void (empty response body) [[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) # **getActivities** -> List getActivities(albumId, assetId, type, userId) +> List getActivities(albumId, assetId, type, level, userId) @@ -151,10 +151,11 @@ final api_instance = ActivityApi(); final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final type = ; // ReactionType | +final level = ; // ReactionLevel | final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | try { - final result = api_instance.getActivities(albumId, assetId, type, userId); + final result = api_instance.getActivities(albumId, assetId, type, level, userId); print(result); } catch (e) { print('Exception when calling ActivityApi->getActivities: $e\n'); @@ -168,6 +169,7 @@ Name | Type | Description | Notes **albumId** | **String**| | **assetId** | **String**| | [optional] **type** | [**ReactionType**](.md)| | [optional] + **level** | [**ReactionLevel**](.md)| | [optional] **userId** | **String**| | [optional] ### Return type diff --git a/mobile/openapi/doc/AlbumResponseDto.md b/mobile/openapi/doc/AlbumResponseDto.md index 93620b9fc..bc00d30af 100644 --- a/mobile/openapi/doc/AlbumResponseDto.md +++ b/mobile/openapi/doc/AlbumResponseDto.md @@ -17,6 +17,7 @@ Name | Type | Description | Notes **endDate** | [**DateTime**](DateTime.md) | | [optional] **hasSharedLink** | **bool** | | **id** | **String** | | +**isActivityEnabled** | **bool** | | **lastModifiedAssetTimestamp** | [**DateTime**](DateTime.md) | | [optional] **owner** | [**UserResponseDto**](UserResponseDto.md) | | **ownerId** | **String** | | diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index cf50f659d..811841947 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -374,7 +374,7 @@ void (empty response body) [[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) # **getAllAssets** -> List getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch) +> List getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch) @@ -399,15 +399,17 @@ import 'package:openapi/api.dart'; //defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); final api_instance = AssetApi(); +final skip = 56; // int | +final take = 56; // int | final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final isFavorite = true; // bool | final isArchived = true; // bool | -final skip = 8.14; // num | final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime | final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client try { - final result = api_instance.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch); + final result = api_instance.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch); print(result); } catch (e) { print('Exception when calling AssetApi->getAllAssets: $e\n'); @@ -418,11 +420,13 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- + **skip** | **int**| | [optional] + **take** | **int**| | [optional] **userId** | **String**| | [optional] **isFavorite** | **bool**| | [optional] **isArchived** | **bool**| | [optional] - **skip** | **num**| | [optional] **updatedAfter** | **DateTime**| | [optional] + **updatedBefore** | **DateTime**| | [optional] **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] ### Return type @@ -1696,7 +1700,7 @@ void (empty response body) [[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) # **uploadFile** -> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData) +> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, duration, isArchived, isExternal, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData) @@ -1724,11 +1728,11 @@ final deviceAssetId = deviceAssetId_example; // String | final deviceId = deviceId_example; // String | final fileCreatedAt = 2013-10-20T19:20:30+01:00; // DateTime | final fileModifiedAt = 2013-10-20T19:20:30+01:00; // DateTime | -final isFavorite = true; // bool | final key = key_example; // String | final duration = duration_example; // String | final isArchived = true; // bool | final isExternal = true; // bool | +final isFavorite = true; // bool | final isOffline = true; // bool | final isReadOnly = true; // bool | final isVisible = true; // bool | @@ -1737,7 +1741,7 @@ final livePhotoData = BINARY_DATA_HERE; // MultipartFile | final sidecarData = BINARY_DATA_HERE; // MultipartFile | try { - final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData); + final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, duration, isArchived, isExternal, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData); print(result); } catch (e) { print('Exception when calling AssetApi->uploadFile: $e\n'); @@ -1753,11 +1757,11 @@ Name | Type | Description | Notes **deviceId** | **String**| | **fileCreatedAt** | **DateTime**| | **fileModifiedAt** | **DateTime**| | - **isFavorite** | **bool**| | **key** | **String**| | [optional] **duration** | **String**| | [optional] **isArchived** | **bool**| | [optional] **isExternal** | **bool**| | [optional] + **isFavorite** | **bool**| | [optional] **isOffline** | **bool**| | [optional] **isReadOnly** | **bool**| | [optional] **isVisible** | **bool**| | [optional] diff --git a/mobile/openapi/doc/AuthenticationApi.md b/mobile/openapi/doc/AuthenticationApi.md index c56f88228..9521568e9 100644 --- a/mobile/openapi/doc/AuthenticationApi.md +++ b/mobile/openapi/doc/AuthenticationApi.md @@ -322,7 +322,7 @@ void (empty response body) [[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) # **signUpAdmin** -> AdminSignupResponseDto signUpAdmin(signUpDto) +> UserResponseDto signUpAdmin(signUpDto) @@ -349,7 +349,7 @@ Name | Type | Description | Notes ### Return type -[**AdminSignupResponseDto**](AdminSignupResponseDto.md) +[**UserResponseDto**](UserResponseDto.md) ### Authorization diff --git a/mobile/openapi/doc/ImportAssetDto.md b/mobile/openapi/doc/ImportAssetDto.md index c9a5f25d2..3f2747edc 100644 --- a/mobile/openapi/doc/ImportAssetDto.md +++ b/mobile/openapi/doc/ImportAssetDto.md @@ -16,7 +16,7 @@ Name | Type | Description | Notes **fileModifiedAt** | [**DateTime**](DateTime.md) | | **isArchived** | **bool** | | [optional] **isExternal** | **bool** | | [optional] -**isFavorite** | **bool** | | +**isFavorite** | **bool** | | [optional] **isOffline** | **bool** | | [optional] **isReadOnly** | **bool** | | [optional] [default to true] **isVisible** | **bool** | | [optional] diff --git a/mobile/openapi/doc/LoginResponseDto.md b/mobile/openapi/doc/LoginResponseDto.md index ad3592eff..e344ef124 100644 --- a/mobile/openapi/doc/LoginResponseDto.md +++ b/mobile/openapi/doc/LoginResponseDto.md @@ -8,14 +8,14 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**accessToken** | **String** | | [readonly] -**firstName** | **String** | | [readonly] -**isAdmin** | **bool** | | [readonly] -**lastName** | **String** | | [readonly] -**profileImagePath** | **String** | | [readonly] -**shouldChangePassword** | **bool** | | [readonly] -**userEmail** | **String** | | [readonly] -**userId** | **String** | | [readonly] +**accessToken** | **String** | | +**firstName** | **String** | | +**isAdmin** | **bool** | | +**lastName** | **String** | | +**profileImagePath** | **String** | | +**shouldChangePassword** | **bool** | | +**userEmail** | **String** | | +**userId** | **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) diff --git a/mobile/openapi/doc/AdminSignupResponseDto.md b/mobile/openapi/doc/MapTheme.md similarity index 62% rename from mobile/openapi/doc/AdminSignupResponseDto.md rename to mobile/openapi/doc/MapTheme.md index 08d3d8bfa..29fd8d998 100644 --- a/mobile/openapi/doc/AdminSignupResponseDto.md +++ b/mobile/openapi/doc/MapTheme.md @@ -1,4 +1,4 @@ -# openapi.model.AdminSignupResponseDto +# openapi.model.MapTheme ## Load the model package ```dart @@ -8,11 +8,6 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**createdAt** | [**DateTime**](DateTime.md) | | -**email** | **String** | | -**firstName** | **String** | | -**id** | **String** | | -**lastName** | **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) diff --git a/mobile/openapi/doc/ReactionLevel.md b/mobile/openapi/doc/ReactionLevel.md new file mode 100644 index 000000000..a53955cb0 --- /dev/null +++ b/mobile/openapi/doc/ReactionLevel.md @@ -0,0 +1,14 @@ +# openapi.model.ReactionLevel + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/ServerConfigDto.md b/mobile/openapi/doc/ServerConfigDto.md index cbe56d2f4..fd543257b 100644 --- a/mobile/openapi/doc/ServerConfigDto.md +++ b/mobile/openapi/doc/ServerConfigDto.md @@ -10,7 +10,6 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **isInitialized** | **bool** | | **loginPageMessage** | **String** | | -**mapTileUrl** | **String** | | **oauthButtonText** | **String** | | **trashDays** | **int** | | diff --git a/mobile/openapi/doc/SystemConfigApi.md b/mobile/openapi/doc/SystemConfigApi.md index 9377b6e0b..e782265b4 100644 --- a/mobile/openapi/doc/SystemConfigApi.md +++ b/mobile/openapi/doc/SystemConfigApi.md @@ -11,6 +11,7 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**getConfig**](SystemConfigApi.md#getconfig) | **GET** /system-config | [**getConfigDefaults**](SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | +[**getMapStyle**](SystemConfigApi.md#getmapstyle) | **GET** /system-config/map/style.json | [**getStorageTemplateOptions**](SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | [**updateConfig**](SystemConfigApi.md#updateconfig) | **PUT** /system-config | @@ -117,6 +118,61 @@ 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) +# **getMapStyle** +> Object getMapStyle(theme) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = SystemConfigApi(); +final theme = ; // MapTheme | + +try { + final result = api_instance.getMapStyle(theme); + print(result); +} catch (e) { + print('Exception when calling SystemConfigApi->getMapStyle: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **theme** | [**MapTheme**](.md)| | + +### Return type + +[**Object**](Object.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: 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) + # **getStorageTemplateOptions** > SystemConfigTemplateStorageOptionDto getStorageTemplateOptions() diff --git a/mobile/openapi/doc/SystemConfigMapDto.md b/mobile/openapi/doc/SystemConfigMapDto.md index 99fa91fd2..1846563eb 100644 --- a/mobile/openapi/doc/SystemConfigMapDto.md +++ b/mobile/openapi/doc/SystemConfigMapDto.md @@ -8,8 +8,9 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**darkStyle** | **String** | | **enabled** | **bool** | | -**tileUrl** | **String** | | +**lightStyle** | **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) diff --git a/mobile/openapi/doc/UpdateAlbumDto.md b/mobile/openapi/doc/UpdateAlbumDto.md index 283b8bc29..4ded87d1b 100644 --- a/mobile/openapi/doc/UpdateAlbumDto.md +++ b/mobile/openapi/doc/UpdateAlbumDto.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **albumName** | **String** | | [optional] **albumThumbnailAssetId** | **String** | | [optional] **description** | **String** | | [optional] +**isActivityEnabled** | **bool** | | [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) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 2bd437010..7e4ed3a7b 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -54,7 +54,6 @@ part 'model/activity_create_dto.dart'; part 'model/activity_response_dto.dart'; part 'model/activity_statistics_response_dto.dart'; part 'model/add_users_dto.dart'; -part 'model/admin_signup_response_dto.dart'; part 'model/album_count_response_dto.dart'; part 'model/album_response_dto.dart'; part 'model/all_job_status_response_dto.dart'; @@ -117,6 +116,7 @@ part 'model/login_credential_dto.dart'; part 'model/login_response_dto.dart'; part 'model/logout_response_dto.dart'; part 'model/map_marker_response_dto.dart'; +part 'model/map_theme.dart'; part 'model/memory_lane_response_dto.dart'; part 'model/merge_person_dto.dart'; part 'model/model_type.dart'; @@ -133,6 +133,7 @@ part 'model/person_response_dto.dart'; part 'model/person_statistics_response_dto.dart'; part 'model/person_update_dto.dart'; part 'model/queue_status_dto.dart'; +part 'model/reaction_level.dart'; part 'model/reaction_type.dart'; part 'model/recognition_config.dart'; part 'model/scan_library_dto.dart'; diff --git a/mobile/openapi/lib/api/activity_api.dart b/mobile/openapi/lib/api/activity_api.dart index 458538a5d..8e2354e20 100644 --- a/mobile/openapi/lib/api/activity_api.dart +++ b/mobile/openapi/lib/api/activity_api.dart @@ -112,8 +112,10 @@ class ActivityApi { /// /// * [ReactionType] type: /// + /// * [ReactionLevel] level: + /// /// * [String] userId: - Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionType? type, String? userId, }) async { + Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionType? type, ReactionLevel? level, String? userId, }) async { // ignore: prefer_const_declarations final path = r'/activity'; @@ -131,6 +133,9 @@ class ActivityApi { if (type != null) { queryParams.addAll(_queryParams('', 'type', type)); } + if (level != null) { + queryParams.addAll(_queryParams('', 'level', level)); + } if (userId != null) { queryParams.addAll(_queryParams('', 'userId', userId)); } @@ -157,9 +162,11 @@ class ActivityApi { /// /// * [ReactionType] type: /// + /// * [ReactionLevel] level: + /// /// * [String] userId: - Future?> getActivities(String albumId, { String? assetId, ReactionType? type, String? userId, }) async { - final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, type: type, userId: userId, ); + Future?> getActivities(String albumId, { String? assetId, ReactionType? type, ReactionLevel? level, String? userId, }) async { + final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, type: type, level: level, userId: userId, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index b0e9c822d..0d3c2bfe8 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -309,19 +309,23 @@ class AssetApi { /// /// Parameters: /// + /// * [int] skip: + /// + /// * [int] take: + /// /// * [String] userId: /// /// * [bool] isFavorite: /// /// * [bool] isArchived: /// - /// * [num] skip: - /// /// * [DateTime] updatedAfter: /// + /// * [DateTime] updatedBefore: + /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, num? skip, DateTime? updatedAfter, String? ifNoneMatch, }) async { + Future getAllAssetsWithHttpInfo({ int? skip, int? take, String? userId, bool? isFavorite, bool? isArchived, DateTime? updatedAfter, DateTime? updatedBefore, String? ifNoneMatch, }) async { // ignore: prefer_const_declarations final path = r'/asset'; @@ -332,6 +336,12 @@ class AssetApi { final headerParams = {}; final formParams = {}; + if (skip != null) { + queryParams.addAll(_queryParams('', 'skip', skip)); + } + if (take != null) { + queryParams.addAll(_queryParams('', 'take', take)); + } if (userId != null) { queryParams.addAll(_queryParams('', 'userId', userId)); } @@ -341,12 +351,12 @@ class AssetApi { if (isArchived != null) { queryParams.addAll(_queryParams('', 'isArchived', isArchived)); } - if (skip != null) { - queryParams.addAll(_queryParams('', 'skip', skip)); - } if (updatedAfter != null) { queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); } + if (updatedBefore != null) { + queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore)); + } if (ifNoneMatch != null) { headerParams[r'if-none-match'] = parameterToString(ifNoneMatch); @@ -370,20 +380,24 @@ class AssetApi { /// /// Parameters: /// + /// * [int] skip: + /// + /// * [int] take: + /// /// * [String] userId: /// /// * [bool] isFavorite: /// /// * [bool] isArchived: /// - /// * [num] skip: - /// /// * [DateTime] updatedAfter: /// + /// * [DateTime] updatedBefore: + /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, num? skip, DateTime? updatedAfter, String? ifNoneMatch, }) async { - final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, skip: skip, updatedAfter: updatedAfter, ifNoneMatch: ifNoneMatch, ); + Future?> getAllAssets({ int? skip, int? take, String? userId, bool? isFavorite, bool? isArchived, DateTime? updatedAfter, DateTime? updatedBefore, String? ifNoneMatch, }) async { + final response = await getAllAssetsWithHttpInfo( skip: skip, take: take, userId: userId, isFavorite: isFavorite, isArchived: isArchived, updatedAfter: updatedAfter, updatedBefore: updatedBefore, ifNoneMatch: ifNoneMatch, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1660,8 +1674,6 @@ class AssetApi { /// /// * [DateTime] fileModifiedAt (required): /// - /// * [bool] isFavorite (required): - /// /// * [String] key: /// /// * [String] duration: @@ -1670,6 +1682,8 @@ class AssetApi { /// /// * [bool] isExternal: /// + /// * [bool] isFavorite: + /// /// * [bool] isOffline: /// /// * [bool] isReadOnly: @@ -1681,7 +1695,7 @@ class AssetApi { /// * [MultipartFile] livePhotoData: /// /// * [MultipartFile] sidecarData: - Future uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async { + Future uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isFavorite, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async { // ignore: prefer_const_declarations final path = r'/asset/upload'; @@ -1790,8 +1804,6 @@ class AssetApi { /// /// * [DateTime] fileModifiedAt (required): /// - /// * [bool] isFavorite (required): - /// /// * [String] key: /// /// * [String] duration: @@ -1800,6 +1812,8 @@ class AssetApi { /// /// * [bool] isExternal: /// + /// * [bool] isFavorite: + /// /// * [bool] isOffline: /// /// * [bool] isReadOnly: @@ -1811,8 +1825,8 @@ class AssetApi { /// * [MultipartFile] livePhotoData: /// /// * [MultipartFile] sidecarData: - Future uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async { - final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, duration: duration, isArchived: isArchived, isExternal: isExternal, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, libraryId: libraryId, livePhotoData: livePhotoData, sidecarData: sidecarData, ); + Future uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isFavorite, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async { + final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, duration: duration, isArchived: isArchived, isExternal: isExternal, isFavorite: isFavorite, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, libraryId: libraryId, livePhotoData: livePhotoData, sidecarData: sidecarData, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart index 3b986d34d..7c6dac147 100644 --- a/mobile/openapi/lib/api/authentication_api.dart +++ b/mobile/openapi/lib/api/authentication_api.dart @@ -300,7 +300,7 @@ class AuthenticationApi { /// Parameters: /// /// * [SignUpDto] signUpDto (required): - Future signUpAdmin(SignUpDto signUpDto,) async { + Future signUpAdmin(SignUpDto signUpDto,) async { final response = await signUpAdminWithHttpInfo(signUpDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -309,7 +309,7 @@ class AuthenticationApi { // 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), 'AdminSignupResponseDto',) as AdminSignupResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto; } return null; diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart index c8b0c58ec..f13f8d52d 100644 --- a/mobile/openapi/lib/api/system_config_api.dart +++ b/mobile/openapi/lib/api/system_config_api.dart @@ -98,6 +98,55 @@ class SystemConfigApi { return null; } + /// Performs an HTTP 'GET /system-config/map/style.json' operation and returns the [Response]. + /// Parameters: + /// + /// * [MapTheme] theme (required): + Future getMapStyleWithHttpInfo(MapTheme theme,) async { + // ignore: prefer_const_declarations + final path = r'/system-config/map/style.json'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'theme', theme)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [MapTheme] theme (required): + Future getMapStyle(MapTheme theme,) async { + final response = await getMapStyleWithHttpInfo(theme,); + 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), 'Object',) as Object; + + } + return null; + } + /// Performs an HTTP 'GET /system-config/storage-template-options' operation and returns the [Response]. Future getStorageTemplateOptionsWithHttpInfo() async { // ignore: prefer_const_declarations diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 76a849d71..dfdd5b4ef 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -197,8 +197,6 @@ class ApiClient { return ActivityStatisticsResponseDto.fromJson(value); case 'AddUsersDto': return AddUsersDto.fromJson(value); - case 'AdminSignupResponseDto': - return AdminSignupResponseDto.fromJson(value); case 'AlbumCountResponseDto': return AlbumCountResponseDto.fromJson(value); case 'AlbumResponseDto': @@ -323,6 +321,8 @@ class ApiClient { return LogoutResponseDto.fromJson(value); case 'MapMarkerResponseDto': return MapMarkerResponseDto.fromJson(value); + case 'MapTheme': + return MapThemeTypeTransformer().decode(value); case 'MemoryLaneResponseDto': return MemoryLaneResponseDto.fromJson(value); case 'MergePersonDto': @@ -355,6 +355,8 @@ class ApiClient { return PersonUpdateDto.fromJson(value); case 'QueueStatusDto': return QueueStatusDto.fromJson(value); + case 'ReactionLevel': + return ReactionLevelTypeTransformer().decode(value); case 'ReactionType': return ReactionTypeTypeTransformer().decode(value); case 'RecognitionConfig': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index da870ea99..f39131448 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -88,6 +88,9 @@ String parameterToString(dynamic value) { if (value is LibraryType) { return LibraryTypeTypeTransformer().encode(value).toString(); } + if (value is MapTheme) { + return MapThemeTypeTransformer().encode(value).toString(); + } if (value is ModelType) { return ModelTypeTypeTransformer().encode(value).toString(); } @@ -97,6 +100,9 @@ String parameterToString(dynamic value) { if (value is PathType) { return PathTypeTypeTransformer().encode(value).toString(); } + if (value is ReactionLevel) { + return ReactionLevelTypeTransformer().encode(value).toString(); + } if (value is ReactionType) { return ReactionTypeTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/admin_signup_response_dto.dart b/mobile/openapi/lib/model/admin_signup_response_dto.dart deleted file mode 100644 index 5e6764f23..000000000 --- a/mobile/openapi/lib/model/admin_signup_response_dto.dart +++ /dev/null @@ -1,130 +0,0 @@ -// -// 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 AdminSignupResponseDto { - /// Returns a new [AdminSignupResponseDto] instance. - AdminSignupResponseDto({ - required this.createdAt, - required this.email, - required this.firstName, - required this.id, - required this.lastName, - }); - - DateTime createdAt; - - String email; - - String firstName; - - String id; - - String lastName; - - @override - bool operator ==(Object other) => identical(this, other) || other is AdminSignupResponseDto && - other.createdAt == createdAt && - other.email == email && - other.firstName == firstName && - other.id == id && - other.lastName == lastName; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (createdAt.hashCode) + - (email.hashCode) + - (firstName.hashCode) + - (id.hashCode) + - (lastName.hashCode); - - @override - String toString() => 'AdminSignupResponseDto[createdAt=$createdAt, email=$email, firstName=$firstName, id=$id, lastName=$lastName]'; - - Map toJson() { - final json = {}; - json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); - json[r'email'] = this.email; - json[r'firstName'] = this.firstName; - json[r'id'] = this.id; - json[r'lastName'] = this.lastName; - return json; - } - - /// Returns a new [AdminSignupResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static AdminSignupResponseDto? fromJson(dynamic value) { - if (value is Map) { - final json = value.cast(); - - return AdminSignupResponseDto( - createdAt: mapDateTime(json, r'createdAt', '')!, - email: mapValueOfType(json, r'email')!, - firstName: mapValueOfType(json, r'firstName')!, - id: mapValueOfType(json, r'id')!, - lastName: mapValueOfType(json, r'lastName')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = AdminSignupResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = AdminSignupResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of AdminSignupResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = AdminSignupResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'createdAt', - 'email', - 'firstName', - 'id', - 'lastName', - }; -} - diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index cf2ad9252..86e009e33 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -22,6 +22,7 @@ class AlbumResponseDto { this.endDate, required this.hasSharedLink, required this.id, + required this.isActivityEnabled, this.lastModifiedAssetTimestamp, required this.owner, required this.ownerId, @@ -55,6 +56,8 @@ class AlbumResponseDto { String id; + bool isActivityEnabled; + /// /// 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 @@ -92,6 +95,7 @@ class AlbumResponseDto { other.endDate == endDate && other.hasSharedLink == hasSharedLink && other.id == id && + other.isActivityEnabled == isActivityEnabled && other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp && other.owner == owner && other.ownerId == ownerId && @@ -112,6 +116,7 @@ class AlbumResponseDto { (endDate == null ? 0 : endDate!.hashCode) + (hasSharedLink.hashCode) + (id.hashCode) + + (isActivityEnabled.hashCode) + (lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) + (owner.hashCode) + (ownerId.hashCode) + @@ -121,7 +126,7 @@ class AlbumResponseDto { (updatedAt.hashCode); @override - String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]'; + String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -142,6 +147,7 @@ class AlbumResponseDto { } json[r'hasSharedLink'] = this.hasSharedLink; json[r'id'] = this.id; + json[r'isActivityEnabled'] = this.isActivityEnabled; if (this.lastModifiedAssetTimestamp != null) { json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String(); } else { @@ -177,6 +183,7 @@ class AlbumResponseDto { endDate: mapDateTime(json, r'endDate', ''), hasSharedLink: mapValueOfType(json, r'hasSharedLink')!, id: mapValueOfType(json, r'id')!, + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', ''), owner: UserResponseDto.fromJson(json[r'owner'])!, ownerId: mapValueOfType(json, r'ownerId')!, @@ -239,6 +246,7 @@ class AlbumResponseDto { 'description', 'hasSharedLink', 'id', + 'isActivityEnabled', 'owner', 'ownerId', 'shared', diff --git a/mobile/openapi/lib/model/import_asset_dto.dart b/mobile/openapi/lib/model/import_asset_dto.dart index 66ee3faee..7ba26da9d 100644 --- a/mobile/openapi/lib/model/import_asset_dto.dart +++ b/mobile/openapi/lib/model/import_asset_dto.dart @@ -21,7 +21,7 @@ class ImportAssetDto { required this.fileModifiedAt, this.isArchived, this.isExternal, - required this.isFavorite, + this.isFavorite, this.isOffline, this.isReadOnly = true, this.isVisible, @@ -63,7 +63,13 @@ class ImportAssetDto { /// bool? isExternal; - bool isFavorite; + /// + /// 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. + /// + bool? isFavorite; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -127,7 +133,7 @@ class ImportAssetDto { (fileModifiedAt.hashCode) + (isArchived == null ? 0 : isArchived!.hashCode) + (isExternal == null ? 0 : isExternal!.hashCode) + - (isFavorite.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + (isOffline == null ? 0 : isOffline!.hashCode) + (isReadOnly.hashCode) + (isVisible == null ? 0 : isVisible!.hashCode) + @@ -159,7 +165,11 @@ class ImportAssetDto { } else { // json[r'isExternal'] = null; } + if (this.isFavorite != null) { json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } if (this.isOffline != null) { json[r'isOffline'] = this.isOffline; } else { @@ -200,7 +210,7 @@ class ImportAssetDto { fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!, isArchived: mapValueOfType(json, r'isArchived'), isExternal: mapValueOfType(json, r'isExternal'), - isFavorite: mapValueOfType(json, r'isFavorite')!, + isFavorite: mapValueOfType(json, r'isFavorite'), isOffline: mapValueOfType(json, r'isOffline'), isReadOnly: mapValueOfType(json, r'isReadOnly') ?? true, isVisible: mapValueOfType(json, r'isVisible'), @@ -258,7 +268,6 @@ class ImportAssetDto { 'deviceId', 'fileCreatedAt', 'fileModifiedAt', - 'isFavorite', }; } diff --git a/mobile/openapi/lib/model/map_theme.dart b/mobile/openapi/lib/model/map_theme.dart new file mode 100644 index 000000000..a67d9d2ef --- /dev/null +++ b/mobile/openapi/lib/model/map_theme.dart @@ -0,0 +1,85 @@ +// +// 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 MapTheme { + /// Instantiate a new enum with the provided [value]. + const MapTheme._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const light = MapTheme._(r'light'); + static const dark = MapTheme._(r'dark'); + + /// List of all possible values in this [enum][MapTheme]. + static const values = [ + light, + dark, + ]; + + static MapTheme? fromJson(dynamic value) => MapThemeTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MapTheme.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MapTheme] to String, +/// and [decode] dynamic data back to [MapTheme]. +class MapThemeTypeTransformer { + factory MapThemeTypeTransformer() => _instance ??= const MapThemeTypeTransformer._(); + + const MapThemeTypeTransformer._(); + + String encode(MapTheme data) => data.value; + + /// Decodes a [dynamic value][data] to a MapTheme. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MapTheme? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'light': return MapTheme.light; + case r'dark': return MapTheme.dark; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MapThemeTypeTransformer] instance. + static MapThemeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/reaction_level.dart b/mobile/openapi/lib/model/reaction_level.dart new file mode 100644 index 000000000..5c8b8e62b --- /dev/null +++ b/mobile/openapi/lib/model/reaction_level.dart @@ -0,0 +1,85 @@ +// +// 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 ReactionLevel { + /// Instantiate a new enum with the provided [value]. + const ReactionLevel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const album = ReactionLevel._(r'album'); + static const asset = ReactionLevel._(r'asset'); + + /// List of all possible values in this [enum][ReactionLevel]. + static const values = [ + album, + asset, + ]; + + static ReactionLevel? fromJson(dynamic value) => ReactionLevelTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReactionLevel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ReactionLevel] to String, +/// and [decode] dynamic data back to [ReactionLevel]. +class ReactionLevelTypeTransformer { + factory ReactionLevelTypeTransformer() => _instance ??= const ReactionLevelTypeTransformer._(); + + const ReactionLevelTypeTransformer._(); + + String encode(ReactionLevel data) => data.value; + + /// Decodes a [dynamic value][data] to a ReactionLevel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ReactionLevel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'album': return ReactionLevel.album; + case r'asset': return ReactionLevel.asset; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ReactionLevelTypeTransformer] instance. + static ReactionLevelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart index 25bdeb6d5..ffe5b2af3 100644 --- a/mobile/openapi/lib/model/server_config_dto.dart +++ b/mobile/openapi/lib/model/server_config_dto.dart @@ -15,7 +15,6 @@ class ServerConfigDto { ServerConfigDto({ required this.isInitialized, required this.loginPageMessage, - required this.mapTileUrl, required this.oauthButtonText, required this.trashDays, }); @@ -24,8 +23,6 @@ class ServerConfigDto { String loginPageMessage; - String mapTileUrl; - String oauthButtonText; int trashDays; @@ -34,7 +31,6 @@ class ServerConfigDto { bool operator ==(Object other) => identical(this, other) || other is ServerConfigDto && other.isInitialized == isInitialized && other.loginPageMessage == loginPageMessage && - other.mapTileUrl == mapTileUrl && other.oauthButtonText == oauthButtonText && other.trashDays == trashDays; @@ -43,18 +39,16 @@ class ServerConfigDto { // ignore: unnecessary_parenthesis (isInitialized.hashCode) + (loginPageMessage.hashCode) + - (mapTileUrl.hashCode) + (oauthButtonText.hashCode) + (trashDays.hashCode); @override - String toString() => 'ServerConfigDto[isInitialized=$isInitialized, loginPageMessage=$loginPageMessage, mapTileUrl=$mapTileUrl, oauthButtonText=$oauthButtonText, trashDays=$trashDays]'; + String toString() => 'ServerConfigDto[isInitialized=$isInitialized, loginPageMessage=$loginPageMessage, oauthButtonText=$oauthButtonText, trashDays=$trashDays]'; Map toJson() { final json = {}; json[r'isInitialized'] = this.isInitialized; json[r'loginPageMessage'] = this.loginPageMessage; - json[r'mapTileUrl'] = this.mapTileUrl; json[r'oauthButtonText'] = this.oauthButtonText; json[r'trashDays'] = this.trashDays; return json; @@ -70,7 +64,6 @@ class ServerConfigDto { return ServerConfigDto( isInitialized: mapValueOfType(json, r'isInitialized')!, loginPageMessage: mapValueOfType(json, r'loginPageMessage')!, - mapTileUrl: mapValueOfType(json, r'mapTileUrl')!, oauthButtonText: mapValueOfType(json, r'oauthButtonText')!, trashDays: mapValueOfType(json, r'trashDays')!, ); @@ -122,7 +115,6 @@ class ServerConfigDto { static const requiredKeys = { 'isInitialized', 'loginPageMessage', - 'mapTileUrl', 'oauthButtonText', 'trashDays', }; diff --git a/mobile/openapi/lib/model/system_config_map_dto.dart b/mobile/openapi/lib/model/system_config_map_dto.dart index 1d39f7152..667ae2115 100644 --- a/mobile/openapi/lib/model/system_config_map_dto.dart +++ b/mobile/openapi/lib/model/system_config_map_dto.dart @@ -13,32 +13,38 @@ part of openapi.api; class SystemConfigMapDto { /// Returns a new [SystemConfigMapDto] instance. SystemConfigMapDto({ + required this.darkStyle, required this.enabled, - required this.tileUrl, + required this.lightStyle, }); + String darkStyle; + bool enabled; - String tileUrl; + String lightStyle; @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigMapDto && + other.darkStyle == darkStyle && other.enabled == enabled && - other.tileUrl == tileUrl; + other.lightStyle == lightStyle; @override int get hashCode => // ignore: unnecessary_parenthesis + (darkStyle.hashCode) + (enabled.hashCode) + - (tileUrl.hashCode); + (lightStyle.hashCode); @override - String toString() => 'SystemConfigMapDto[enabled=$enabled, tileUrl=$tileUrl]'; + String toString() => 'SystemConfigMapDto[darkStyle=$darkStyle, enabled=$enabled, lightStyle=$lightStyle]'; Map toJson() { final json = {}; + json[r'darkStyle'] = this.darkStyle; json[r'enabled'] = this.enabled; - json[r'tileUrl'] = this.tileUrl; + json[r'lightStyle'] = this.lightStyle; return json; } @@ -50,8 +56,9 @@ class SystemConfigMapDto { final json = value.cast(); return SystemConfigMapDto( + darkStyle: mapValueOfType(json, r'darkStyle')!, enabled: mapValueOfType(json, r'enabled')!, - tileUrl: mapValueOfType(json, r'tileUrl')!, + lightStyle: mapValueOfType(json, r'lightStyle')!, ); } return null; @@ -99,8 +106,9 @@ class SystemConfigMapDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'darkStyle', 'enabled', - 'tileUrl', + 'lightStyle', }; } diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart index 6c0bf3eca..32d4d2a60 100644 --- a/mobile/openapi/lib/model/update_album_dto.dart +++ b/mobile/openapi/lib/model/update_album_dto.dart @@ -16,6 +16,7 @@ class UpdateAlbumDto { this.albumName, this.albumThumbnailAssetId, this.description, + this.isActivityEnabled, }); /// @@ -42,21 +43,31 @@ class UpdateAlbumDto { /// String? description; + /// + /// 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. + /// + bool? isActivityEnabled; + @override bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto && other.albumName == albumName && other.albumThumbnailAssetId == albumThumbnailAssetId && - other.description == description; + other.description == description && + other.isActivityEnabled == isActivityEnabled; @override int get hashCode => // ignore: unnecessary_parenthesis (albumName == null ? 0 : albumName!.hashCode) + (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + - (description == null ? 0 : description!.hashCode); + (description == null ? 0 : description!.hashCode) + + (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode); @override - String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description]'; + String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled]'; Map toJson() { final json = {}; @@ -75,6 +86,11 @@ class UpdateAlbumDto { } else { // json[r'description'] = null; } + if (this.isActivityEnabled != null) { + json[r'isActivityEnabled'] = this.isActivityEnabled; + } else { + // json[r'isActivityEnabled'] = null; + } return json; } @@ -89,6 +105,7 @@ class UpdateAlbumDto { albumName: mapValueOfType(json, r'albumName'), albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), description: mapValueOfType(json, r'description'), + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled'), ); } return null; diff --git a/mobile/openapi/test/activity_api_test.dart b/mobile/openapi/test/activity_api_test.dart index 9b6fe1a6c..7353c6ea3 100644 --- a/mobile/openapi/test/activity_api_test.dart +++ b/mobile/openapi/test/activity_api_test.dart @@ -27,7 +27,7 @@ void main() { // TODO }); - //Future> getActivities(String albumId, { String assetId, ReactionType type, String userId }) async + //Future> getActivities(String albumId, { String assetId, ReactionType type, ReactionLevel level, String userId }) async test('test getActivities', () async { // TODO }); diff --git a/mobile/openapi/test/admin_signup_response_dto_test.dart b/mobile/openapi/test/admin_signup_response_dto_test.dart deleted file mode 100644 index 62582e9b3..000000000 --- a/mobile/openapi/test/admin_signup_response_dto_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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 AdminSignupResponseDto -void main() { - // final instance = AdminSignupResponseDto(); - - group('test AdminSignupResponseDto', () { - // DateTime createdAt - test('to test the property `createdAt`', () async { - // TODO - }); - - // String email - test('to test the property `email`', () async { - // TODO - }); - - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - - // String id - test('to test the property `id`', () async { - // TODO - }); - - // String lastName - test('to test the property `lastName`', () async { - // TODO - }); - - - }); - -} diff --git a/mobile/openapi/test/album_response_dto_test.dart b/mobile/openapi/test/album_response_dto_test.dart index c84174200..933f77c19 100644 --- a/mobile/openapi/test/album_response_dto_test.dart +++ b/mobile/openapi/test/album_response_dto_test.dart @@ -61,6 +61,11 @@ void main() { // TODO }); + // bool isActivityEnabled + test('to test the property `isActivityEnabled`', () async { + // TODO + }); + // DateTime lastModifiedAssetTimestamp test('to test the property `lastModifiedAssetTimestamp`', () async { // TODO diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 453982cf1..e241c34ca 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -53,7 +53,7 @@ void main() { // Get all AssetEntity belong to the user // - //Future> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, DateTime updatedAfter, String ifNoneMatch }) async + //Future> getAllAssets({ int skip, int take, String userId, bool isFavorite, bool isArchived, DateTime updatedAfter, DateTime updatedBefore, String ifNoneMatch }) async test('test getAllAssets', () async { // TODO }); @@ -172,7 +172,7 @@ void main() { // TODO }); - //Future uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, String duration, bool isArchived, bool isExternal, bool isOffline, bool isReadOnly, bool isVisible, String libraryId, MultipartFile livePhotoData, MultipartFile sidecarData }) async + //Future uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String key, String duration, bool isArchived, bool isExternal, bool isFavorite, bool isOffline, bool isReadOnly, bool isVisible, String libraryId, MultipartFile livePhotoData, MultipartFile sidecarData }) async test('test uploadFile', () async { // TODO }); diff --git a/mobile/openapi/test/authentication_api_test.dart b/mobile/openapi/test/authentication_api_test.dart index 22eb7550f..aa2f1879d 100644 --- a/mobile/openapi/test/authentication_api_test.dart +++ b/mobile/openapi/test/authentication_api_test.dart @@ -47,7 +47,7 @@ void main() { // TODO }); - //Future signUpAdmin(SignUpDto signUpDto) async + //Future signUpAdmin(SignUpDto signUpDto) async test('test signUpAdmin', () async { // TODO }); diff --git a/mobile/openapi/test/map_theme_test.dart b/mobile/openapi/test/map_theme_test.dart new file mode 100644 index 000000000..82fa9ff3d --- /dev/null +++ b/mobile/openapi/test/map_theme_test.dart @@ -0,0 +1,21 @@ +// +// 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 MapTheme +void main() { + + group('test MapTheme', () { + + }); + +} diff --git a/mobile/openapi/test/reaction_level_test.dart b/mobile/openapi/test/reaction_level_test.dart new file mode 100644 index 000000000..6fcba58b1 --- /dev/null +++ b/mobile/openapi/test/reaction_level_test.dart @@ -0,0 +1,21 @@ +// +// 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 ReactionLevel +void main() { + + group('test ReactionLevel', () { + + }); + +} diff --git a/mobile/openapi/test/server_config_dto_test.dart b/mobile/openapi/test/server_config_dto_test.dart index 0aa581b13..d43e47823 100644 --- a/mobile/openapi/test/server_config_dto_test.dart +++ b/mobile/openapi/test/server_config_dto_test.dart @@ -26,11 +26,6 @@ void main() { // TODO }); - // String mapTileUrl - test('to test the property `mapTileUrl`', () async { - // TODO - }); - // String oauthButtonText test('to test the property `oauthButtonText`', () async { // TODO diff --git a/mobile/openapi/test/system_config_api_test.dart b/mobile/openapi/test/system_config_api_test.dart index b8e57aaae..fcf1ecfc5 100644 --- a/mobile/openapi/test/system_config_api_test.dart +++ b/mobile/openapi/test/system_config_api_test.dart @@ -27,6 +27,11 @@ void main() { // TODO }); + //Future getMapStyle(MapTheme theme) async + test('test getMapStyle', () async { + // TODO + }); + //Future getStorageTemplateOptions() async test('test getStorageTemplateOptions', () async { // TODO diff --git a/mobile/openapi/test/system_config_map_dto_test.dart b/mobile/openapi/test/system_config_map_dto_test.dart index d0b753dc0..b42753d4f 100644 --- a/mobile/openapi/test/system_config_map_dto_test.dart +++ b/mobile/openapi/test/system_config_map_dto_test.dart @@ -16,13 +16,18 @@ void main() { // final instance = SystemConfigMapDto(); group('test SystemConfigMapDto', () { + // String darkStyle + test('to test the property `darkStyle`', () async { + // TODO + }); + // bool enabled test('to test the property `enabled`', () async { // TODO }); - // String tileUrl - test('to test the property `tileUrl`', () async { + // String lightStyle + test('to test the property `lightStyle`', () async { // TODO }); diff --git a/mobile/openapi/test/update_album_dto_test.dart b/mobile/openapi/test/update_album_dto_test.dart index 7b8472ad3..67ec80010 100644 --- a/mobile/openapi/test/update_album_dto_test.dart +++ b/mobile/openapi/test/update_album_dto_test.dart @@ -31,6 +31,11 @@ void main() { // TODO }); + // bool isActivityEnabled + test('to test the property `isActivityEnabled`', () async { + // TODO + }); + }); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 3fc34f62e..eca0d1246 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -345,6 +345,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.2" + executor_lib: + dependency: "direct main" + description: + name: executor_lib + sha256: "544889daa5726462657dab6410b75f2f8e3a77479d85b307a25c346e243bc38e" + url: "https://pub.dev" + source: hosted + version: "1.1.1" fake_async: dependency: transitive description: @@ -1131,6 +1139,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" + url: "https://pub.dev" + source: hosted + version: "2.1.0" provider: dependency: transitive description: @@ -1520,6 +1536,15 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + vector_map_tiles: + dependency: "direct main" + description: + path: "." + ref: immich_above_4 + resolved-ref: dc685bdbcca2ff2b49b4d0fb77b7bc17fad48608 + url: "https://github.com/shenlong-tanwen/flutter-vector-map-tiles.git" + source: git + version: "4.0.0" vector_math: dependency: transitive description: @@ -1528,6 +1553,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vector_tile: + dependency: transitive + description: + name: vector_tile + sha256: "2ac77f6bbd7ddd97efe059207d67bb7eaf807ab98ad58d99fe41a22c230f44e1" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + vector_tile_renderer: + dependency: "direct main" + description: + name: vector_tile_renderer + sha256: de212da0f5e48107d3b763a940a428eb1f49d8a4664d41ac0b654f77209a2d0b + url: "https://pub.dev" + source: hosted + version: "4.0.0" video_player: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 6d5aa735d..943df3713 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,7 +2,7 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: "none" -version: 1.84.0+108 +version: 1.85.0+109 isar_version: &isar_version 3.1.0+1 environment: @@ -28,6 +28,13 @@ dependencies: flutter_map: ^4.0.0 flutter_map_heatmap: ^0.0.4 geolocator: ^10.0.0 # used to move to current location in map view + vector_map_tiles: + git: + url: https://github.com/shenlong-tanwen/flutter-vector-map-tiles.git + ref: immich_above_4 + # Adding the following as direct dependency since flutter cannot detect them as transitive dep + vector_tile_renderer: ^4.0.0 + executor_lib: 1.1.1 flutter_udid: ^2.0.0 package_info_plus: ^4.1.0 url_launcher: ^6.1.3 diff --git a/mobile/test/builtin_extensions_test.dart b/mobile/test/builtin_extensions_test.dart index 875a20fb0..9fc729774 100644 --- a/mobile/test/builtin_extensions_test.dart +++ b/mobile/test/builtin_extensions_test.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/utils/builtin_extensions.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; void main() { group('Test toDuration', () { diff --git a/server/Dockerfile b/server/Dockerfile index fe843968f..02aa18611 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,55 +1,24 @@ -FROM node:20.8-bookworm as builder - -WORKDIR /usr/src/app - -COPY bin/install-ffmpeg.sh build-lock.json ./ -RUN sed -i -e's/ main/ main contrib non-free non-free-firmware/g' /etc/apt/sources.list.d/debian.sources -RUN apt-get update && apt-get install -yqq build-essential ninja-build meson pkg-config jq zlib1g autoconf \ -libglib2.0-dev libexpat1-dev librsvg2-dev libexif-dev libwebp-dev liborc-0.4-dev \ -libjpeg62-turbo-dev libgsf-1-dev libspng-dev libjxl-dev libheif-dev liblcms2-2 \ -mesa-va-drivers libmimalloc2.0 $(if [ $(arch) = "x86_64" ]; then echo "intel-media-va-driver-non-free"; fi) \ -&& ./install-ffmpeg.sh && apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* - -# debian build for imagemagick has broken RAW support, so build manually -ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -ENV LD_RUN_PATH=/usr/local/lib:$LD_RUN_PATH -COPY bin/build-libraw.sh bin/build-imagemagick.sh bin/build-libvips.sh bin/use-camera-wb.patch ./ -RUN ./build-libraw.sh -RUN ./build-imagemagick.sh -RUN ./build-libvips.sh +FROM ghcr.io/immich-app/base-server-dev:20231109 as builder COPY package.json package-lock.json ./ - RUN npm ci - COPY . . + FROM builder as prod RUN npm run build RUN npm prune --omit=dev --omit=optional -FROM node:20.8-bookworm + +FROM ghcr.io/immich-app/base-server-prod:20231109 ENV NODE_ENV=production -WORKDIR /usr/src/app - -COPY bin/install-ffmpeg.sh build-lock.json ./ -RUN sed -i -e's/ main/ main contrib non-free non-free-firmware/g' /etc/apt/sources.list.d/debian.sources -RUN apt-get update && apt-get install -yqq tini libheif1 libwebp7 libwebpdemux2 libwebpmux3 mesa-va-drivers \ -libjpeg62-turbo libexpat1 librsvg2-2 libjxl0.7 libspng0 libexif12 libgcc-s1 libglib2.0-0 \ -libgsf-1-114 libopenjp2-7 liblcms2-2 liborc-0.4-0 libopenexr-3-1-30 liblqr-1-0 libltdl7 zlib1g libgomp1 \ -mesa-va-drivers libmimalloc2.0 $(if [ $(arch) = "x86_64" ]; then echo "intel-media-va-driver-non-free"; fi) jq wget \ -&& ./install-ffmpeg.sh && apt-get remove -yqq jq wget && apt-get autoremove -yqq && apt-get clean && rm -rf /var/lib/apt/lists/* \ -&& rm install-ffmpeg.sh && rm build-lock.json - -COPY --from=prod /usr/local/lib/ /usr/local/lib/ -RUN ldconfig /usr/local/lib - COPY --from=prod /usr/src/app/node_modules ./node_modules COPY --from=prod /usr/src/app/dist ./dist COPY --from=prod /usr/src/app/bin ./bin +COPY ./assets ./assets COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE diff --git a/server/assets/style-dark.json b/server/assets/style-dark.json new file mode 100644 index 000000000..43ddb8c1c --- /dev/null +++ b/server/assets/style-dark.json @@ -0,0 +1,1895 @@ +{ + "version": 8, + "name": "Immich Map", + "metadata": { "maputnik:renderer": "mbgljs" }, + "sources": { + "immich-map": { + "type": "vector", + "url": "https://api-l.cofractal.com/v0/maps/vt/overture" + } + }, + "sprite": "https://maputnik.github.io/osm-liberty/sprites/osm-liberty", + "glyphs": "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { "background-color": "rgb(42,42,41)" } + }, + { + "id": "park", + "type": "fill", + "source": "immich-map", + "source-layer": "park", + "paint": { + "fill-color": "rgba(8, 8, 7, 1)", + "fill-opacity": 0.7, + "fill-outline-color": "rgba(0, 0, 0, 1)", + "fill-antialias": false + } + }, + { + "id": "park_outline", + "type": "line", + "source": "immich-map", + "source-layer": "park", + "paint": { "line-dasharray": [1, 1.5], "line-color": "rgba(55, 55, 55, 1)" } + }, + { + "id": "landuse_residential", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "maxzoom": 8, + "filter": ["==", "class", "residential"], + "paint": { + "fill-color": { + "base": 1, + "stops": [ + [9, "rgba(59, 56, 56, 0.84)"], + [12, "hsla(35, 57%, 88%, 0.49)"] + ] + } + } + }, + { + "id": "landcover_wood", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "wood"]], + "paint": { + "fill-antialias": false, + "fill-color": "rgba(186, 209, 173, 0.3)", + "fill-opacity": 0.4 + } + }, + { + "id": "landcover_grass", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "grass"]], + "paint": { + "fill-antialias": false, + "fill-color": "rgba(176, 213, 154, 0.2)", + "fill-opacity": 0.3 + } + }, + { + "id": "landcover_ice", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "ice"]], + "paint": { + "fill-antialias": false, + "fill-color": "rgba(94, 100, 100, 1)", + "fill-opacity": 0.8 + } + }, + { + "id": "landuse_cemetery", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "cemetery"], + "layout": { "visibility": "none" }, + "paint": { "fill-color": "rgba(69, 69, 65, 1)" } + }, + { + "id": "landuse_hospital", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "hospital"], + "layout": { "visibility": "none" }, + "paint": { "fill-color": "#fde" } + }, + { + "id": "landuse_school", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "school"], + "layout": { "visibility": "none" }, + "paint": { "fill-color": "rgb(236,238,204)" } + }, + { + "id": "waterway_tunnel", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "brunnel", "tunnel"]], + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [3, 3], + "line-gap-width": { + "stops": [ + [12, 0], + [20, 6] + ] + }, + "line-opacity": 1, + "line-width": { + "base": 1.4, + "stops": [ + [8, 1], + [20, 2] + ] + } + } + }, + { + "id": "waterway_river", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "class", "river"], ["!=", "brunnel", "tunnel"]], + "layout": { "line-cap": "round" }, + "paint": { + "line-color": "rgba(78, 85, 88, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [11, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "waterway_other", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["!=", "class", "river"], ["!=", "brunnel", "tunnel"]], + "layout": { "line-cap": "round" }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "source": "immich-map", + "source-layer": "water", + "filter": ["all", ["!=", "brunnel", "tunnel"]], + "paint": { "fill-color": "rgba(26, 26, 26, 1)" } + }, + { + "id": "landcover_sand", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "sand"]], + "paint": { "fill-color": "rgba(193, 192, 188, 1)" } + }, + { + "id": "aeroway_fill", + "type": "fill", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["==", "$type", "Polygon"], + "paint": { "fill-color": "rgba(229, 228, 224, 1)", "fill-opacity": 0.7 } + }, + { + "id": "aeroway_runway", + "type": "line", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["all", ["==", "$type", "LineString"], ["==", "class", "runway"]], + "paint": { + "line-color": "#f0ede9", + "line-width": { + "base": 1.2, + "stops": [ + [11, 3], + [20, 16] + ] + } + } + }, + { + "id": "aeroway_taxiway", + "type": "line", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["all", ["==", "$type", "LineString"], ["==", "class", "taxiway"]], + "paint": { + "line-color": "#f0ede9", + "line-width": { + "base": 1.2, + "stops": [ + [11, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "tunnel_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(52, 51, 49, 1)", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_service_track_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "tunnel_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(40, 38, 36, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_street_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "street", "street_limited"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "tunnel_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(100, 86, 69, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "tunnel_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(28, 26, 26, 1)", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "tunnel_path_pedestrian", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "tunnel"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [1, 0.75], + "line-width": { + "base": 1.2, + "stops": [ + [14, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "tunnel_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "tunnel_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(149, 139, 93, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_minor", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "minor"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "tunnel_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(116, 114, 97, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "tunnel_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(129, 124, 110, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "tunnel_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "tunnel_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "tunnel_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "tunnel_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "road_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 12, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["==", "ramp", 1]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(65, 63, 62, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "road_minor_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "minor"], + ["!=", "ramp", 1] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(17, 17, 17, 1)", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 20] + ] + } + } + }, + { + "id": "road_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(102, 102, 102, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "road_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(61, 61, 61, 0.6)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "road_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 5, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["!=", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(61, 61, 61, 0.6)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "road_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 12, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["==", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(184, 184, 179, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "road_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "service", "track"]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(84, 81, 81, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "road_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "ramp", 1], + ["!in", "class", "pedestrian", "path", "track", "service", "motorway"] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "road_minor", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "minor"] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(40, 40, 40, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 18] + ] + } + } + }, + { + "id": "road_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(36, 33, 33, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [8, 0.5], + [20, 13] + ] + } + } + }, + { + "id": "road_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(61, 61, 61, 0.6)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "road_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 5, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["!=", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "rgba(61, 61, 61, 0.6)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "road_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "road_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "road_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "road_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "bridge_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round", "visibility": "visible" }, + "paint": { + "line-color": "rgba(75, 68, 63, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "bridge_service_track_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "bridge_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "link"], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "bridge_street_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "street", "street_limited"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "hsl(36, 6%, 74%)", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 25] + ] + } + } + }, + { + "id": "bridge_path_pedestrian_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(35, 6%, 80%)", + "line-dasharray": [1, 0], + "line-width": { + "base": 1.2, + "stops": [ + [14, 1.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(61, 57, 52, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "bridge_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(102, 102, 102, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "bridge_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round", "visibility": "none" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "bridge_path_pedestrian", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [1, 0.3], + "line-width": { + "base": 1.2, + "stops": [ + [14, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "bridge_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round", "visibility": "none" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "bridge_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "link"], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge_street", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "minor"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(73, 71, 68, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "bridge_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "rgba(147, 147, 143, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "bridge_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round", "visibility": "none" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "bridge_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "rail"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "bridge_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "rail"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "bridge_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "transit"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "bridge_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "transit"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "building", + "type": "fill", + "source": "immich-map", + "source-layer": "building", + "minzoom": 13, + "maxzoom": 14, + "paint": { + "fill-color": "rgba(20, 20, 20, 1)", + "fill-outline-color": { + "base": 1, + "stops": [ + [13, "rgba(10, 10, 9, 0.32)"], + [14, "rgba(22, 22, 22, 1)"] + ] + } + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "immich-map", + "source-layer": "building", + "minzoom": 14, + "paint": { + "fill-extrusion-color": "rgba(57, 57, 57, 1)", + "fill-extrusion-height": { + "property": "render_height", + "type": "identity" + }, + "fill-extrusion-base": { + "property": "render_min_height", + "type": "identity" + }, + "fill-extrusion-opacity": 0.8 + } + }, + { + "id": "boundary_state", + "type": "line", + "source": "immich-map", + "source-layer": "boundary" + }, + { + "id": "boundary_3", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "minzoom": 8, + "filter": ["all", ["in", "admin_level", 3, 4]], + "layout": { "line-join": "round", "visibility": "visible" }, + "paint": { + "line-color": "#9e9cab", + "line-dasharray": [5, 1], + "line-width": { + "base": 1, + "stops": [ + [4, 0.4], + [5, 1], + [12, 1.8] + ] + } + } + }, + { + "id": "boundary_country", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "maxzoom": 5, + "filter": ["all", ["==", "admin_level", 2], ["!has", "claimed_by"]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "hsl(248, 1%, 41%)", + "line-opacity": { + "base": 1, + "stops": [ + [0, 0.4], + [4, 1] + ] + }, + "line-width": { + "base": 1, + "stops": [ + [3, 1], + [5, 1.2], + [12, 3] + ] + } + } + }, + { + "id": "boundary_2_z5-", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "minzoom": 5, + "filter": ["all", ["==", "admin_level", 2]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "none" + }, + "paint": { + "line-color": "hsl(248, 1%, 41%)", + "line-opacity": { + "base": 1, + "stops": [ + [0, 0.4], + [4, 1] + ] + }, + "line-width": { + "base": 1, + "stops": [ + [3, 1], + [5, 1.2], + [12, 3] + ] + } + } + }, + { + "id": "water_name_line", + "type": "symbol", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "$type", "LineString"]], + "layout": { + "text-field": "{name}", + "text-font": ["Open Sans Bold"], + "text-max-width": 5, + "text-size": 12, + "symbol-placement": "line" + }, + "paint": { + "text-color": "rgba(70, 178, 228, 1)", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 0 + } + }, + { + "id": "water_name_point", + "type": "symbol", + "source": "immich-map", + "source-layer": "water_name", + "filter": ["==", "$type", "Point"], + "layout": { + "text-field": "{name}", + "text-font": ["Open Sans Regular"], + "text-max-width": 5, + "text-size": 12 + }, + "paint": { + "text-color": "rgba(193, 193, 193, 1)", + "text-halo-color": "rgba(92, 105, 106, 0.7)", + "text-halo-width": 1 + } + }, + { + "id": "poi_z16", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 16, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 20]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12, + "visibility": "none" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi_z15", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 15, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 7], ["<", "rank", 20]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12 + }, + "paint": { + "text-color": "rgba(252, 135, 145, 1)", + "text-halo-blur": 0.5, + "text-halo-color": "rgba(54, 49, 49, 1)", + "text-halo-width": 1 + } + }, + { + "id": "poi_z14", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 14, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 1], ["<", "rank", 7]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Bold Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12 + }, + "paint": { + "text-color": "rgba(153, 242, 197, 1)", + "text-halo-blur": 0.5, + "text-halo-color": "rgba(0, 0, 0, 1)", + "text-halo-width": 0 + } + }, + { + "id": "poi_transit", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "filter": ["all", ["in", "class", "bus", "rail", "airport"]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "left", + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0.9, 0], + "text-size": 12, + "visibility": "none" + }, + "paint": { + "text-color": "#4898ff", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "road_label", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation_name", + "filter": ["all"], + "layout": { + "symbol-placement": "line", + "text-anchor": "center", + "text-field": "{name}", + "text-font": ["Open Sans Bold"], + "text-offset": [0, 0.15], + "text-size": { + "base": 1, + "stops": [ + [13, 12], + [14, 13] + ] + } + }, + "paint": { + "text-color": "rgba(210, 210, 210, 1)", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "road_shield", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation_name", + "minzoom": 7, + "filter": ["all", ["<=", "ref_length", 6]], + "layout": { + "icon-image": "default_{ref_length}", + "icon-rotation-alignment": "viewport", + "symbol-placement": { + "base": 1, + "stops": [ + [10, "point"], + [11, "line"] + ] + }, + "symbol-spacing": 500, + "text-field": "{ref}", + "text-font": ["Open Sans Regular"], + "text-offset": [0, 0.1], + "text-rotation-alignment": "viewport", + "text-size": 10, + "icon-size": 0.8, + "visibility": "none" + } + }, + { + "id": "place_other", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["in", "class", "hamlet", "island", "islet", "neighbourhood", "suburb", "quarter"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": { + "base": 1.2, + "stops": [ + [12, 10], + [15, 14] + ] + }, + "text-transform": "uppercase" + }, + "paint": { + "text-color": "rgba(255, 255, 255, 1)", + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place_village", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "class", "village"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Regular"], + "text-max-width": 8, + "text-size": { + "base": 1.2, + "stops": [ + [10, 12], + [15, 22] + ] + } + }, + "paint": { + "text-color": "rgba(189, 189, 189, 1)", + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 1 + } + }, + { + "id": "place_town", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "class", "town"]], + "layout": { + "icon-image": { + "base": 1, + "stops": [ + [0, "dot_9"], + [8, ""] + ] + }, + "text-anchor": "bottom", + "text-field": "{name_en}", + "text-font": ["Klokantech Noto Sans Regular"], + "text-max-width": 8, + "text-offset": [0, 0], + "text-size": { + "base": 1.2, + "stops": [ + [7, 12], + [11, 16] + ] + } + }, + "paint": { + "text-color": "rgba(247, 247, 247, 0.5)", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 0 + } + }, + { + "id": "place_city", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "minzoom": 5, + "filter": ["all", ["==", "class", "city"]], + "layout": { + "icon-image": { + "base": 1, + "stops": [ + [0, "dot_9"], + [8, ""] + ] + }, + "text-anchor": "bottom", + "text-field": "{name_en}", + "text-font": ["Open Sans Semibold"], + "text-max-width": 8, + "text-offset": [0, 0], + "text-size": { + "base": 0.5, + "stops": [ + [7, 14], + [11, 24] + ] + }, + "icon-allow-overlap": true, + "icon-optional": false + }, + "paint": { + "text-color": "rgba(230, 230, 230, 1)", + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 0.5 + } + }, + { + "id": "state", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "minzoom": 4, + "maxzoom": 6, + "filter": ["all", ["==", "class", "state"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Klokantech Noto Sans Regular"], + "text-size": { + "stops": [ + [4, 9], + [6, 15] + ] + }, + "text-transform": "uppercase" + }, + "paint": { + "text-color": "rgba(226, 219, 219, 1)", + "text-halo-color": "rgba(0, 0, 0, 0.7)", + "text-halo-width": 1, + "text-halo-blur": 0, + "text-translate": [1, 1] + } + }, + { + "id": "country_3", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", [">=", "rank", 3], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Klokantech Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [1, 11], + [4, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "rgba(226, 221, 221, 1)", + "text-halo-blur": 1, + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 1 + } + }, + { + "id": "country_2", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "rank", 2], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Klokantech Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [1, 11], + [4, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "rgba(226, 221, 221, 1)", + "text-halo-blur": 1, + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 1 + } + }, + { + "id": "country_1", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "rank", 1], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Klokantech Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [1, 11], + [4, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "rgba(226, 221, 221, 1)", + "text-halo-blur": 1, + "text-halo-color": "rgba(0, 0, 0, 0.8)", + "text-halo-width": 1 + } + } + ], + "id": "immich-map-dark" +} diff --git a/server/assets/style-light.json b/server/assets/style-light.json new file mode 100644 index 000000000..ac372d4ca --- /dev/null +++ b/server/assets/style-light.json @@ -0,0 +1,2000 @@ +{ + "version": 8, + "name": "Immich Map", + "metadata": { "maputnik:renderer": "mbgljs" }, + "sources": { + "immich-map": { + "type": "vector", + "url": "https://api-l.cofractal.com/v0/maps/vt/overture" + } + }, + "sprite": "https://maputnik.github.io/osm-liberty/sprites/osm-liberty", + "glyphs": "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { "background-color": "rgba(232, 244, 237, 1)" } + }, + { + "id": "park", + "type": "fill", + "source": "immich-map", + "source-layer": "park", + "paint": { + "fill-color": "#d8e8c8", + "fill-opacity": 0.7, + "fill-outline-color": "rgba(95, 208, 100, 1)" + } + }, + { + "id": "park_outline", + "type": "line", + "source": "immich-map", + "source-layer": "park", + "paint": { + "line-dasharray": [1, 1.5], + "line-color": "rgba(228, 241, 215, 1)" + } + }, + { + "id": "landuse_residential", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "maxzoom": 8, + "filter": ["==", "class", "residential"], + "paint": { + "fill-color": { + "base": 1, + "stops": [ + [9, "hsla(0, 3%, 85%, 0.84)"], + [12, "hsla(35, 57%, 88%, 0.49)"] + ] + } + } + }, + { + "id": "landcover_wood", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "wood"]], + "paint": { + "fill-antialias": false, + "fill-color": "hsla(98, 61%, 72%, 0.7)", + "fill-opacity": 0.4 + } + }, + { + "id": "landcover_grass", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "grass"]], + "paint": { + "fill-antialias": false, + "fill-color": "rgba(176, 213, 154, 1)", + "fill-opacity": 0.3 + } + }, + { + "id": "landcover_ice", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "ice"]], + "paint": { + "fill-antialias": false, + "fill-color": "rgba(224, 236, 236, 1)", + "fill-opacity": 0.8 + } + }, + { + "id": "landuse_cemetery", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "cemetery"], + "paint": { "fill-color": "hsl(75, 37%, 81%)" } + }, + { + "id": "landuse_hospital", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "hospital"], + "paint": { "fill-color": "#fde" } + }, + { + "id": "landuse_school", + "type": "fill", + "source": "immich-map", + "source-layer": "landuse", + "filter": ["==", "class", "school"], + "paint": { "fill-color": "rgb(236,238,204)" } + }, + { + "id": "waterway_tunnel", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "brunnel", "tunnel"]], + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [3, 3], + "line-gap-width": { + "stops": [ + [12, 0], + [20, 6] + ] + }, + "line-opacity": 1, + "line-width": { + "base": 1.4, + "stops": [ + [8, 1], + [20, 2] + ] + } + } + }, + { + "id": "waterway_river", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "class", "river"], ["!=", "brunnel", "tunnel"]], + "layout": { "line-cap": "round" }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.2, + "stops": [ + [11, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "waterway_other", + "type": "line", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["!=", "class", "river"], ["!=", "brunnel", "tunnel"]], + "layout": { "line-cap": "round" }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "source": "immich-map", + "source-layer": "water", + "filter": ["all", ["!=", "brunnel", "tunnel"]], + "paint": { "fill-color": "rgba(148, 209, 236, 0.66)" } + }, + { + "id": "landcover_sand", + "type": "fill", + "source": "immich-map", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "sand"]], + "paint": { "fill-color": "rgba(247, 239, 195, 1)" } + }, + { + "id": "aeroway_fill", + "type": "fill", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["==", "$type", "Polygon"], + "paint": { "fill-color": "rgba(229, 228, 224, 1)", "fill-opacity": 0.7 } + }, + { + "id": "aeroway_runway", + "type": "line", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["all", ["==", "$type", "LineString"], ["==", "class", "runway"]], + "paint": { + "line-color": "#f0ede9", + "line-width": { + "base": 1.2, + "stops": [ + [11, 3], + [20, 16] + ] + } + } + }, + { + "id": "aeroway_taxiway", + "type": "line", + "source": "immich-map", + "source-layer": "aeroway", + "minzoom": 11, + "filter": ["all", ["==", "$type", "LineString"], ["==", "class", "taxiway"]], + "paint": { + "line-color": "#f0ede9", + "line-width": { + "base": 1.2, + "stops": [ + [11, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "tunnel_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_service_track_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "tunnel_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_street_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "street", "street_limited"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "tunnel_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "tunnel_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "tunnel_path_pedestrian", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "tunnel"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [1, 0.75], + "line-width": { + "base": 1.2, + "stops": [ + [14, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "tunnel_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "tunnel_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_minor", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "minor"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "tunnel_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "tunnel_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "tunnel"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#ffdaa6", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "tunnel_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "tunnel_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "tunnel_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["in", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "tunnel_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "road_area_pattern", + "type": "fill", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "$type", "Polygon"]], + "paint": { "fill-pattern": "pedestrian_polygon" } + }, + { + "id": "road_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 12, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["==", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "road_service_track_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "road_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["!in", "class", "pedestrian", "path", "track", "service", "motorway"], + ["==", "ramp", 1] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "road_minor_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "minor"], + ["!=", "ramp", 1] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 20] + ] + } + } + }, + { + "id": "road_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "road_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "road_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 5, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["!=", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "road_path_pedestrian", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 14, + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "path", "pedestrian"] + ], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [1, 0.7], + "line-width": { + "base": 1.2, + "stops": [ + [14, 1], + [20, 10] + ] + } + } + }, + { + "id": "road_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 12, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["==", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "road_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "service", "track"]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "road_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "ramp", 1], + ["!in", "class", "pedestrian", "path", "track", "service", "motorway"] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "road_minor", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "minor"] + ], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 18] + ] + } + } + }, + { + "id": "road_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [8, 0.5], + [20, 13] + ] + } + } + }, + { + "id": "road_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "road_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 5, + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "motorway"], ["!=", "ramp", 1]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": { + "base": 1, + "stops": [ + [5, "hsl(26, 87%, 62%)"], + [6, "#fc8"] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "road_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "road_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "road_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "road_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["!in", "brunnel", "bridge", "tunnel"], ["==", "class", "transit"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "road_one_way_arrow", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 15, + "filter": ["==", "oneway", 1], + "layout": { "icon-image": "arrow", "symbol-placement": "line" } + }, + { + "id": "road_one_way_arrow_opposite", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation", + "minzoom": 15, + "filter": ["==", "oneway", -1], + "layout": { + "icon-image": "arrow", + "symbol-placement": "line", + "icon-rotate": 180 + } + }, + { + "id": "bridge_motorway_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "bridge_service_track_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#cfcdca", + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "bridge_link_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "link"], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "bridge_street_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "street", "street_limited"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "hsl(36, 6%, 74%)", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 25] + ] + } + } + }, + { + "id": "bridge_path_pedestrian_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(35, 6%, 80%)", + "line-dasharray": [1, 0], + "line-width": { + "base": 1.2, + "stops": [ + [14, 1.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge_secondary_tertiary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "bridge_trunk_primary_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "bridge_motorway_casing", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.7], + [7, 1.75], + [20, 22] + ] + } + } + }, + { + "id": "bridge_path_pedestrian", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "path", "pedestrian"] + ], + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [1, 0.3], + "line-width": { + "base": 1.2, + "stops": [ + [14, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "bridge_motorway_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["==", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge_service_track", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "service", "track"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "bridge_link", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "link"], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge_street", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "minor"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge_secondary_tertiary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "secondary", "tertiary"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "bridge_trunk_primary", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["in", "class", "primary", "trunk"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "bridge_motorway", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "motorway"], ["!=", "ramp", 1], ["==", "brunnel", "bridge"]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [7, 1], + [20, 18] + ] + } + } + }, + { + "id": "bridge_major_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "rail"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "bridge_major_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "rail"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "bridge_transit_rail", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "transit"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "bridge_transit_rail_hatching", + "type": "line", + "source": "immich-map", + "source-layer": "transportation", + "filter": ["all", ["==", "class", "transit"], ["==", "brunnel", "bridge"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "building", + "type": "fill", + "source": "immich-map", + "source-layer": "building", + "minzoom": 13, + "maxzoom": 14, + "paint": { + "fill-color": "hsl(35, 8%, 85%)", + "fill-outline-color": { + "base": 1, + "stops": [ + [13, "hsla(35, 6%, 79%, 0.32)"], + [14, "hsl(35, 6%, 79%)"] + ] + } + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "immich-map", + "source-layer": "building", + "minzoom": 14, + "paint": { + "fill-extrusion-color": "hsl(35, 8%, 85%)", + "fill-extrusion-height": { + "property": "render_height", + "type": "identity" + }, + "fill-extrusion-base": { + "property": "render_min_height", + "type": "identity" + }, + "fill-extrusion-opacity": 0.8 + } + }, + { + "id": "boundary_state", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "paint": { "line-color": "rgba(185, 185, 185, 0.58)" } + }, + { + "id": "boundary_3", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "minzoom": 8, + "filter": ["all", ["in", "admin_level", 3, 4]], + "layout": { "line-join": "round" }, + "paint": { + "line-color": "#9e9cab", + "line-dasharray": [5, 1], + "line-width": { + "base": 1, + "stops": [ + [4, 0.4], + [5, 1], + [12, 1.8] + ] + } + } + }, + { + "id": "boundary_2_z0-4", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "maxzoom": 5, + "filter": ["all", ["==", "admin_level", 2], ["!has", "claimed_by"]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "hsl(240, 50%, 60%)", + "line-opacity": { + "base": 1, + "stops": [ + [0, 0.4], + [4, 1] + ] + }, + "line-width": { + "base": 1, + "stops": [ + [3, 1], + [5, 1.2], + [12, 3] + ] + } + } + }, + { + "id": "boundary_2_z5-", + "type": "line", + "source": "immich-map", + "source-layer": "boundary", + "minzoom": 5, + "filter": ["all", ["==", "admin_level", 2]], + "layout": { "line-cap": "round", "line-join": "round" }, + "paint": { + "line-color": "hsl(248, 1%, 41%)", + "line-opacity": { + "base": 1, + "stops": [ + [0, 0.4], + [4, 1] + ] + }, + "line-width": { + "base": 1, + "stops": [ + [3, 1], + [5, 1.2], + [12, 3] + ] + } + } + }, + { + "id": "water_name_line", + "type": "symbol", + "source": "immich-map", + "source-layer": "waterway", + "filter": ["all", ["==", "$type", "LineString"]], + "layout": { + "text-field": "{name}", + "text-font": ["Open Sans Regular"], + "text-max-width": 5, + "text-size": 12, + "symbol-placement": "line" + }, + "paint": { + "text-color": "#5d60be", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1 + } + }, + { + "id": "water_name_point", + "type": "symbol", + "source": "immich-map", + "source-layer": "water_name", + "filter": ["==", "$type", "Point"], + "layout": { + "text-field": "{name}", + "text-font": ["Open Sans Regular"], + "text-max-width": 5, + "text-size": 12 + }, + "paint": { + "text-color": "#5d60be", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1 + } + }, + { + "id": "poi_z16", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 16, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 20]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi_z15", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 15, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 7], ["<", "rank", 20]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi_z14", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "minzoom": 14, + "filter": ["all", ["==", "$type", "Point"], [">=", "rank", 1], ["<", "rank", 7]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi_transit", + "type": "symbol", + "source": "immich-map", + "source-layer": "poi", + "filter": ["all", ["in", "class", "bus", "rail", "airport"]], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "left", + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-max-width": 9, + "text-offset": [0.9, 0], + "text-size": 12 + }, + "paint": { + "text-color": "#4898ff", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "road_label", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation_name", + "filter": ["all"], + "layout": { + "symbol-placement": "line", + "text-anchor": "center", + "text-field": "{name}", + "text-font": ["Open Sans Regular"], + "text-offset": [0, 0.15], + "text-size": { + "base": 1, + "stops": [ + [13, 12], + [14, 13] + ] + } + }, + "paint": { + "text-color": "#765", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "road_shield", + "type": "symbol", + "source": "immich-map", + "source-layer": "transportation_name", + "minzoom": 7, + "filter": ["all", ["<=", "ref_length", 6]], + "layout": { + "icon-image": "default_{ref_length}", + "icon-rotation-alignment": "viewport", + "symbol-placement": { + "base": 1, + "stops": [ + [10, "point"], + [11, "line"] + ] + }, + "symbol-spacing": 500, + "text-field": "{ref}", + "text-font": ["Open Sans Regular"], + "text-offset": [0, 0.1], + "text-rotation-alignment": "viewport", + "text-size": 10, + "icon-size": 0.8 + } + }, + { + "id": "place_other", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["in", "class", "hamlet", "island", "islet", "neighbourhood", "suburb", "quarter"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": { + "base": 1.2, + "stops": [ + [12, 10], + [15, 14] + ] + }, + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#633", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place_village", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "class", "village"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Regular"], + "text-max-width": 8, + "text-size": { + "base": 1.2, + "stops": [ + [10, 12], + [15, 22] + ] + } + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place_town", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "class", "town"]], + "layout": { + "icon-image": { + "base": 1, + "stops": [ + [0, "dot_9"], + [8, ""] + ] + }, + "text-anchor": "bottom", + "text-field": "{name_en}", + "text-font": ["Open Sans Regular"], + "text-max-width": 8, + "text-offset": [0, 0], + "text-size": { + "base": 1.2, + "stops": [ + [7, 12], + [11, 16] + ] + } + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place_city", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "minzoom": 5, + "filter": ["all", ["==", "class", "city"]], + "layout": { + "icon-image": { + "base": 1, + "stops": [ + [0, "dot_9"], + [8, ""] + ] + }, + "text-anchor": "bottom", + "text-field": "{name_en}", + "text-font": ["Open Sans Semibold"], + "text-max-width": 8, + "text-offset": [0, 0], + "text-size": { + "base": 1.2, + "stops": [ + [7, 14], + [11, 24] + ] + }, + "icon-allow-overlap": true, + "icon-optional": false + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "state", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "maxzoom": 6, + "minzoom": 3.5, + "filter": ["all", ["==", "class", "state"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-size": { + "stops": [ + [4, 11], + [6, 15] + ] + }, + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#633", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1 + } + }, + { + "id": "country_3", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", [">=", "rank", 3], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [3, 11], + [7, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1 + } + }, + { + "id": "country_2", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "rank", 2], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [2, 11], + [5, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1 + } + }, + { + "id": "country_1", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "filter": ["all", ["==", "rank", 1], ["==", "class", "country"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [1, 11], + [4, 17] + ] + }, + "text-transform": "none" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1 + } + }, + { + "id": "continent", + "type": "symbol", + "source": "immich-map", + "source-layer": "place", + "maxzoom": 1, + "filter": ["all", ["==", "class", "continent"]], + "layout": { + "text-field": "{name_en}", + "text-font": ["Open Sans Italic"], + "text-size": 13, + "text-transform": "uppercase", + "text-justify": "center" + }, + "paint": { + "text-color": "#633", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1 + } + } + ], + "id": "immich-map-light" +} diff --git a/server/bin/build-imagemagick.sh b/server/bin/build-imagemagick.sh deleted file mode 100755 index e4a57a211..000000000 --- a/server/bin/build-imagemagick.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e - -LOCK=$(jq -c '.packages[] | select(.name == "imagemagick")' build-lock.json) -IMAGEMAGICK_VERSION=${IMAGEMAGICK_VERSION:=$(echo $LOCK | jq -r '.version')} -IMAGEMAGICK_SHA256=${IMAGEMAGICK_SHA256:=$(echo $LOCK | jq -r '.sha256')} - -echo "$IMAGEMAGICK_SHA256 $IMAGEMAGICK_VERSION.tar.gz" > imagemagick.sha256 -mkdir -p ImageMagick -wget -nv https://github.com/ImageMagick/ImageMagick/archive/${IMAGEMAGICK_VERSION}.tar.gz -sha256sum -c imagemagick.sha256 -tar -xvf ${IMAGEMAGICK_VERSION}.tar.gz -C ImageMagick --strip-components=1 -rm ${IMAGEMAGICK_VERSION}.tar.gz -rm imagemagick.sha256 -patch -u ImageMagick/coders/dng.c -i use-camera-wb.patch -cd ImageMagick -./configure --with-modules -make -j$(nproc) -make install -cd .. && rm -rf ImageMagick -ldconfig /usr/local/lib diff --git a/server/bin/build-libraw.sh b/server/bin/build-libraw.sh deleted file mode 100755 index 50bc049ea..000000000 --- a/server/bin/build-libraw.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e - -LOCK=$(jq -c '.packages[] | select(.name == "libraw")' build-lock.json) -LIBRAW_VERSION=${LIBRAW_VERSION:=$(echo $LOCK | jq -r '.version')} -LIBRAW_SHA256=${LIBRAW_SHA256:=$(echo $LOCK | jq -r '.sha256')} - -echo "$LIBRAW_SHA256 $LIBRAW_VERSION.tar.gz" > libraw.sha256 -mkdir -p libraw -wget -nv https://github.com/libraw/libraw/archive/${LIBRAW_VERSION}.tar.gz -sha256sum -c libraw.sha256 -tar -xvf ${LIBRAW_VERSION}.tar.gz -C libraw --strip-components=1 -rm ${LIBRAW_VERSION}.tar.gz -rm libraw.sha256 -cd libraw -autoreconf --install -./configure -make -j$(nproc) -make install -cd .. && rm -rf libraw -ldconfig /usr/local/lib diff --git a/server/bin/build-libvips.sh b/server/bin/build-libvips.sh deleted file mode 100755 index 2c5bc55ba..000000000 --- a/server/bin/build-libvips.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e - -LOCK=$(jq -c '.packages[] | select(.name == "libvips")' build-lock.json) -LIBVIPS_VERSION=${LIBVIPS_VERSION:=$(echo $LOCK | jq -r '.version')} -LIBVIPS_SHA256=${LIBVIPS_SHA256:=$(echo $LOCK | jq -r '.sha256')} - -echo "$LIBVIPS_SHA256 vips-$LIBVIPS_VERSION.tar.xz" > libvips.sha256 -mkdir -p libvips -wget -nv https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.xz -sha256sum -c libvips.sha256 -tar -xvf vips-${LIBVIPS_VERSION}.tar.xz -C libvips --strip-components=1 -rm vips-${LIBVIPS_VERSION}.tar.xz -rm libvips.sha256 -cd libvips -meson setup build --buildtype=release --libdir=lib -Dintrospection=false -Dtiff=disabled -cd build -# ninja test # tests set concurrency too high for arm/v7 -ninja install -cd .. && rm -rf libvips -ldconfig /usr/local/lib diff --git a/server/bin/install-ffmpeg.sh b/server/bin/install-ffmpeg.sh deleted file mode 100755 index 46b9e51ef..000000000 --- a/server/bin/install-ffmpeg.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e - -LOCK=$(jq -c '.packages[] | select(.name == "ffmpeg")' build-lock.json) -export TARGETARCH=${TARGETARCH:=$(dpkg --print-architecture)} -FFMPEG_VERSION=${FFMPEG_VERSION:=$(echo $LOCK | jq -r '.version')} -FFMPEG_SHA256=${FFMPEG_SHA256:=$(echo $LOCK | jq -r '.sha256[$ENV.TARGETARCH]')} - -echo "$FFMPEG_SHA256 jellyfin-ffmpeg6_${FFMPEG_VERSION}-bookworm_${TARGETARCH}.deb" > ffmpeg.sha256 - -wget -nv https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_VERSION}/jellyfin-ffmpeg6_${FFMPEG_VERSION}-bookworm_${TARGETARCH}.deb -sha256sum -c ffmpeg.sha256 -apt-get -yqq -f install ./jellyfin-ffmpeg6_${FFMPEG_VERSION}-bookworm_${TARGETARCH}.deb -rm jellyfin-ffmpeg6_${FFMPEG_VERSION}-bookworm_${TARGETARCH}.deb -rm ffmpeg.sha256 -ldconfig /usr/lib/jellyfin-ffmpeg/lib - -ln -s /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin -ln -s /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin diff --git a/server/bin/use-camera-wb.patch b/server/bin/use-camera-wb.patch deleted file mode 100755 index 507afeb3e..000000000 --- a/server/bin/use-camera-wb.patch +++ /dev/null @@ -1,9 +0,0 @@ -@@ -339,6 +339,8 @@ - option=GetImageOption(image_info,"dng:use_camera_wb"); - if (option != (const char *) NULL) - raw_info->params.use_camera_wb=IsStringTrue(option); -+ else -+ raw_info->params.use_camera_wb=MagickTrue; - option=GetImageOption(image_info,"dng:use-auto-wb"); - if (option == (const char *) NULL) - option=GetImageOption(image_info,"dng:use_auto_wb"); diff --git a/server/build-lock.json b/server/build-lock.json deleted file mode 100644 index 691fe5825..000000000 --- a/server/build-lock.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "packages": [ - { - "name": "imagemagick", - "version": "7.1.1-13", - "sha256": "8e3ce1aaad19da9f2ca444072bcc631d193a219e3ee11c13ad6d3c895044142c" - }, - { - "name": "libraw", - "version": "0.21.1", - "sha256": "b63d7ffa43463f74afcc02f9083048c231349b41cc9255dec0840cf8a67b52e0" - }, - { - "name": "libvips", - "version": "8.14.5", - "sha256": "90374e9f6fbd5657b5faf306cacda20658d6144d385316b59b865bc1a487b68d" - }, - { - "name": "ffmpeg", - "version": "6.0-4", - "sha256": { - "amd64": "18d98b292b891cde86c2a08e5e989c3430e51a136cdc232bc4162fef3b4f0f44", - "arm64": "67eb1e5a38ac695dd253d9ac290ad0e9fb709e8260449a7445e8460b7db3c516", - "armhf": "a29605ab0eced3511c8a6623504fab5b8bb174a486d87f94bf5522ed9a5970e6" - } - } - ] -} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 75a57043f..193586cbd 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -31,6 +31,14 @@ "$ref": "#/components/schemas/ReactionType" } }, + { + "name": "level", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/ReactionLevel" + } + }, { "name": "userId", "required": false, @@ -914,6 +922,22 @@ "description": "Get all AssetEntity belong to the user", "operationId": "getAllAssets", "parameters": [ + { + "name": "skip", + "required": false, + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "take", + "required": false, + "in": "query", + "schema": { + "type": "integer" + } + }, { "name": "userId", "required": false, @@ -940,15 +964,16 @@ } }, { - "name": "skip", + "name": "updatedAfter", "required": false, "in": "query", "schema": { - "type": "number" + "format": "date-time", + "type": "string" } }, { - "name": "updatedAfter", + "name": "updatedBefore", "required": false, "in": "query", "schema": { @@ -2613,14 +2638,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminSignupResponseDto" + "$ref": "#/components/schemas/UserResponseDto" } } }, "description": "" - }, - "400": { - "description": "The server already has an admin" } }, "tags": [ @@ -4867,6 +4889,47 @@ ] } }, + "/system-config/map/style.json": { + "get": { + "operationId": "getMapStyle", + "parameters": [ + { + "name": "theme", + "required": true, + "in": "query", + "schema": { + "$ref": "#/components/schemas/MapTheme" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "System Config" + ] + } + }, "/system-config/storage-template-options": { "get": { "operationId": "getStorageTemplateOptions", @@ -5621,7 +5684,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.84.0", + "version": "1.85.0", "contact": {} }, "tags": [], @@ -5795,34 +5858,6 @@ ], "type": "object" }, - "AdminSignupResponseDto": { - "properties": { - "createdAt": { - "format": "date-time", - "type": "string" - }, - "email": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "id": { - "type": "string" - }, - "lastName": { - "type": "string" - } - }, - "required": [ - "id", - "email", - "firstName", - "lastName", - "createdAt" - ], - "type": "object" - }, "AlbumCountResponseDto": { "properties": { "notShared": { @@ -5877,6 +5912,9 @@ "id": { "type": "string" }, + "isActivityEnabled": { + "type": "boolean" + }, "lastModifiedAssetTimestamp": { "format": "date-time", "type": "string" @@ -5918,7 +5956,8 @@ "sharedUsers", "hasSharedLink", "assets", - "owner" + "owner", + "isActivityEnabled" ], "type": "object" }, @@ -6655,8 +6694,7 @@ "deviceAssetId", "deviceId", "fileCreatedAt", - "fileModifiedAt", - "isFavorite" + "fileModifiedAt" ], "type": "object" }, @@ -7143,8 +7181,7 @@ "deviceAssetId", "deviceId", "fileCreatedAt", - "fileModifiedAt", - "isFavorite" + "fileModifiedAt" ], "type": "object" }, @@ -7358,35 +7395,27 @@ "LoginResponseDto": { "properties": { "accessToken": { - "readOnly": true, "type": "string" }, "firstName": { - "readOnly": true, "type": "string" }, "isAdmin": { - "readOnly": true, "type": "boolean" }, "lastName": { - "readOnly": true, "type": "string" }, "profileImagePath": { - "readOnly": true, "type": "string" }, "shouldChangePassword": { - "readOnly": true, "type": "boolean" }, "userEmail": { - "readOnly": true, "type": "string" }, "userId": { - "readOnly": true, "type": "string" } }, @@ -7438,6 +7467,13 @@ ], "type": "object" }, + "MapTheme": { + "enum": [ + "light", + "dark" + ], + "type": "string" + }, "MemoryLaneResponseDto": { "properties": { "assets": { @@ -7700,6 +7736,13 @@ ], "type": "object" }, + "ReactionLevel": { + "enum": [ + "album", + "asset" + ], + "type": "string" + }, "ReactionType": { "enum": [ "comment", @@ -7907,9 +7950,6 @@ "loginPageMessage": { "type": "string" }, - "mapTileUrl": { - "type": "string" - }, "oauthButtonText": { "type": "string" }, @@ -7921,7 +7961,6 @@ "trashDays", "oauthButtonText", "loginPageMessage", - "mapTileUrl", "isInitialized" ], "type": "object" @@ -8572,16 +8611,20 @@ }, "SystemConfigMapDto": { "properties": { + "darkStyle": { + "type": "string" + }, "enabled": { "type": "boolean" }, - "tileUrl": { + "lightStyle": { "type": "string" } }, "required": [ "enabled", - "tileUrl" + "lightStyle", + "darkStyle" ], "type": "object" }, @@ -8895,6 +8938,9 @@ }, "description": { "type": "string" + }, + "isActivityEnabled": { + "type": "boolean" } }, "type": "object" diff --git a/server/package-lock.json b/server/package-lock.json index 6eb1fb7f3..70c213b68 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich", - "version": "1.84.0", + "version": "1.85.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "immich", - "version": "1.84.0", + "version": "1.85.0", "license": "UNLICENSED", "dependencies": { "@babel/runtime": "^7.22.11", diff --git a/server/package.json b/server/package.json index f073c738a..62e9c960a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.84.0", + "version": "1.85.0", "description": "", "author": "", "private": true, diff --git a/server/src/domain/access/access.core.ts b/server/src/domain/access/access.core.ts index 414252778..88abd79b1 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/domain/access/access.core.ts @@ -138,10 +138,7 @@ export class AccessCore { switch (permission) { // uses album id case Permission.ACTIVITY_CREATE: - return ( - (await this.repository.album.hasOwnerAccess(authUser.id, id)) || - (await this.repository.album.hasSharedAlbumAccess(authUser.id, id)) - ); + return await this.repository.activity.hasCreateAccess(authUser.id, id); // uses activity id case Permission.ACTIVITY_DELETE: diff --git a/server/src/domain/activity/activity.dto.ts b/server/src/domain/activity/activity.dto.ts index e1a163b81..a5a5bd3df 100644 --- a/server/src/domain/activity/activity.dto.ts +++ b/server/src/domain/activity/activity.dto.ts @@ -9,6 +9,11 @@ export enum ReactionType { LIKE = 'like', } +export enum ReactionLevel { + ALBUM = 'album', + ASSET = 'asset', +} + export type MaybeDuplicate = { duplicate: boolean; value: T }; export class ActivityResponseDto { @@ -39,6 +44,11 @@ export class ActivitySearchDto extends ActivityDto { @ApiProperty({ enumName: 'ReactionType', enum: ReactionType }) type?: ReactionType; + @IsEnum(ReactionLevel) + @Optional() + @ApiProperty({ enumName: 'ReactionLevel', enum: ReactionLevel }) + level?: ReactionLevel; + @ValidateUUID({ optional: true }) userId?: string; } diff --git a/server/src/domain/activity/activity.service.ts b/server/src/domain/activity/activity.service.ts index bacdab317..8b601558d 100644 --- a/server/src/domain/activity/activity.service.ts +++ b/server/src/domain/activity/activity.service.ts @@ -10,6 +10,7 @@ import { ActivitySearchDto, ActivityStatisticsResponseDto, MaybeDuplicate, + ReactionLevel, ReactionType, mapActivity, } from './activity.dto'; @@ -30,7 +31,7 @@ export class ActivityService { const activities = await this.repository.search({ userId: dto.userId, albumId: dto.albumId, - assetId: dto.assetId, + assetId: dto.level === ReactionLevel.ALBUM ? null : dto.assetId, isLiked: dto.type && dto.type === ReactionType.LIKE, }); @@ -54,10 +55,12 @@ export class ActivityService { let activity: ActivityEntity | null = null; let duplicate = false; - if (dto.type === 'like') { + if (dto.type === ReactionType.LIKE) { delete dto.comment; [activity] = await this.repository.search({ ...common, + // `null` will search for an album like + assetId: dto.assetId ?? null, isLiked: true, }); duplicate = !!activity; diff --git a/server/src/domain/activity/activity.spec.ts b/server/src/domain/activity/activity.spec.ts index 968f7421a..496d8978b 100644 --- a/server/src/domain/activity/activity.spec.ts +++ b/server/src/domain/activity/activity.spec.ts @@ -94,7 +94,7 @@ describe(ActivityService.name, () => { }); it('should create a comment', async () => { - accessMock.album.hasOwnerAccess.mockResolvedValue(true); + accessMock.activity.hasCreateAccess.mockResolvedValue(true); activityMock.create.mockResolvedValue(activityStub.oneComment); await sut.create(authStub.admin, { @@ -113,8 +113,23 @@ describe(ActivityService.name, () => { }); }); - it('should create a like', async () => { + it('should fail because activity is disabled for the album', async () => { accessMock.album.hasOwnerAccess.mockResolvedValue(true); + accessMock.activity.hasCreateAccess.mockResolvedValue(false); + activityMock.create.mockResolvedValue(activityStub.oneComment); + + await expect( + sut.create(authStub.admin, { + albumId: 'album-id', + assetId: 'asset-id', + type: ReactionType.COMMENT, + comment: 'comment', + }), + ).rejects.toBeInstanceOf(BadRequestException); + }); + + it('should create a like', async () => { + accessMock.activity.hasCreateAccess.mockResolvedValue(true); activityMock.create.mockResolvedValue(activityStub.liked); activityMock.search.mockResolvedValue([]); @@ -134,6 +149,7 @@ describe(ActivityService.name, () => { it('should skip if like exists', async () => { accessMock.album.hasOwnerAccess.mockResolvedValue(true); + accessMock.activity.hasCreateAccess.mockResolvedValue(true); activityMock.search.mockResolvedValue([activityStub.liked]); await sut.create(authStub.admin, { diff --git a/server/src/domain/album/album-response.dto.ts b/server/src/domain/album/album-response.dto.ts index b426bc37d..671922408 100644 --- a/server/src/domain/album/album-response.dto.ts +++ b/server/src/domain/album/album-response.dto.ts @@ -21,6 +21,7 @@ export class AlbumResponseDto { lastModifiedAssetTimestamp?: Date; startDate?: Date; endDate?: Date; + isActivityEnabled!: boolean; } export const mapAlbum = (entity: AlbumEntity, withAssets: boolean): AlbumResponseDto => { @@ -61,6 +62,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean): AlbumRespons endDate, assets: (withAssets ? assets : []).map((asset) => mapAsset(asset)), assetCount: entity.assets?.length || 0, + isActivityEnabled: entity.isActivityEnabled, }; }; diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts index b8e789943..37d44c33a 100644 --- a/server/src/domain/album/album.service.ts +++ b/server/src/domain/album/album.service.ts @@ -125,12 +125,12 @@ export class AlbumService { throw new BadRequestException('Invalid album thumbnail'); } } - const updatedAlbum = await this.albumRepository.update({ id: album.id, albumName: dto.albumName, description: dto.description, albumThumbnailAssetId: dto.albumThumbnailAssetId, + isActivityEnabled: dto.isActivityEnabled, }); await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } }); diff --git a/server/src/domain/album/dto/album-update.dto.ts b/server/src/domain/album/dto/album-update.dto.ts index f574f2c23..3b1858ba1 100644 --- a/server/src/domain/album/dto/album-update.dto.ts +++ b/server/src/domain/album/dto/album-update.dto.ts @@ -1,4 +1,4 @@ -import { IsString } from 'class-validator'; +import { IsBoolean, IsString } from 'class-validator'; import { Optional, ValidateUUID } from '../../domain.util'; export class UpdateAlbumDto { @@ -12,4 +12,8 @@ export class UpdateAlbumDto { @ValidateUUID({ optional: true }) albumThumbnailAssetId?: string; + + @Optional() + @IsBoolean() + isActivityEnabled?: boolean; } diff --git a/server/src/domain/auth/auth.dto.ts b/server/src/domain/auth/auth.dto.ts new file mode 100644 index 000000000..c0703911d --- /dev/null +++ b/server/src/domain/auth/auth.dto.ts @@ -0,0 +1,132 @@ +import { UserEntity, UserTokenEntity } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; + +export class AuthUserDto { + id!: string; + email!: string; + isAdmin!: boolean; + isPublicUser?: boolean; + sharedLinkId?: string; + isAllowUpload?: boolean; + isAllowDownload?: boolean; + isShowMetadata?: boolean; + accessTokenId?: string; + externalPath?: string | null; +} + +export class LoginCredentialDto { + @IsEmail({ require_tld: false }) + @Transform(({ value }) => value?.toLowerCase()) + @IsNotEmpty() + @ApiProperty({ example: 'testuser@email.com' }) + email!: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'password' }) + password!: string; +} + +export class LoginResponseDto { + accessToken!: string; + userId!: string; + userEmail!: string; + firstName!: string; + lastName!: string; + profileImagePath!: string; + isAdmin!: boolean; + shouldChangePassword!: boolean; +} + +export function mapLoginResponse(entity: UserEntity, accessToken: string): LoginResponseDto { + return { + accessToken: accessToken, + userId: entity.id, + userEmail: entity.email, + firstName: entity.firstName, + lastName: entity.lastName, + isAdmin: entity.isAdmin, + profileImagePath: entity.profileImagePath, + shouldChangePassword: entity.shouldChangePassword, + }; +} + +export class LogoutResponseDto { + successful!: boolean; + redirectUri!: string; +} + +export class SignUpDto extends LoginCredentialDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'Admin' }) + firstName!: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'Doe' }) + lastName!: string; +} + +export class ChangePasswordDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'password' }) + password!: string; + + @IsString() + @IsNotEmpty() + @MinLength(8) + @ApiProperty({ example: 'password' }) + newPassword!: string; +} + +export class ValidateAccessTokenResponseDto { + authStatus!: boolean; +} + +export class AuthDeviceResponseDto { + id!: string; + createdAt!: string; + updatedAt!: string; + current!: boolean; + deviceType!: string; + deviceOS!: string; +} + +export const mapUserToken = (entity: UserTokenEntity, currentId?: string): AuthDeviceResponseDto => ({ + id: entity.id, + createdAt: entity.createdAt.toISOString(), + updatedAt: entity.updatedAt.toISOString(), + current: currentId === entity.id, + deviceOS: entity.deviceOS, + deviceType: entity.deviceType, +}); + +export class OAuthCallbackDto { + @IsNotEmpty() + @IsString() + @ApiProperty() + url!: string; +} + +export class OAuthConfigDto { + @IsNotEmpty() + @IsString() + redirectUri!: string; +} + +/** @deprecated use oauth authorize */ +export class OAuthConfigResponseDto { + enabled!: boolean; + passwordLoginEnabled!: boolean; + url?: string; + buttonText?: string; + autoLaunch?: boolean; +} + +export class OAuthAuthorizeResponseDto { + url!: string; +} diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index 2ef598b05..3fb5d4d32 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -31,8 +31,8 @@ import { IUserTokenRepository, } from '../repositories'; import { AuthType } from './auth.constant'; +import { AuthUserDto, SignUpDto } from './auth.dto'; import { AuthService } from './auth.service'; -import { AuthUserDto, SignUpDto } from './dto'; // const token = Buffer.from('my-api-key', 'utf8').toString('base64'); diff --git a/server/src/domain/auth/auth.service.ts b/server/src/domain/auth/auth.service.ts index 289f50bfb..00aa41bfd 100644 --- a/server/src/domain/auth/auth.service.ts +++ b/server/src/domain/auth/auth.service.ts @@ -32,18 +32,21 @@ import { LOGIN_URL, MOBILE_REDIRECT, } from './auth.constant'; -import { AuthUserDto, ChangePasswordDto, LoginCredentialDto, OAuthCallbackDto, OAuthConfigDto, SignUpDto } from './dto'; import { - AdminSignupResponseDto, AuthDeviceResponseDto, + AuthUserDto, + ChangePasswordDto, + LoginCredentialDto, LoginResponseDto, LogoutResponseDto, OAuthAuthorizeResponseDto, + OAuthCallbackDto, + OAuthConfigDto, OAuthConfigResponseDto, - mapAdminSignupResponse, + SignUpDto, mapLoginResponse, mapUserToken, -} from './response-dto'; +} from './auth.dto'; export interface LoginDetails { isSecure: boolean; @@ -133,7 +136,7 @@ export class AuthService { return this.userCore.updateUser(authUser, authUser.id, { password: newPassword }); } - async adminSignUp(dto: SignUpDto): Promise { + async adminSignUp(dto: SignUpDto): Promise { const adminUser = await this.userRepository.getAdmin(); if (adminUser) { @@ -149,7 +152,7 @@ export class AuthService { storageLabel: 'admin', }); - return mapAdminSignupResponse(admin); + return mapUser(admin); } async validate(headers: IncomingHttpHeaders, params: Record): Promise { diff --git a/server/src/domain/auth/dto/auth-user.dto.ts b/server/src/domain/auth/dto/auth-user.dto.ts deleted file mode 100644 index a689096d8..000000000 --- a/server/src/domain/auth/dto/auth-user.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -export class AuthUserDto { - id!: string; - email!: string; - isAdmin!: boolean; - isPublicUser?: boolean; - sharedLinkId?: string; - isAllowUpload?: boolean; - isAllowDownload?: boolean; - isShowMetadata?: boolean; - accessTokenId?: string; - externalPath?: string | null; -} diff --git a/server/src/domain/auth/dto/change-password.dto.ts b/server/src/domain/auth/dto/change-password.dto.ts deleted file mode 100644 index 9c5ce479e..000000000 --- a/server/src/domain/auth/dto/change-password.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, MinLength } from 'class-validator'; - -export class ChangePasswordDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; - - @IsString() - @IsNotEmpty() - @MinLength(8) - @ApiProperty({ example: 'password' }) - newPassword!: string; -} diff --git a/server/src/domain/auth/dto/index.ts b/server/src/domain/auth/dto/index.ts deleted file mode 100644 index 59a65770a..000000000 --- a/server/src/domain/auth/dto/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './auth-user.dto'; -export * from './change-password.dto'; -export * from './login-credential.dto'; -export * from './oauth-auth-code.dto'; -export * from './oauth-config.dto'; -export * from './sign-up.dto'; diff --git a/server/src/domain/auth/dto/login-credential.dto.spec.ts b/server/src/domain/auth/dto/login-credential.dto.spec.ts deleted file mode 100644 index 7682db4c4..000000000 --- a/server/src/domain/auth/dto/login-credential.dto.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validateSync } from 'class-validator'; -import { LoginCredentialDto } from './login-credential.dto'; - -describe('LoginCredentialDto', () => { - it('should allow emails without a tld', () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(LoginCredentialDto, { email: someEmail, password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); - - it('should fail without an email', () => { - const dto = plainToInstance(LoginCredentialDto, { password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should fail with an invalid email', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'invalid.com', password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should make the email all lowercase', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'TeSt@ImMiCh.com', password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual('test@immich.com'); - }); - - it('should fail without a password', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'test@immich.com', password: '' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('password'); - }); -}); diff --git a/server/src/domain/auth/dto/login-credential.dto.ts b/server/src/domain/auth/dto/login-credential.dto.ts deleted file mode 100644 index 12516a108..000000000 --- a/server/src/domain/auth/dto/login-credential.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; - -export class LoginCredentialDto { - @IsEmail({ require_tld: false }) - @Transform(({ value }) => value?.toLowerCase()) - @IsNotEmpty() - @ApiProperty({ example: 'testuser@email.com' }) - email!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; -} diff --git a/server/src/domain/auth/dto/oauth-auth-code.dto.ts b/server/src/domain/auth/dto/oauth-auth-code.dto.ts deleted file mode 100644 index 924db0052..000000000 --- a/server/src/domain/auth/dto/oauth-auth-code.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class OAuthCallbackDto { - @IsNotEmpty() - @IsString() - @ApiProperty() - url!: string; -} diff --git a/server/src/domain/auth/dto/oauth-config.dto.ts b/server/src/domain/auth/dto/oauth-config.dto.ts deleted file mode 100644 index a14fc19dc..000000000 --- a/server/src/domain/auth/dto/oauth-config.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class OAuthConfigDto { - @IsNotEmpty() - @IsString() - redirectUri!: string; -} diff --git a/server/src/domain/auth/dto/sign-up.dto.spec.ts b/server/src/domain/auth/dto/sign-up.dto.spec.ts deleted file mode 100644 index de708fe2e..000000000 --- a/server/src/domain/auth/dto/sign-up.dto.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validateSync } from 'class-validator'; -import { SignUpDto } from './sign-up.dto'; - -describe('SignUpDto', () => { - it('should require all fields', () => { - const dto = plainToInstance(SignUpDto, { - email: '', - password: '', - firstName: '', - lastName: '', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(4); - expect(errors[0].property).toEqual('email'); - expect(errors[1].property).toEqual('password'); - expect(errors[2].property).toEqual('firstName'); - expect(errors[3].property).toEqual('lastName'); - }); - - it('should require a valid email', () => { - const dto = plainToInstance(SignUpDto, { - email: 'immich.com', - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should allow emails without a tld', () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(SignUpDto, { - email: someEmail, - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); - - it('should make the email all lowercase', () => { - const dto = plainToInstance(SignUpDto, { - email: 'TeSt@ImMiCh.com', - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual('test@immich.com'); - }); -}); diff --git a/server/src/domain/auth/dto/sign-up.dto.ts b/server/src/domain/auth/dto/sign-up.dto.ts deleted file mode 100644 index 66741eb7e..000000000 --- a/server/src/domain/auth/dto/sign-up.dto.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; - -export class SignUpDto { - @IsEmail({ require_tld: false }) - @Transform(({ value }) => value?.toLowerCase()) - @IsNotEmpty() - @ApiProperty({ example: 'testuser@email.com' }) - email!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'Admin' }) - firstName!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'Doe' }) - lastName!: string; -} diff --git a/server/src/domain/auth/index.ts b/server/src/domain/auth/index.ts index d3aa704ba..52e0463bc 100644 --- a/server/src/domain/auth/index.ts +++ b/server/src/domain/auth/index.ts @@ -1,4 +1,3 @@ export * from './auth.constant'; +export * from './auth.dto'; export * from './auth.service'; -export * from './dto'; -export * from './response-dto'; diff --git a/server/src/domain/auth/response-dto/admin-signup-response.dto.ts b/server/src/domain/auth/response-dto/admin-signup-response.dto.ts deleted file mode 100644 index 5c2e4413c..000000000 --- a/server/src/domain/auth/response-dto/admin-signup-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UserEntity } from '@app/infra/entities'; - -export class AdminSignupResponseDto { - id!: string; - email!: string; - firstName!: string; - lastName!: string; - createdAt!: Date; -} - -export function mapAdminSignupResponse(entity: UserEntity): AdminSignupResponseDto { - return { - id: entity.id, - email: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, - createdAt: entity.createdAt, - }; -} diff --git a/server/src/domain/auth/response-dto/auth-device-response.dto.ts b/server/src/domain/auth/response-dto/auth-device-response.dto.ts deleted file mode 100644 index 986f743c0..000000000 --- a/server/src/domain/auth/response-dto/auth-device-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UserTokenEntity } from '@app/infra/entities'; - -export class AuthDeviceResponseDto { - id!: string; - createdAt!: string; - updatedAt!: string; - current!: boolean; - deviceType!: string; - deviceOS!: string; -} - -export const mapUserToken = (entity: UserTokenEntity, currentId?: string): AuthDeviceResponseDto => ({ - id: entity.id, - createdAt: entity.createdAt.toISOString(), - updatedAt: entity.updatedAt.toISOString(), - current: currentId === entity.id, - deviceOS: entity.deviceOS, - deviceType: entity.deviceType, -}); diff --git a/server/src/domain/auth/response-dto/index.ts b/server/src/domain/auth/response-dto/index.ts deleted file mode 100644 index 491c957fd..000000000 --- a/server/src/domain/auth/response-dto/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './admin-signup-response.dto'; -export * from './auth-device-response.dto'; -export * from './login-response.dto'; -export * from './logout-response.dto'; -export * from './oauth-config-response.dto'; -export * from './validate-asset-token-response.dto'; diff --git a/server/src/domain/auth/response-dto/login-response.dto.ts b/server/src/domain/auth/response-dto/login-response.dto.ts deleted file mode 100644 index 1be804996..000000000 --- a/server/src/domain/auth/response-dto/login-response.dto.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { UserEntity } from '@app/infra/entities'; -import { ApiResponseProperty } from '@nestjs/swagger'; - -export class LoginResponseDto { - @ApiResponseProperty() - accessToken!: string; - - @ApiResponseProperty() - userId!: string; - - @ApiResponseProperty() - userEmail!: string; - - @ApiResponseProperty() - firstName!: string; - - @ApiResponseProperty() - lastName!: string; - - @ApiResponseProperty() - profileImagePath!: string; - - @ApiResponseProperty() - isAdmin!: boolean; - - @ApiResponseProperty() - shouldChangePassword!: boolean; -} - -export function mapLoginResponse(entity: UserEntity, accessToken: string): LoginResponseDto { - return { - accessToken: accessToken, - userId: entity.id, - userEmail: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, - isAdmin: entity.isAdmin, - profileImagePath: entity.profileImagePath, - shouldChangePassword: entity.shouldChangePassword, - }; -} diff --git a/server/src/domain/auth/response-dto/logout-response.dto.ts b/server/src/domain/auth/response-dto/logout-response.dto.ts deleted file mode 100644 index 16816264e..000000000 --- a/server/src/domain/auth/response-dto/logout-response.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class LogoutResponseDto { - successful!: boolean; - redirectUri!: string; -} diff --git a/server/src/domain/auth/response-dto/oauth-config-response.dto.ts b/server/src/domain/auth/response-dto/oauth-config-response.dto.ts deleted file mode 100644 index dadd66f59..000000000 --- a/server/src/domain/auth/response-dto/oauth-config-response.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class OAuthConfigResponseDto { - enabled!: boolean; - passwordLoginEnabled!: boolean; - url?: string; - buttonText?: string; - autoLaunch?: boolean; -} - -export class OAuthAuthorizeResponseDto { - url!: string; -} diff --git a/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts b/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts deleted file mode 100644 index 4fdb2971d..000000000 --- a/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class ValidateAccessTokenResponseDto { - authStatus!: boolean; -} diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 9d966460e..3a4ac6b6d 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -1,4 +1,4 @@ -import { Colorspace, SystemConfigKey } from '@app/infra/entities'; +import { AssetFaceEntity, Colorspace, SystemConfigKey } from '@app/infra/entities'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { IAccessRepositoryMock, @@ -449,7 +449,26 @@ describe(PersonService.name, () => { expect(machineLearningMock.detectFaces).not.toHaveBeenCalled(); }); + it('should skip it the asset has already been processed', async () => { + assetMock.getByIds.mockResolvedValue([ + { + ...assetStub.noResizePath, + faces: [ + { + id: 'asset-face-1', + assetId: assetStub.noResizePath.id, + personId: faceStub.face1.personId, + } as AssetFaceEntity, + ], + }, + ]); + await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id }); + expect(machineLearningMock.detectFaces).not.toHaveBeenCalled(); + }); + it('should handle no results', async () => { + const start = Date.now(); + machineLearningMock.detectFaces.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([assetStub.image]); await sut.handleRecognizeFaces({ id: assetStub.image.id }); @@ -468,6 +487,12 @@ describe(PersonService.name, () => { ); expect(personMock.createFace).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled(); + + expect(assetMock.upsertJobStatus).toHaveBeenCalledWith({ + assetId: assetStub.image.id, + facesRecognizedAt: expect.any(Date), + }); + expect(assetMock.upsertJobStatus.mock.calls[0][0].facesRecognizedAt?.getTime()).toBeGreaterThan(start); }); it('should match existing people', async () => { diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index dcf5dbb78..49329a452 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -216,8 +216,14 @@ export class PersonService { return true; } - const [asset] = await this.assetRepository.getByIds([id]); - if (!asset || !asset.resizePath) { + const relations = { + exifInfo: true, + faces: { + person: true, + }, + }; + const [asset] = await this.assetRepository.getByIds([id], relations); + if (!asset || !asset.resizePath || asset.faces?.length > 0) { return false; } @@ -268,6 +274,11 @@ export class PersonService { } } + await this.assetRepository.upsertJobStatus({ + assetId: asset.id, + facesRecognizedAt: new Date(), + }); + return true; } diff --git a/server/src/domain/repositories/access.repository.ts b/server/src/domain/repositories/access.repository.ts index 43b53e605..f9ceb6f52 100644 --- a/server/src/domain/repositories/access.repository.ts +++ b/server/src/domain/repositories/access.repository.ts @@ -2,8 +2,9 @@ export const IAccessRepository = 'IAccessRepository'; export interface IAccessRepository { activity: { - hasOwnerAccess(userId: string, albumId: string): Promise; - hasAlbumOwnerAccess(userId: string, albumId: string): Promise; + hasOwnerAccess(userId: string, activityId: string): Promise; + hasAlbumOwnerAccess(userId: string, activityId: string): Promise; + hasCreateAccess(userId: string, albumId: string): Promise; }; asset: { hasOwnerAccess(userId: string, assetId: string): Promise; diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts index bdbdd78f2..da8f8547e 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/domain/repositories/asset.repository.ts @@ -1,4 +1,5 @@ -import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; +import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities'; +import { FindOptionsRelations } from 'typeorm'; import { Paginated, PaginationOptions } from '../domain.util'; export type AssetStats = Record; @@ -99,7 +100,7 @@ export const IAssetRepository = 'IAssetRepository'; export interface IAssetRepository { create(asset: AssetCreate): Promise; getByDate(ownerId: string, date: Date): Promise; - getByIds(ids: string[]): Promise; + getByIds(ids: string[], relations?: FindOptionsRelations): Promise; getByDayOfYear(ownerId: string, monthDay: MonthDay): Promise; getByChecksum(userId: string, checksum: Buffer): Promise; getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated; @@ -125,4 +126,5 @@ export interface IAssetRepository { getTimeBuckets(options: TimeBucketOptions): Promise; getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise; upsertExif(exif: Partial): Promise; + upsertJobStatus(jobStatus: Partial): Promise; } diff --git a/server/src/domain/repositories/system-config.repository.ts b/server/src/domain/repositories/system-config.repository.ts index f99979207..d154f6eff 100644 --- a/server/src/domain/repositories/system-config.repository.ts +++ b/server/src/domain/repositories/system-config.repository.ts @@ -3,8 +3,9 @@ import { SystemConfigEntity } from '@app/infra/entities'; export const ISystemConfigRepository = 'ISystemConfigRepository'; export interface ISystemConfigRepository { + fetchStyle(url: string): Promise; load(): Promise; - readFile(filename: string): Promise; + readFile(filename: string): Promise; saveAll(items: SystemConfigEntity[]): Promise; deleteKeys(keys: string[]): Promise; } diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index 846458223..907ce26ea 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -85,7 +85,6 @@ export class ServerThemeDto extends SystemConfigThemeDto {} export class ServerConfigDto { oauthButtonText!: string; loginPageMessage!: string; - mapTileUrl!: string; @ApiProperty({ type: 'integer' }) trashDays!: number; isInitialized!: boolean; diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts index f4025a331..204eb1bd1 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -185,7 +185,6 @@ describe(ServerInfoService.name, () => { loginPageMessage: '', oauthButtonText: 'Login with OAuth', trashDays: 30, - mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', }); expect(configMock.load).toHaveBeenCalled(); }); diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index dc2efd416..27005f176 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -85,7 +85,6 @@ export class ServerInfoService { return { loginPageMessage, - mapTileUrl: config.map.tileUrl, trashDays: config.trash.days, oauthButtonText: config.oauth.buttonText, isInitialized, diff --git a/server/src/domain/system-config/dto/system-config-map.dto.ts b/server/src/domain/system-config/dto/system-config-map.dto.ts index 479b1486d..07700d98c 100644 --- a/server/src/domain/system-config/dto/system-config-map.dto.ts +++ b/server/src/domain/system-config/dto/system-config-map.dto.ts @@ -5,5 +5,8 @@ export class SystemConfigMapDto { enabled!: boolean; @IsString() - tileUrl!: string; + lightStyle!: string; + + @IsString() + darkStyle!: string; } diff --git a/server/src/domain/system-config/system-config-map-theme.dto.ts b/server/src/domain/system-config/system-config-map-theme.dto.ts new file mode 100644 index 000000000..9286d8d23 --- /dev/null +++ b/server/src/domain/system-config/system-config-map-theme.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +export enum MapTheme { + LIGHT = 'light', + DARK = 'dark', +} + +export class MapThemeDto { + @IsEnum(MapTheme) + @ApiProperty({ enum: MapTheme, enumName: 'MapTheme' }) + theme!: MapTheme; +} diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 4596370a4..ebcd57740 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -80,7 +80,8 @@ export const defaults = Object.freeze({ }, map: { enabled: true, - tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + lightStyle: '', + darkStyle: '', }, reverseGeocoding: { enabled: true, diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 29ed44e91..06e97e646 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -80,7 +80,8 @@ const updatedConfig = Object.freeze({ }, map: { enabled: true, - tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + lightStyle: '', + darkStyle: '', }, reverseGeocoding: { enabled: true, @@ -185,7 +186,7 @@ describe(SystemConfigService.name, () => { it('should load the config from a file', async () => { process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; const partialConfig = { ffmpeg: { crf: 30 }, oauth: { autoLaunch: true }, trash: { days: 10 } }; - configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify(partialConfig))); + configMock.readFile.mockResolvedValue(JSON.stringify(partialConfig)); await expect(sut.getConfig()).resolves.toEqual(updatedConfig); @@ -194,7 +195,7 @@ describe(SystemConfigService.name, () => { it('should accept an empty configuration file', async () => { process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; - configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify({}))); + configMock.readFile.mockResolvedValue(JSON.stringify({})); await expect(sut.getConfig()).resolves.toEqual(defaults); @@ -204,7 +205,7 @@ describe(SystemConfigService.name, () => { it('should allow underscores in the machine learning url', async () => { process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; const partialConfig = { machineLearning: { url: 'immich_machine_learning' } }; - configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify(partialConfig))); + configMock.readFile.mockResolvedValue(JSON.stringify(partialConfig)); const config = await sut.getConfig(); expect(config.machineLearning.url).toEqual('immich_machine_learning'); @@ -222,7 +223,7 @@ describe(SystemConfigService.name, () => { for (const test of tests) { it(`should ${test.should}`, async () => { process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; - configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify(test.config))); + configMock.readFile.mockResolvedValue(JSON.stringify(test.config)); await expect(sut.getConfig()).rejects.toBeInstanceOf(Error); }); @@ -286,7 +287,7 @@ describe(SystemConfigService.name, () => { it('should throw an error if a config file is in use', async () => { process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; - configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify({}))); + configMock.readFile.mockResolvedValue(JSON.stringify({})); await expect(sut.updateConfig(defaults)).rejects.toBeInstanceOf(BadRequestException); expect(configMock.saveAll).not.toHaveBeenCalled(); }); diff --git a/server/src/domain/system-config/system-config.service.ts b/server/src/domain/system-config/system-config.service.ts index a44f10c09..1fdbca65e 100644 --- a/server/src/domain/system-config/system-config.service.ts +++ b/server/src/domain/system-config/system-config.service.ts @@ -20,7 +20,7 @@ import { SystemConfigCore, SystemConfigValidator } from './system-config.core'; export class SystemConfigService { private core: SystemConfigCore; constructor( - @Inject(ISystemConfigRepository) repository: ISystemConfigRepository, + @Inject(ISystemConfigRepository) private repository: ISystemConfigRepository, @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, ) { @@ -76,4 +76,15 @@ export class SystemConfigService { return options; } + + async getMapStyle(theme: 'light' | 'dark') { + const { map } = await this.getConfig(); + const styleUrl = theme === 'dark' ? map.darkStyle : map.lightStyle; + + if (styleUrl) { + return this.repository.fetchStyle(styleUrl); + } + + return JSON.parse(await this.repository.readFile(`./assets/style-${theme}.json`)); + } } diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index 9dac7e604..13cf6bf17 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -1,8 +1,8 @@ import { AssetCreate } from '@app/domain'; import { AssetEntity } from '@app/infra/entities'; +import OptionalBetween from '@app/infra/utils/optional-between.util'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { MoreThan } from 'typeorm'; import { In } from 'typeorm/find-options/operator/In'; import { Repository } from 'typeorm/repository/Repository'; import { AssetSearchDto } from './dto/asset-search.dto'; @@ -129,7 +129,7 @@ export class AssetRepository implements IAssetRepository { isVisible: true, isFavorite: dto.isFavorite, isArchived: dto.isArchived, - updatedAt: dto.updatedAfter ? MoreThan(dto.updatedAfter) : undefined, + updatedAt: OptionalBetween(dto.updatedAfter, dto.updatedBefore), }, relations: { exifInfo: true, @@ -137,6 +137,7 @@ export class AssetRepository implements IAssetRepository { stack: true, }, skip: dto.skip || 0, + take: dto.take, order: { fileCreatedAt: 'DESC', }, diff --git a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts index 3067edebe..d73856ab9 100644 --- a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts +++ b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts @@ -1,7 +1,7 @@ import { Optional, toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; -import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsUUID } from 'class-validator'; +import { IsBoolean, IsDate, IsInt, IsNotEmpty, IsUUID } from 'class-validator'; export class AssetSearchDto { @Optional() @@ -17,9 +17,17 @@ export class AssetSearchDto { isArchived?: boolean; @Optional() - @IsNumber() + @IsInt() + @Type(() => Number) + @ApiProperty({ type: 'integer' }) skip?: number; + @Optional() + @IsInt() + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + take?: number; + @Optional() @IsUUID('4') @ApiProperty({ format: 'uuid' }) @@ -29,4 +37,9 @@ export class AssetSearchDto { @IsDate() @Type(() => Date) updatedAfter?: Date; + + @Optional() + @IsDate() + @Type(() => Date) + updatedBefore?: Date; } diff --git a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts index de1f7f1bb..0338fe792 100644 --- a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts +++ b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts @@ -22,9 +22,10 @@ export class CreateAssetBase { @Type(() => Date) fileModifiedAt!: Date; + @Optional() @IsBoolean() @Transform(toBoolean) - isFavorite!: boolean; + isFavorite?: boolean; @Optional() @IsBoolean() diff --git a/server/src/immich/app.guard.ts b/server/src/immich/app.guard.ts index bf4538ad0..0a9fe2dc1 100644 --- a/server/src/immich/app.guard.ts +++ b/server/src/immich/app.guard.ts @@ -20,16 +20,9 @@ export enum Metadata { PUBLIC_SECURITY = 'public_security', } -const adminDecorator = SetMetadata(Metadata.ADMIN_ROUTE, true); - -const sharedLinkDecorators = [ - SetMetadata(Metadata.SHARED_ROUTE, true), - ApiQuery({ name: 'key', type: String, required: false }), -]; - export interface AuthenticatedOptions { - admin?: boolean; - isShared?: boolean; + admin?: true; + isShared?: true; } export const Authenticated = (options: AuthenticatedOptions = {}) => { @@ -41,11 +34,11 @@ export const Authenticated = (options: AuthenticatedOptions = {}) => { ]; if (options.admin) { - decorators.push(adminDecorator); + decorators.push(AdminRoute()); } if (options.isShared) { - decorators.push(...sharedLinkDecorators); + decorators.push(SharedLinkRoute()); } return applyDecorators(...decorators); @@ -53,8 +46,9 @@ export const Authenticated = (options: AuthenticatedOptions = {}) => { export const PublicRoute = () => applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY)); -export const SharedLinkRoute = () => applyDecorators(...sharedLinkDecorators); -export const AdminRoute = () => adminDecorator; +export const SharedLinkRoute = () => + applyDecorators(SetMetadata(Metadata.SHARED_ROUTE, true), ApiQuery({ name: 'key', type: String, required: false })); +export const AdminRoute = (value = true) => SetMetadata(Metadata.ADMIN_ROUTE, value); export const AuthUser = createParamDecorator((data, ctx: ExecutionContext): AuthUserDto => { return ctx.switchToHttp().getRequest<{ user: AuthUserDto }>().user; diff --git a/server/src/immich/controllers/auth.controller.ts b/server/src/immich/controllers/auth.controller.ts index 83e4b5145..ae48a78eb 100644 --- a/server/src/immich/controllers/auth.controller.ts +++ b/server/src/immich/controllers/auth.controller.ts @@ -1,5 +1,4 @@ import { - AdminSignupResponseDto, AuthDeviceResponseDto, AuthService, AuthUserDto, @@ -15,7 +14,7 @@ import { ValidateAccessTokenResponseDto, } from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; -import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -42,9 +41,8 @@ export class AuthController { @PublicRoute() @Post('admin-sign-up') - @ApiBadRequestResponse({ description: 'The server already has an admin' }) - signUpAdmin(@Body() signUpCredential: SignUpDto): Promise { - return this.service.adminSignUp(signUpCredential); + signUpAdmin(@Body() dto: SignUpDto): Promise { + return this.service.adminSignUp(dto); } @Get('devices') diff --git a/server/src/immich/controllers/system-config.controller.ts b/server/src/immich/controllers/system-config.controller.ts index aa44c7acd..da2402efe 100644 --- a/server/src/immich/controllers/system-config.controller.ts +++ b/server/src/immich/controllers/system-config.controller.ts @@ -1,7 +1,8 @@ import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain'; -import { Body, Controller, Get, Put } from '@nestjs/common'; +import { MapThemeDto } from '@app/domain/system-config/system-config-map-theme.dto'; +import { Body, Controller, Get, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticated } from '../app.guard'; +import { AdminRoute, Authenticated } from '../app.guard'; import { UseValidation } from '../app.utils'; @ApiTags('System Config') @@ -30,4 +31,10 @@ export class SystemConfigController { getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { return this.service.getStorageTemplateOptions(); } + + @AdminRoute(false) + @Get('map/style.json') + getMapStyle(@Query() dto: MapThemeDto) { + return this.service.getMapStyle(dto.theme); + } } diff --git a/server/src/infra/entities/album.entity.ts b/server/src/infra/entities/album.entity.ts index 38ce4310c..fbc125351 100644 --- a/server/src/infra/entities/album.entity.ts +++ b/server/src/infra/entities/album.entity.ts @@ -56,4 +56,7 @@ export class AlbumEntity { @OneToMany(() => SharedLinkEntity, (link) => link.album) sharedLinks!: SharedLinkEntity[]; + + @Column({ default: true }) + isActivityEnabled!: boolean; } diff --git a/server/src/infra/entities/asset-job-status.entity.ts b/server/src/infra/entities/asset-job-status.entity.ts new file mode 100644 index 000000000..36905cc8f --- /dev/null +++ b/server/src/infra/entities/asset-job-status.entity.ts @@ -0,0 +1,15 @@ +import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; +import { AssetEntity } from './asset.entity'; + +@Entity('asset_job_status') +export class AssetJobStatusEntity { + @OneToOne(() => AssetEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @JoinColumn() + asset!: AssetEntity; + + @PrimaryColumn() + assetId!: string; + + @Column({ type: 'timestamptz', nullable: true }) + facesRecognizedAt!: Date | null; +} diff --git a/server/src/infra/entities/asset.entity.ts b/server/src/infra/entities/asset.entity.ts index 937107f9d..93050b23c 100644 --- a/server/src/infra/entities/asset.entity.ts +++ b/server/src/infra/entities/asset.entity.ts @@ -15,6 +15,7 @@ import { } from 'typeorm'; import { AlbumEntity } from './album.entity'; import { AssetFaceEntity } from './asset-face.entity'; +import { AssetJobStatusEntity } from './asset-job-status.entity'; import { ExifEntity } from './exif.entity'; import { LibraryEntity } from './library.entity'; import { SharedLinkEntity } from './shared-link.entity'; @@ -158,6 +159,9 @@ export class AssetEntity { @OneToMany(() => AssetEntity, (asset) => asset.stackParent) stack?: AssetEntity[]; + + @OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true }) + jobStatus?: AssetJobStatusEntity; } export enum AssetType { diff --git a/server/src/infra/entities/index.ts b/server/src/infra/entities/index.ts index cbe2bf6c3..e4b5c38b4 100644 --- a/server/src/infra/entities/index.ts +++ b/server/src/infra/entities/index.ts @@ -2,6 +2,7 @@ import { ActivityEntity } from './activity.entity'; import { AlbumEntity } from './album.entity'; import { APIKeyEntity } from './api-key.entity'; import { AssetFaceEntity } from './asset-face.entity'; +import { AssetJobStatusEntity } from './asset-job-status.entity'; import { AssetEntity } from './asset.entity'; import { AuditEntity } from './audit.entity'; import { ExifEntity } from './exif.entity'; @@ -20,6 +21,7 @@ export * from './activity.entity'; export * from './album.entity'; export * from './api-key.entity'; export * from './asset-face.entity'; +export * from './asset-job-status.entity'; export * from './asset.entity'; export * from './audit.entity'; export * from './exif.entity'; @@ -40,6 +42,7 @@ export const databaseEntities = [ APIKeyEntity, AssetEntity, AssetFaceEntity, + AssetJobStatusEntity, AuditEntity, ExifEntity, MoveEntity, diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index b71a44c0a..84e72e638 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -62,7 +62,8 @@ export enum SystemConfigKey { MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES = 'machineLearning.facialRecognition.minFaces', MAP_ENABLED = 'map.enabled', - MAP_TILE_URL = 'map.tileUrl', + MAP_LIGHT_STYLE = 'map.lightStyle', + MAP_DARK_STYLE = 'map.darkStyle', REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled', REVERSE_GEOCODING_CITIES_FILE_OVERRIDE = 'reverseGeocoding.citiesFileOverride', @@ -194,7 +195,8 @@ export interface SystemConfig { }; map: { enabled: boolean; - tileUrl: string; + lightStyle: string; + darkStyle: string; }; reverseGeocoding: { enabled: boolean; diff --git a/server/src/infra/migrations/1699268680508-DisableActivity.ts b/server/src/infra/migrations/1699268680508-DisableActivity.ts new file mode 100644 index 000000000..d860244f6 --- /dev/null +++ b/server/src/infra/migrations/1699268680508-DisableActivity.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class DisableActivity1699268680508 implements MigrationInterface { + name = 'DisableActivity1699268680508' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "albums" ADD "isActivityEnabled" boolean NOT NULL DEFAULT true`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "isActivityEnabled"`); + } + +} diff --git a/server/src/infra/migrations/1699345863886-AddJobStatus.ts b/server/src/infra/migrations/1699345863886-AddJobStatus.ts new file mode 100644 index 000000000..c7df6387c --- /dev/null +++ b/server/src/infra/migrations/1699345863886-AddJobStatus.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddJobStatus1699345863886 implements MigrationInterface { + name = 'AddJobStatus1699345863886' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId"))`); + await queryRunner.query(`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "asset_job_status" DROP CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4"`); + await queryRunner.query(`DROP TABLE "asset_job_status"`); + } + +} diff --git a/server/src/infra/repositories/access.repository.ts b/server/src/infra/repositories/access.repository.ts index 566514796..aff498ac3 100644 --- a/server/src/infra/repositories/access.repository.ts +++ b/server/src/infra/repositories/access.repository.ts @@ -43,6 +43,24 @@ export class AccessRepository implements IAccessRepository { }, }); }, + hasCreateAccess: (userId: string, albumId: string): Promise => { + return this.albumRepository.exist({ + where: [ + { + id: albumId, + isActivityEnabled: true, + sharedUsers: { + id: userId, + }, + }, + { + id: albumId, + isActivityEnabled: true, + ownerId: userId, + }, + ], + }); + }, }; library = { hasOwnerAccess: (userId: string, libraryId: string): Promise => { diff --git a/server/src/infra/repositories/activity.repository.ts b/server/src/infra/repositories/activity.repository.ts index 271124db5..25fd5fa7a 100644 --- a/server/src/infra/repositories/activity.repository.ts +++ b/server/src/infra/repositories/activity.repository.ts @@ -1,12 +1,12 @@ import { IActivityRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { IsNull, Repository } from 'typeorm'; import { ActivityEntity } from '../entities/activity.entity'; export interface ActivitySearch { albumId?: string; - assetId?: string; + assetId?: string | null; userId?: string; isLiked?: boolean; } @@ -20,7 +20,7 @@ export class ActivityRepository implements IActivityRepository { return this.repository.find({ where: { userId, - assetId, + assetId: assetId === null ? IsNull() : assetId, albumId, isLiked, }, diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index c237a3604..5112fc9f6 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -20,7 +20,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DateTime } from 'luxon'; import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm'; -import { AssetEntity, AssetType, ExifEntity } from '../entities'; +import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities'; import OptionalBetween from '../utils/optional-between.util'; import { paginate } from '../utils/pagination.util'; @@ -39,12 +39,17 @@ export class AssetRepository implements IAssetRepository { constructor( @InjectRepository(AssetEntity) private repository: Repository, @InjectRepository(ExifEntity) private exifRepository: Repository, + @InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository, ) {} async upsertExif(exif: Partial): Promise { await this.exifRepository.upsert(exif, { conflictPaths: ['assetId'] }); } + async upsertJobStatus(jobStatus: Partial): Promise { + await this.jobStatusRepository.upsert(jobStatus, { conflictPaths: ['assetId'] }); + } + create(asset: AssetCreate): Promise { return this.repository.save(asset); } @@ -104,10 +109,9 @@ export class AssetRepository implements IAssetRepository { .getMany(); } - getByIds(ids: string[]): Promise { - return this.repository.find({ - where: { id: In(ids) }, - relations: { + getByIds(ids: string[], relations?: FindOptionsRelations): Promise { + if (!relations) { + relations = { exifInfo: true, smartInfo: true, tags: true, @@ -115,7 +119,11 @@ export class AssetRepository implements IAssetRepository { person: true, }, stack: true, - }, + }; + } + return this.repository.find({ + where: { id: In(ids) }, + relations, withDeleted: true, }); } @@ -320,6 +328,7 @@ export class AssetRepository implements IAssetRepository { case WithoutProperty.FACES: relations = { faces: true, + jobStatus: true, }; where = { resizePath: Not(IsNull()), @@ -328,6 +337,9 @@ export class AssetRepository implements IAssetRepository { assetId: IsNull(), personId: IsNull(), }, + jobStatus: { + facesRecognizedAt: IsNull(), + }, }; break; diff --git a/server/src/infra/repositories/system-config.repository.ts b/server/src/infra/repositories/system-config.repository.ts index cfe0eab3d..0a66c2f22 100644 --- a/server/src/infra/repositories/system-config.repository.ts +++ b/server/src/infra/repositories/system-config.repository.ts @@ -1,5 +1,6 @@ import { ISystemConfigRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; +import axios from 'axios'; import { readFile } from 'fs/promises'; import { In, Repository } from 'typeorm'; import { SystemConfigEntity } from '../entities'; @@ -9,12 +10,17 @@ export class SystemConfigRepository implements ISystemConfigRepository { @InjectRepository(SystemConfigEntity) private repository: Repository, ) {} + async fetchStyle(url: string) { + return axios.get(url).then((response) => response.data); + } load(): Promise { return this.repository.find(); } - readFile = readFile; + readFile(filename: string): Promise { + return readFile(filename, { encoding: 'utf-8' }); + } saveAll(items: SystemConfigEntity[]): Promise { return this.repository.save(items); diff --git a/server/test/api/auth-api.ts b/server/test/api/auth-api.ts index 5f1750963..3043c941f 100644 --- a/server/test/api/auth-api.ts +++ b/server/test/api/auth-api.ts @@ -1,5 +1,5 @@ -import { AdminSignupResponseDto, AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto } from '@app/domain'; -import { adminSignupStub, loginResponseStub, loginStub, signupResponseStub } from '@test'; +import { AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto, UserResponseDto } from '@app/domain'; +import { adminSignupStub, loginResponseStub, loginStub } from '@test'; import request from 'supertest'; export const authApi = { @@ -7,9 +7,8 @@ export const authApi = { const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub); expect(status).toBe(201); - expect(body).toEqual(signupResponseStub); - return body as AdminSignupResponseDto; + return body as UserResponseDto; }, adminLogin: async (server: any) => { const { status, body } = await request(server).post('/auth/login').send(loginStub.admin); diff --git a/server/test/e2e/activity.e2e-spec.ts b/server/test/e2e/activity.e2e-spec.ts index 5cc86fc6a..0bb8aa2c9 100644 --- a/server/test/e2e/activity.e2e-spec.ts +++ b/server/test/e2e/activity.e2e-spec.ts @@ -247,6 +247,20 @@ describe(`${ActivityController.name} (e2e)`, () => { expect(body).toEqual(reaction); }); + it('should not confuse an album like with an asset like', async () => { + const reaction = await api.activityApi.create(server, admin.accessToken, { + albumId: album.id, + assetId: asset.id, + type: ReactionType.LIKE, + }); + const { status, body } = await request(server) + .post('/activity') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, type: 'like' }); + expect(status).toEqual(201); + expect(body.id).not.toEqual(reaction.id); + }); + it('should add a comment to an asset', async () => { const { status, body } = await request(server) .post('/activity') diff --git a/server/test/e2e/album.e2e-spec.ts b/server/test/e2e/album.e2e-spec.ts index e10f5414f..8348eff03 100644 --- a/server/test/e2e/album.e2e-spec.ts +++ b/server/test/e2e/album.e2e-spec.ts @@ -226,6 +226,7 @@ describe(`${AlbumController.name} (e2e)`, () => { assets: [], assetCount: 0, owner: expect.objectContaining({ email: user1.userEmail }), + isActivityEnabled: true, }); }); }); diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index b9b10e104..7736f085f 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -6,9 +6,12 @@ import { LoginResponseDto, SharedLinkResponseDto, TimeBucketSize, + WithoutProperty, + usePagination, } from '@app/domain'; import { AssetController } from '@app/immich'; import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities'; +import { AssetRepository } from '@app/infra/repositories'; import { INestApplication } from '@nestjs/common'; import { api } from '@test/api'; import { errorStub, uuidStub } from '@test/fixtures'; @@ -146,7 +149,6 @@ describe(`${AssetController.name} (e2e)`, () => { { should: 'require `deviceId`', dto: { ...makeUploadDto({ omit: 'deviceId' }) } }, { should: 'require `fileCreatedAt`', dto: { ...makeUploadDto({ omit: 'fileCreatedAt' }) } }, { should: 'require `fileModifiedAt`', dto: { ...makeUploadDto({ omit: 'fileModifiedAt' }) } }, - { should: 'require `isFavorite`', dto: { ...makeUploadDto({ omit: 'isFavorite' }) } }, { should: 'require `duration`', dto: { ...makeUploadDto({ omit: 'duration' }) } }, { should: 'throw if `isFavorite` is not a boolean', dto: { ...makeUploadDto(), isFavorite: 'not-a-boolean' } }, { should: 'throw if `isVisible` is not a boolean', dto: { ...makeUploadDto(), isVisible: 'not-a-boolean' } }, @@ -789,4 +791,55 @@ describe(`${AssetController.name} (e2e)`, () => { expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: asset3.id })])); }); }); + + describe(AssetRepository.name, () => { + describe('getWithout', () => { + describe('WithoutProperty.FACES', () => { + const getAssetIdsWithoutFaces = async () => { + const assetPagination = usePagination(10, (pagination) => + assetRepository.getWithout(pagination, WithoutProperty.FACES), + ); + let assets: AssetEntity[] = []; + for await (const assetsPage of assetPagination) { + assets = [...assets, ...assetsPage]; + } + return assets.map((a) => a.id); + }; + + beforeEach(async () => { + await assetRepository.save({ id: asset1.id, resizePath: '/path/to/resize' }); + expect(await getAssetIdsWithoutFaces()).toContain(asset1.id); + }); + + describe('with recognized faces', () => { + beforeEach(async () => { + const personRepository = app.get(IPersonRepository); + const person = await personRepository.create({ ownerId: asset1.ownerId, name: 'Test Person' }); + await personRepository.createFace({ assetId: asset1.id, personId: person.id }); + }); + + it('should not return asset with facesRecognizedAt unset', async () => { + expect(await getAssetIdsWithoutFaces()).not.toContain(asset1.id); + }); + + it('should not return asset with facesRecognizedAt set', async () => { + await assetRepository.upsertJobStatus({ assetId: asset1.id, facesRecognizedAt: new Date() }); + expect(await getAssetIdsWithoutFaces()).not.toContain(asset1.id); + }); + }); + + describe('without recognized faces', () => { + it('should return asset with facesRecognizedAt unset', async () => { + expect(await getAssetIdsWithoutFaces()).toContain(asset1.id); + }); + + it('should not return asset with facesRecognizedAt set', async () => { + expect(await getAssetIdsWithoutFaces()).toContain(asset1.id); + await assetRepository.upsertJobStatus({ assetId: asset1.id, facesRecognizedAt: new Date() }); + expect(await getAssetIdsWithoutFaces()).not.toContain(asset1.id); + }); + }); + }); + }); + }); }); diff --git a/server/test/e2e/auth.e2e-spec.ts b/server/test/e2e/auth.e2e-spec.ts index 985645129..191add42f 100644 --- a/server/test/e2e/auth.e2e-spec.ts +++ b/server/test/e2e/auth.e2e-spec.ts @@ -8,7 +8,6 @@ import { errorStub, loginResponseStub, loginStub, - signupResponseStub, uuidStub, } from '@test/fixtures'; import { testApp } from '@test/test-utils'; @@ -19,6 +18,24 @@ const lastName = 'Admin'; const password = 'Password123'; const email = 'admin@immich.app'; +const adminSignupResponse = { + id: expect.any(String), + firstName: 'Immich', + lastName: 'Admin', + email: 'admin@immich.app', + storageLabel: 'admin', + externalPath: null, + profileImagePath: '', + // why? lol + shouldChangePassword: true, + isAdmin: true, + createdAt: expect.any(String), + updatedAt: expect.any(String), + deletedAt: null, + oauthId: '', + memoriesEnabled: true, +}; + describe(`${AuthController.name} (e2e)`, () => { let server: any; let accessToken: string; @@ -84,7 +101,7 @@ describe(`${AuthController.name} (e2e)`, () => { .post('/auth/admin-sign-up') .send({ ...adminSignupStub, email: 'admin@local' }); expect(status).toEqual(201); - expect(body).toEqual({ ...signupResponseStub, email: 'admin@local' }); + expect(body).toEqual({ ...adminSignupResponse, email: 'admin@local' }); }); it('should transform email to lower case', async () => { @@ -92,7 +109,7 @@ describe(`${AuthController.name} (e2e)`, () => { .post('/auth/admin-sign-up') .send({ ...adminSignupStub, email: 'aDmIn@IMMICH.app' }); expect(status).toEqual(201); - expect(body).toEqual(signupResponseStub); + expect(body).toEqual(adminSignupResponse); }); it('should not allow a second admin to sign up', async () => { diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index 33b7c5693..0b508a2ef 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -96,7 +96,6 @@ describe(`${ServerInfoController.name} (e2e)`, () => { expect(body).toEqual({ loginPageMessage: '', oauthButtonText: 'Login with OAuth', - mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', trashDays: 30, isInitialized: true, }); diff --git a/server/test/e2e/system-config.e2e-spec.ts b/server/test/e2e/system-config.e2e-spec.ts new file mode 100644 index 000000000..dd7a69439 --- /dev/null +++ b/server/test/e2e/system-config.e2e-spec.ts @@ -0,0 +1,79 @@ +import { LoginResponseDto } from '@app/domain'; +import { SystemConfigController } from '@app/immich'; +import { api } from '@test/api'; +import { db } from '@test/db'; +import { errorStub } from '@test/fixtures'; +import { testApp } from '@test/test-utils'; +import request from 'supertest'; + +describe(`${SystemConfigController.name} (e2e)`, () => { + let server: any; + let admin: LoginResponseDto; + + beforeAll(async () => { + [server] = await testApp.create(); + }); + + afterAll(async () => { + await testApp.teardown(); + }); + + beforeEach(async () => { + await db.reset(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + }); + + describe('GET /system-config/map/style.json', () => { + it('should require authentication', async () => { + const { status, body } = await request(server).get('/system-config/map/style.json'); + expect(status).toBe(401); + expect(body).toEqual(errorStub.unauthorized); + }); + + it('should throw an error if a theme is not light or dark', async () => { + for (const theme of ['dark1', true, 123, '', null, undefined]) { + const { status, body } = await request(server) + .get('/system-config/map/style.json') + .query({ theme }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorStub.badRequest(['theme must be one of the following values: light, dark'])); + } + }); + + it('should return the light style.json', async () => { + const { status, body } = await request(server) + .get('/system-config/map/style.json') + .query({ theme: 'light' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: 'immich-map-light' })); + }); + + it('should return the dark style.json', async () => { + const { status, body } = await request(server) + .get('/system-config/map/style.json') + .query({ theme: 'dark' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' })); + }); + + it('should not require admin authentication', async () => { + const credentials = { email: 'user1@immich.app', password: 'Password123' }; + await api.userApi.create(server, admin.accessToken, { + ...credentials, + firstName: 'User 1', + lastName: 'Test', + }); + const { accessToken } = await api.authApi.login(server, credentials); + const { status, body } = await request(server) + .get('/system-config/map/style.json') + .query({ theme: 'dark' }) + .set('Authorization', `Bearer ${accessToken}`); + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' })); + }); + }); +}); diff --git a/server/test/fixtures/album.stub.ts b/server/test/fixtures/album.stub.ts index 48ed92817..fd4464d19 100644 --- a/server/test/fixtures/album.stub.ts +++ b/server/test/fixtures/album.stub.ts @@ -18,6 +18,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), sharedWithUser: Object.freeze({ id: 'album-2', @@ -33,6 +34,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [userStub.user1], + isActivityEnabled: true, }), sharedWithMultiple: Object.freeze({ id: 'album-3', @@ -48,6 +50,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [userStub.user1, userStub.user2], + isActivityEnabled: true, }), sharedWithAdmin: Object.freeze({ id: 'album-3', @@ -63,6 +66,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [userStub.admin], + isActivityEnabled: true, }), oneAsset: Object.freeze({ id: 'album-4', @@ -78,6 +82,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), twoAssets: Object.freeze({ id: 'album-4a', @@ -93,6 +98,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), emptyWithInvalidThumbnail: Object.freeze({ id: 'album-5', @@ -108,6 +114,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), emptyWithValidThumbnail: Object.freeze({ id: 'album-5', @@ -123,6 +130,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), oneAssetInvalidThumbnail: Object.freeze({ id: 'album-6', @@ -138,6 +146,7 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), oneAssetValidThumbnail: Object.freeze({ id: 'album-6', @@ -153,5 +162,6 @@ export const albumStub = { deletedAt: null, sharedLinks: [], sharedUsers: [], + isActivityEnabled: true, }), }; diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 6a45c16af..68ff9b717 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -12,14 +12,6 @@ export const userSignupStub = { memoriesEnabled: true, }; -export const signupResponseStub = { - id: expect.any(String), - email: 'admin@immich.app', - firstName: 'Immich', - lastName: 'Admin', - createdAt: expect.any(String), -}; - export const loginStub = { admin: { email: 'admin@immich.app', diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index dd6eb5233..56a0c1045 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -100,6 +100,7 @@ const albumResponse: AlbumResponseDto = { hasSharedLink: false, assets: [], assetCount: 1, + isActivityEnabled: true, }; export const sharedLinkStub = { @@ -179,6 +180,7 @@ export const sharedLinkStub = { albumThumbnailAssetId: null, sharedUsers: [], sharedLinks: [], + isActivityEnabled: true, assets: [ { id: 'id_1', diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 4f7992e86..6abfc7c9e 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -19,6 +19,7 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => activity: { hasOwnerAccess: jest.fn(), hasAlbumOwnerAccess: jest.fn(), + hasCreateAccess: jest.fn(), }, asset: { hasOwnerAccess: jest.fn(), diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index c185d3983..3b584e09c 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -4,6 +4,7 @@ export const newAssetRepositoryMock = (): jest.Mocked => { return { create: jest.fn(), upsertExif: jest.fn(), + upsertJobStatus: jest.fn(), getByDate: jest.fn(), getByDayOfYear: jest.fn(), getByIds: jest.fn().mockResolvedValue([]), diff --git a/server/test/repositories/system-config.repository.mock.ts b/server/test/repositories/system-config.repository.mock.ts index 44ad8f630..3be69f267 100644 --- a/server/test/repositories/system-config.repository.mock.ts +++ b/server/test/repositories/system-config.repository.mock.ts @@ -6,6 +6,7 @@ export const newSystemConfigRepositoryMock = (reset = true): jest.Mocked=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", @@ -1934,294 +1884,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3197,6 +2859,75 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, "node_modules/@mdi/js": { "version": "7.3.67", "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.3.67.tgz", @@ -3759,10 +3490,9 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" }, "node_modules/@types/geojson": { - "version": "7946.0.11", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.11.tgz", - "integrity": "sha512-L7A0AINMXQpVwxHJ4jxD6/XjZ4NDufaRlUJHjNIFKYUFBH1SvOW+neaqb0VTRSLW5suSrSu19ObFEFnfNcr+qg==", - "dev": true + "version": "7946.0.12", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz", + "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==" }, "node_modules/@types/graceful-fs": { "version": "4.1.7", @@ -3862,24 +3592,6 @@ "integrity": "sha512-/ZJGVeDif6EHRzK3kUifyOekGJcBXD1s/eRYAYgkJHI4QAkohz62E0PSMbFrGpOdTulPWRgOAh1mFZbYw9a9iQ==", "dev": true }, - "node_modules/@types/leaflet": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.6.tgz", - "integrity": "sha512-HakGTK5LBBWegNWsAmTlG55zN1zszYec7aG47/z6SzT90bW2vqjmbqk3YKAbrtveO+G7fSTKTYqVbIwAFnTrbg==", - "dev": true, - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/leaflet.markercluster": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.2.tgz", - "integrity": "sha512-Yfi5R0Fb0xc/qotTuqJDAX65XKRt6DauzIdsMTcgrrhgf8DpBOrqPV/jw8/zUjY8FetRd6QdnefNdNJKmG/+zA==", - "dev": true, - "dependencies": { - "@types/leaflet": "*" - } - }, "node_modules/@types/lodash": { "version": "4.14.199", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", @@ -3901,12 +3613,32 @@ "integrity": "sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==", "dev": true }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.3.tgz", + "integrity": "sha512-2W46IOXlu7vC8m3+M5rDqSnuY22GFxxx3xhkoyqyPWrD+eP2iAwNst0A1+umLYjCTJMJTSpiofphn9h9k+Kw+w==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.3.tgz", + "integrity": "sha512-d263B3KCQtXKVZMHpMJrEW5EeLBsQ8jvAS9nhpUKC5hHIlQaACG9PWkW8qxEeNuceo9120AwPjeS91uNa4ltqA==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/node": { "version": "20.8.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", "dev": true }, + "node_modules/@types/pbf": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.4.tgz", + "integrity": "sha512-SOFlLGZkLbEXJRwcWCqeP/Koyaf/uAqLXHUsdo/nMfjLsNd8kqauwHe9GBOljSmpcHp/LC6kOjo3SidGjNirVA==" + }, "node_modules/@types/pug": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.7.tgz", @@ -3931,6 +3663,14 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/supercluster": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.2.tgz", + "integrity": "sha512-qMhofL945Z4njQUuntadexAgPtpiBC014WvVqU70Prj42LC77Xgmz04us7hSMmwjs7KbgAwGBmje+FSOvDbP0Q==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -4431,6 +4171,14 @@ "dequal": "^2.0.3" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -4453,6 +4201,14 @@ "node": ">=8" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4871,6 +4627,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -5317,6 +5090,28 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -5550,6 +5345,11 @@ "node": ">=12" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "node_modules/electron-to-chromium": { "version": "1.4.538", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.538.tgz", @@ -6252,6 +6052,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/factory.ts": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/factory.ts/-/factory.ts-1.4.1.tgz", @@ -6329,6 +6140,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", + "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6484,6 +6300,11 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -6521,7 +6342,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "engines": { "node": ">=10" }, @@ -6529,6 +6349,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gl-matrix": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", @@ -6586,6 +6414,30 @@ "node": ">=10" } }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6942,6 +6794,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -6956,6 +6813,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -7086,6 +6951,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7177,7 +7050,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -7325,14 +7197,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9475,6 +9345,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9487,11 +9362,26 @@ "node": ">=6" } }, + "node_modules/just-compare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/just-compare/-/just-compare-2.3.0.tgz", + "integrity": "sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg==" + }, + "node_modules/just-flush": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/just-flush/-/just-flush-2.3.0.tgz", + "integrity": "sha512-fBuxQ1gJ61BurmhwKS5LYTzhkbrT5j/2U7ax+UbLm9aRvCTh2h6AfzLteOckE4KKomqOf0Y3zIG3Xu57sRsKUg==" + }, "node_modules/justified-layout": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz", "integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg==" }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -9505,7 +9395,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9525,19 +9414,6 @@ "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", "dev": true }, - "node_modules/leaflet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" - }, - "node_modules/leaflet.markercluster": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", - "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", - "peerDependencies": { - "leaflet": "^1.3.1" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -9710,6 +9586,45 @@ "tmpl": "1.0.5" } }, + "node_modules/maplibre-gl": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.5.2.tgz", + "integrity": "sha512-deqYA/RiEyXMGroZMDbOWNQTLnFsxREC+mDkQnuyCUNdBWm1KHafsXJYZP7rlLa5RLQNq05IAUAizY9aHTpIUw==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^19.3.3", + "@types/geojson": "^7946.0.12", + "@types/mapbox__point-geometry": "^0.1.3", + "@types/mapbox__vector-tile": "^1.3.3", + "@types/pbf": "^3.0.4", + "@types/supercluster": "^7.1.2", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^2.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -9847,6 +9762,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -10187,6 +10107,18 @@ "node": ">=8" } }, + "node_modules/pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -10261,6 +10193,14 @@ "node": ">=8" } }, + "node_modules/pmtiles": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.11.0.tgz", + "integrity": "sha512-dU9SzzaqmCGpdEuTnIba6bDHT6j09ZJFIXxwGpvkiEnce3ZnBB1VKt6+EOmJGueriweaZLAMTUmKVElU2CBe0g==", + "dependencies": { + "fflate": "^0.8.0" + } + }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10434,6 +10374,11 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10516,6 +10461,11 @@ "node": ">=6" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -10573,6 +10523,11 @@ } ] }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10754,6 +10709,14 @@ "node": ">=8" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -10847,6 +10810,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -10950,6 +10918,20 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -11073,6 +11055,38 @@ "sorcery": "bin/sorcery" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11099,6 +11113,40 @@ "source-map": "^0.6.0" } }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11261,6 +11309,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -11423,6 +11479,35 @@ "svelte": "^3.48.0 || ^4.0.0" } }, + "node_modules/svelte-maplibre": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.6.0.tgz", + "integrity": "sha512-/iTUkJtJpAH22aXAkEysDhWQa6d8J+WpvxenU1k6r5CASVGc4TyYLe7kQt3CkhlNSIP05EpkADvJJMeUMcnbvw==", + "dependencies": { + "d3-geo": "^3.1.0", + "just-compare": "^2.3.0", + "just-flush": "^2.3.0", + "maplibre-gl": "^3.0.0", + "pmtiles": "^2.10.0" + }, + "peerDependencies": { + "@deck.gl/core": "^8.8.0", + "@deck.gl/layers": "^8.8.0", + "@deck.gl/mapbox": "^8.8.0", + "svelte": "^3.54.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@deck.gl/core": { + "optional": true + }, + "@deck.gl/layers": { + "optional": true + }, + "@deck.gl/mapbox": { + "optional": true + } + } + }, "node_modules/svelte-preprocess": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", @@ -11679,6 +11764,11 @@ "globrex": "^0.1.2" } }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11821,6 +11911,19 @@ "node": ">=14.17" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -11885,6 +11988,20 @@ "node": ">=4" } }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -12038,6 +12155,16 @@ } } }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -12348,8759 +12475,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", - "dev": true - }, - "@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, - "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - } - }, - "@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", - "dev": true - }, - "@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - } - }, - "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dev": true, - "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", - "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", - "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "dev": true, - "requires": { - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" - } - }, - "@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" - } - }, - "@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" - } - }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "requires": {} - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz", - "integrity": "sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" - } - }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", - "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/preset-env": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.20.tgz", - "integrity": "sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.20", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.15", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.15", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.15", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", - "@babel/plugin-transform-modules-systemjs": "^7.22.11", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.22.15", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.22.19", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - } - }, - "@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz", - "integrity": "sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-typescript": "^7.22.15" - } - }, - "@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "@babel/runtime": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", - "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", - "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cfcs/core": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@cfcs/core/-/core-0.0.24.tgz", - "integrity": "sha512-feB38qu+eDk0Pggh/yR7gjaNmvUYA2uCxHP3Pz2MLE4LZ/9jPdtu8bzCSI47yTEhWyZCF5Pk698hdz8IN2mTjA==", - "requires": { - "@egjs/component": "^3.0.4" - } - }, - "@egjs/component": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@egjs/component/-/component-3.0.4.tgz", - "integrity": "sha512-sXA7bGbIeLF2OAw/vpka66c6QBBUPcA4UUhR4WGJfnp2XWdiI8QrnJGJMr/UxpE/xnevX9tN3jvNPlW8WkHl3g==" - }, - "@egjs/imready": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@egjs/imready/-/imready-1.4.1.tgz", - "integrity": "sha512-JIOBs4lB7FYdsKi5uvz2j3SObX8eShtZjtqlOH41tm185aJOQZwiKBK8+V4MxzG4X6DqVhpdN8UcuVwBbElfsg==", - "requires": { - "@cfcs/core": "^0.0.24", - "@egjs/component": "^3.0.1" - } - }, - "@egjs/svelte-view360": { - "version": "4.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@egjs/svelte-view360/-/svelte-view360-4.0.0-beta.7.tgz", - "integrity": "sha512-qFNbLNME8H7QU2lg8SCKUTPoBXVdBcM5m8zmlDRE72esCTguDzUq2szXD7L1JWcb2lYPTFl3HVp/sZlcQ/1HpQ==", - "requires": { - "@egjs/view360": "4.0.0-beta.7" - } - }, - "@egjs/view360": { - "version": "4.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@egjs/view360/-/view360-4.0.0-beta.7.tgz", - "integrity": "sha512-prVTTxuQ1/k59NM7G0tm58k2vPHGoaExoFr5E7MoJaSGF56Otj4okQHAxxosXH87aQLN0feZMtBlsKz0b/7zEw==", - "requires": { - "@egjs/component": "^3.0.2", - "@egjs/imready": "^1.3.0", - "@types/webxr": "^0.5.1", - "gl-matrix": "^3.4.3" - } - }, - "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true - }, - "@faker-js/faker": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", - "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", - "dev": true - }, - "@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "dev": true - }, - "@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", - "dev": true, - "requires": { - "@floating-ui/utils": "^0.1.3" - } - }, - "@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", - "dev": true, - "requires": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" - } - }, - "@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - } - }, - "@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "requires": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - } - }, - "@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3" - } - }, - "@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - } - }, - "@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@mdi/js": { - "version": "7.3.67", - "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.3.67.tgz", - "integrity": "sha512-MnRjknFqpTC6FifhGHjZ0+QYq2bAkZFQqIj8JA2AdPZbBxUvr8QSgB2yPAJ8/ob/XkR41xlg5majDR3c1JP1hw==" - }, - "@namnode/store": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@namnode/store/-/store-0.1.0.tgz", - "integrity": "sha512-4NGTldxKcmY0UuZ7OEkvCjs8ZEoeYB6M2UwMu74pdLiFMKxXbj9HdNk1Qn213bxX1O7bY5h+PLh5DZsTURZkYA==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@polka/url": { - "version": "1.0.0-next.23", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", - "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", - "dev": true - }, - "@rollup/plugin-commonjs": { - "version": "25.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", - "integrity": "sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - } - }, - "@rollup/plugin-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", - "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1" - } - }, - "@rollup/plugin-node-resolve": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", - "integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - } - }, - "@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } - }, - "@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" - }, - "@sveltejs/adapter-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz", - "integrity": "sha512-A0VgRQDCDPzdLNoiAbcOxGw4zT1Mc+n1LwT1OmO350R7WxrEqdMUChPPOd1iMfIDWlP4ie6E2d/WQf5es2d4Zw==", - "dev": true, - "requires": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.7.0" - } - }, - "@sveltejs/kit": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.25.1.tgz", - "integrity": "sha512-pD8XsvNJNgTNkFngNlM60my/X8dXWPKVzN5RghEQr0NjGZmuCjy49AfFu2cGbZjNf5pBcqd2RCNMW912P5fkhA==", - "dev": true, - "requires": { - "@sveltejs/vite-plugin-svelte": "^2.4.1", - "@types/cookie": "^0.5.1", - "cookie": "^0.5.0", - "devalue": "^4.3.1", - "esm-env": "^1.0.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.0", - "mime": "^3.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", - "sirv": "^2.0.2", - "tiny-glob": "^0.2.9", - "undici": "~5.25.0" - }, - "dependencies": { - "magic-string": { - "version": "0.30.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", - "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - } - } - }, - "@sveltejs/vite-plugin-svelte": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", - "integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", - "dev": true, - "requires": { - "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.3", - "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.4" - }, - "dependencies": { - "magic-string": { - "version": "0.30.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", - "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - } - } - }, - "@sveltejs/vite-plugin-svelte-inspector": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz", - "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", - "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", - "dev": true, - "requires": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/svelte": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-4.0.3.tgz", - "integrity": "sha512-GldAnyGEOn5gMwME+hLVQrnfuKZFB+it5YOMnRBHX+nqeHMsSa18HeqkdvGqtqLpvn81xV7R7EYFb500ngUfXA==", - "dev": true, - "requires": { - "@testing-library/dom": "^9.3.1" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@types/aria-query": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.2.tgz", - "integrity": "sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ==", - "dev": true - }, - "@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/cookie": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", - "integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA==", - "dev": true - }, - "@types/dom-to-image": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.5.tgz", - "integrity": "sha512-SMYQf4urvjHfSsaEMhIULyjfawUv2a92OfglcGF7dQdDHBjfnGPtWoxw6hmKMiwsdmowHn70mxLw+F5cA7Imyg==", - "dev": true - }, - "@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" - }, - "@types/geojson": { - "version": "7946.0.11", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.11.tgz", - "integrity": "sha512-L7A0AINMXQpVwxHJ4jxD6/XjZ4NDufaRlUJHjNIFKYUFBH1SvOW+neaqb0VTRSLW5suSrSu19ObFEFnfNcr+qg==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true - }, - "@types/justified-layout": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/justified-layout/-/justified-layout-4.1.1.tgz", - "integrity": "sha512-/ZJGVeDif6EHRzK3kUifyOekGJcBXD1s/eRYAYgkJHI4QAkohz62E0PSMbFrGpOdTulPWRgOAh1mFZbYw9a9iQ==", - "dev": true - }, - "@types/leaflet": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.6.tgz", - "integrity": "sha512-HakGTK5LBBWegNWsAmTlG55zN1zszYec7aG47/z6SzT90bW2vqjmbqk3YKAbrtveO+G7fSTKTYqVbIwAFnTrbg==", - "dev": true, - "requires": { - "@types/geojson": "*" - } - }, - "@types/leaflet.markercluster": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.2.tgz", - "integrity": "sha512-Yfi5R0Fb0xc/qotTuqJDAX65XKRt6DauzIdsMTcgrrhgf8DpBOrqPV/jw8/zUjY8FetRd6QdnefNdNJKmG/+zA==", - "dev": true, - "requires": { - "@types/leaflet": "*" - } - }, - "@types/lodash": { - "version": "4.14.199", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", - "dev": true - }, - "@types/lodash-es": { - "version": "4.17.9", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", - "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/luxon": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz", - "integrity": "sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==", - "dev": true - }, - "@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", - "dev": true - }, - "@types/pug": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.7.tgz", - "integrity": "sha512-I469DU0UXNC1aHepwirWhu9YKg5fkxohZD95Ey/5A7lovC+Siu+MCLffva87lnfThaOrw9Vb1DUN5t55oULAAw==", - "dev": true - }, - "@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, - "@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/testing-library__jest-dom": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", - "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", - "dev": true, - "requires": { - "@types/jest": "*" - } - }, - "@types/tough-cookie": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", - "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", - "dev": true - }, - "@types/webxr": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.5.tgz", - "integrity": "sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==" - }, - "@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@zoom-image/core": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.24.0.tgz", - "integrity": "sha512-sN2bkyFelUtV9v07Nvhm+V86hVouEGfqsRz0Grqox943vMDn16izS2yRK3ZHuqeyMskvSDxCcC65p8B15ieX+w==", - "requires": { - "@namnode/store": "^0.1.0" - } - }, - "@zoom-image/svelte": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.1.16.tgz", - "integrity": "sha512-HxcsBaAj7XcDYdVF1ynMtbayNWB/csG0Iwtg35Q1IPeoKj+W/jVNYHeuzJgxeNnPGnN7ZyeAe/OX8c8BFW9XJg==", - "requires": { - "@zoom-image/core": "0.24.0" - } - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" - }, - "acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "requires": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "requires": { - "dequal": "^2.0.3" - } - }, - "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", - "dev": true, - "requires": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "requires": { - "dequal": "^2.0.3" - } - }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", - "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.2", - "semver": "^6.3.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz", - "integrity": "sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.32.2" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", - "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.2" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001542", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz", - "integrity": "sha512-UrtAXVcj1mvPBFQ4sKd38daP8dEcXXr5sQe6QNNinaPd0iA/cxg9/l3VrSdL73jgw5sKyuQ6jNgiKO12W3SsVA==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - }, - "dependencies": { - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "requires": { - "@types/estree": "^1.0.0" - } - } - } - }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, - "copy-image-clipboard": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/copy-image-clipboard/-/copy-image-clipboard-2.1.2.tgz", - "integrity": "sha512-3VCXVl2IpFfOyD8drv9DozcNlwmqBqxOlsgkEGyVAzadjlPk1go8YNZyy8QmTnwHPxSFpeCR9OdsStEdVK7qDA==" - }, - "core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", - "dev": true, - "requires": { - "browserslist": "^4.22.1" - } - }, - "create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "requires": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - } - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "requires": {} - }, - "deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" - }, - "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", - "dev": true - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true - }, - "dom-to-image": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz", - "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "electron-to-chromium": { - "version": "1.4.538", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.538.tgz", - "integrity": "sha512-1a2m63NEookb1beNFTGDihgF3CKL7ksZ7PSA0VloON5DpTEhnOVgaDes8xkrDhkXRxlcN8JymQDGnv+Nn+uvhg==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "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==", - "dev": true - }, - "engine.io-client": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", - "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" - }, - "dependencies": { - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "requires": {} - } - } - }, - "engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - } - }, - "es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "dev": true - }, - "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "requires": {} - }, - "eslint-plugin-svelte": { - "version": "2.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.33.2.tgz", - "integrity": "sha512-knWmauax+E/jvQ9CmuX5dAhQKP9P4eGQZxWa5RMutEJVCcy0wFmiUvOeDND2jR4vUkbDlX4khKjaceY7QzbkYw==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", - "esutils": "^2.0.3", - "known-css-properties": "^0.28.0", - "postcss": "^8.4.5", - "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "esm-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "factory.ts": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/factory.ts/-/factory.ts-1.4.1.tgz", - "integrity": "sha512-x5hrzGOZvQnw82ZK+fUo/p1nlbJGCi564FBx3jQWQix6xyEK8xvdCwjdgdmbaUiqfURWWfjgTJyBU5OSfs52tw==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "source-map-support": "^0.5.21" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "requires": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "gl-matrix": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", - "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "dev": true, - "requires": { - "harmony-reflect": "^1.4.6" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "requires": { - "builtin-modules": "^3.3.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "requires": { - "which-typed-array": "^1.1.11" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - } - }, - "jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - } - }, - "jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - } - }, - "jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "justified-layout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz", - "integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg==" - }, - "keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true - }, - "known-css-properties": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", - "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", - "dev": true - }, - "leaflet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" - }, - "leaflet.markercluster": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", - "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", - "requires": {} - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==" - }, - "lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true - }, - "magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "requires": { - "entities": "^4.4.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - }, - "dependencies": { - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "requires": { - "@types/estree": "^1.0.0" - } - }, - "is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "requires": { - "@types/estree": "*" - } - } - } - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.11" - } - }, - "postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "requires": {} - }, - "postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", - "dev": true, - "requires": {} - }, - "postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true - }, - "prettier-plugin-svelte": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.10.1.tgz", - "integrity": "sha512-Wlq7Z5v2ueCubWo0TZzKc9XHcm7TDxqcuzRuGd0gcENfzfT4JZ9yDlCbEgxWgiPmLHkBjfOtpAWkcT28MCDpUQ==", - "dev": true, - "requires": {} - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "dependencies": { - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - } - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" - } - }, - "regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "requires": { - "mri": "^1.1.0" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", - "dev": true, - "requires": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true - }, - "set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sirv": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", - "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", - "dev": true, - "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^3.0.0" - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "socket.io-client": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", - "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.5.2", - "socket.io-parser": "~4.2.4" - } - }, - "socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - } - }, - "sorcery": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", - "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.14", - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "requires": { - "internal-slot": "^1.0.4" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svelte": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz", - "integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==", - "requires": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^3.2.1", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", - "locate-character": "^3.0.0", - "magic-string": "^0.30.0", - "periscopic": "^3.1.0" - }, - "dependencies": { - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "requires": { - "@types/estree": "^1.0.0" - } - }, - "is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "requires": { - "@types/estree": "*" - } - }, - "magic-string": { - "version": "0.30.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", - "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - } - } - }, - "svelte-check": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.5.2.tgz", - "integrity": "sha512-5a/YWbiH4c+AqAUP+0VneiV5bP8YOk9JL3jwvN+k2PEPLgpu85bjQc5eE67+eIZBBwUEJzmO3I92OqKcqbp3fw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.17", - "chokidar": "^3.4.1", - "fast-glob": "^3.2.7", - "import-fresh": "^3.2.1", - "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^5.0.4", - "typescript": "^5.0.3" - } - }, - "svelte-eslint-parser": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.0.tgz", - "integrity": "sha512-5awZ6Bs+Tb/zQwa41PSdcLynAVQTwW0HGyCBjtbAQ59taLZqDgQSMzRlDmapjZdDtzERm0oXDZNE0E+PKJ6ryg==", - "dev": true, - "requires": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.28", - "postcss-scss": "^4.0.7" - }, - "dependencies": { - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", - "dev": true, - "requires": {} - }, - "svelte-jester": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/svelte-jester/-/svelte-jester-2.3.2.tgz", - "integrity": "sha512-JtxSz4FWAaCRBXbPsh4LcDs4Ua7zdXgLC0TZvT1R56hRV0dymmNP+abw67DTPF7sQPyNxWsOKd0Sl7Q8SnP8kg==", - "dev": true, - "requires": {} - }, - "svelte-loading-spinners": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/svelte-loading-spinners/-/svelte-loading-spinners-0.3.4.tgz", - "integrity": "sha512-vKaW71QMCBcTNijAGc0mUl8k3DQ66iYmp6MB8BMGCXyWk82bTrcLy8FOnSm9fE+8q6TwzD6PLUoYFHt0II93Xw==" - }, - "svelte-local-storage-store": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/svelte-local-storage-store/-/svelte-local-storage-store-0.5.0.tgz", - "integrity": "sha512-SEDrpapeia6fUqta+r1NvSLlJYPkZ4pBcl15EYIOSPNzy6vhpoXu8cnzUDmZxsWl7fZGAHxrVH9UyZCbyO4W+g==", - "requires": {} - }, - "svelte-preprocess": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", - "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==", - "dev": true, - "requires": { - "@types/pug": "^2.0.6", - "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", - "sorcery": "^0.11.0", - "strip-indent": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tailwind-merge": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", - "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", - "dev": true - }, - "tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, - "requires": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.18.2", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "dependencies": { - "postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - } - }, - "yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, - "thumbhash": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz", - "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==" - }, - "tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dev": true, - "requires": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true - }, - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true - }, - "undici": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.3.tgz", - "integrity": "sha512-7lmhlz3K1+IKB6IUjkdzV2l0jKY8/0KguEMdEpzzXCug5pEGIp3DxUg0DEN65DrVoxHiRKpPORC/qzX+UglSkQ==", - "dev": true, - "requires": { - "@fastify/busboy": "^2.0.0" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - } - } - }, - "vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - } - }, - "vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", - "dev": true, - "requires": {} - }, - "w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "requires": { - "xml-name-validator": "^4.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/web/package.json b/web/package.json index 7934c28b2..1a4c6521a 100644 --- a/web/package.json +++ b/web/package.json @@ -31,8 +31,6 @@ "@types/cookie": "^0.5.1", "@types/dom-to-image": "^2.6.4", "@types/justified-layout": "^4.1.0", - "@types/leaflet": "^1.9.1", - "@types/leaflet.markercluster": "^1.5.1", "@types/lodash-es": "^4.17.6", "@types/luxon": "^3.2.0", "@typescript-eslint/eslint-plugin": "^5.53.0", @@ -70,13 +68,13 @@ "dom-to-image": "^2.6.0", "handlebars": "^4.7.7", "justified-layout": "^4.1.0", - "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", "lodash-es": "^4.17.21", "luxon": "^3.2.1", + "maplibre-gl": "^3.5.2", "socket.io-client": "^4.6.1", "svelte-loading-spinners": "^0.3.4", "svelte-local-storage-store": "^0.5.0", + "svelte-maplibre": "^0.6.0", "thumbhash": "^0.1.1" } } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 9f4d765f2..798d388d7 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -209,43 +209,6 @@ export interface AddUsersDto { */ 'sharedUserIds': Array; } -/** - * - * @export - * @interface AdminSignupResponseDto - */ -export interface AdminSignupResponseDto { - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'createdAt': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'email': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'id': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'lastName': string; -} /** * * @export @@ -331,6 +294,12 @@ export interface AlbumResponseDto { * @memberof AlbumResponseDto */ 'id': string; + /** + * + * @type {boolean} + * @memberof AlbumResponseDto + */ + 'isActivityEnabled': boolean; /** * * @type {string} @@ -1855,7 +1824,7 @@ export interface ImportAssetDto { * @type {boolean} * @memberof ImportAssetDto */ - 'isFavorite': boolean; + 'isFavorite'?: boolean; /** * * @type {boolean} @@ -2255,6 +2224,20 @@ export interface MapMarkerResponseDto { */ 'lon': number; } +/** + * + * @export + * @enum {string} + */ + +export const MapTheme = { + Light: 'light', + Dark: 'dark' +} as const; + +export type MapTheme = typeof MapTheme[keyof typeof MapTheme]; + + /** * * @export @@ -2587,6 +2570,20 @@ export interface QueueStatusDto { */ 'isPaused': boolean; } +/** + * + * @export + * @enum {string} + */ + +export const ReactionLevel = { + Album: 'album', + Asset: 'asset' +} as const; + +export type ReactionLevel = typeof ReactionLevel[keyof typeof ReactionLevel]; + + /** * * @export @@ -2853,12 +2850,6 @@ export interface ServerConfigDto { * @memberof ServerConfigDto */ 'loginPageMessage': string; - /** - * - * @type {string} - * @memberof ServerConfigDto - */ - 'mapTileUrl': string; /** * * @type {string} @@ -3726,6 +3717,12 @@ export interface SystemConfigMachineLearningDto { * @interface SystemConfigMapDto */ export interface SystemConfigMapDto { + /** + * + * @type {string} + * @memberof SystemConfigMapDto + */ + 'darkStyle': string; /** * * @type {boolean} @@ -3737,7 +3734,7 @@ export interface SystemConfigMapDto { * @type {string} * @memberof SystemConfigMapDto */ - 'tileUrl': string; + 'lightStyle': string; } /** * @@ -4160,6 +4157,12 @@ export interface UpdateAlbumDto { * @memberof UpdateAlbumDto */ 'description'?: string; + /** + * + * @type {boolean} + * @memberof UpdateAlbumDto + */ + 'isActivityEnabled'?: boolean; } /** * @@ -5076,11 +5079,12 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {ReactionLevel} [level] * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise => { + getActivities: async (albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'albumId' is not null or undefined assertParamExists('getActivities', 'albumId', albumId) const localVarPath = `/activity`; @@ -5116,6 +5120,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat localVarQueryParameter['type'] = type; } + if (level !== undefined) { + localVarQueryParameter['level'] = level; + } + if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -5216,12 +5224,13 @@ export const ActivityApiFp = function(configuration?: Configuration) { * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {ReactionLevel} [level] * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options); + async getActivities(albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, level, userId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -5270,7 +5279,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP * @throws {RequiredError} */ getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath)); + return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(axios, basePath)); }, /** * @@ -5339,6 +5348,13 @@ export interface ActivityApiGetActivitiesRequest { */ readonly type?: ReactionType + /** + * + * @type {ReactionLevel} + * @memberof ActivityApiGetActivities + */ + readonly level?: ReactionLevel + /** * * @type {string} @@ -5405,7 +5421,7 @@ export class ActivityApi extends BaseAPI { * @memberof ActivityApi */ public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) { - return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); + return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); } /** @@ -6695,16 +6711,18 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * Get all AssetEntity belong to the user + * @param {number} [skip] + * @param {number} [take] * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {number} [skip] * @param {string} [updatedAfter] + * @param {string} [updatedBefore] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { + getAllAssets: async (skip?: number, take?: number, userId?: string, isFavorite?: boolean, isArchived?: boolean, updatedAfter?: string, updatedBefore?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/asset`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -6726,6 +6744,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (skip !== undefined) { + localVarQueryParameter['skip'] = skip; + } + + if (take !== undefined) { + localVarQueryParameter['take'] = take; + } + if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -6738,16 +6764,18 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['isArchived'] = isArchived; } - if (skip !== undefined) { - localVarQueryParameter['skip'] = skip; - } - if (updatedAfter !== undefined) { localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? (updatedAfter as any).toISOString() : updatedAfter; } + if (updatedBefore !== undefined) { + localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ? + (updatedBefore as any).toISOString() : + updatedBefore; + } + if (ifNoneMatch != null) { localVarHeaderParameter['if-none-match'] = String(ifNoneMatch); } @@ -7868,11 +7896,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} deviceId * @param {string} fileCreatedAt * @param {string} fileModifiedAt - * @param {boolean} isFavorite * @param {string} [key] * @param {string} [duration] * @param {boolean} [isArchived] * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] * @param {boolean} [isOffline] * @param {boolean} [isReadOnly] * @param {boolean} [isVisible] @@ -7882,7 +7910,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile: async (assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options: AxiosRequestConfig = {}): Promise => { + uploadFile: async (assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isFavorite?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetData' is not null or undefined assertParamExists('uploadFile', 'assetData', assetData) // verify required parameter 'deviceAssetId' is not null or undefined @@ -7893,8 +7921,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration assertParamExists('uploadFile', 'fileCreatedAt', fileCreatedAt) // verify required parameter 'fileModifiedAt' is not null or undefined assertParamExists('uploadFile', 'fileModifiedAt', fileModifiedAt) - // verify required parameter 'isFavorite' is not null or undefined - assertParamExists('uploadFile', 'isFavorite', isFavorite) const localVarPath = `/asset/upload`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8068,17 +8094,19 @@ export const AssetApiFp = function(configuration?: Configuration) { }, /** * Get all AssetEntity belong to the user + * @param {number} [skip] + * @param {number} [take] * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {number} [skip] * @param {string} [updatedAfter] + * @param {string} [updatedBefore] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch, options); + async getAllAssets(skip?: number, take?: number, userId?: string, isFavorite?: boolean, isArchived?: boolean, updatedAfter?: string, updatedBefore?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8335,11 +8363,11 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} deviceId * @param {string} fileCreatedAt * @param {string} fileModifiedAt - * @param {boolean} isFavorite * @param {string} [key] * @param {string} [duration] * @param {boolean} [isArchived] * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] * @param {boolean} [isOffline] * @param {boolean} [isReadOnly] * @param {boolean} [isVisible] @@ -8349,8 +8377,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async uploadFile(assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData, options); + async uploadFile(assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, key?: string, duration?: string, isArchived?: boolean, isExternal?: boolean, isFavorite?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, libraryId?: string, livePhotoData?: File, sidecarData?: File, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, duration, isArchived, isExternal, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -8423,7 +8451,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); + return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); }, /** * Get a single asset\'s information @@ -8626,7 +8654,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ uploadFile(requestParameters: AssetApiUploadFileRequest, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.isFavorite, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(axios, basePath)); + return localVarFp.uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(axios, basePath)); }, }; }; @@ -8721,6 +8749,20 @@ export interface AssetApiDownloadFileRequest { * @interface AssetApiGetAllAssetsRequest */ export interface AssetApiGetAllAssetsRequest { + /** + * + * @type {number} + * @memberof AssetApiGetAllAssets + */ + readonly skip?: number + + /** + * + * @type {number} + * @memberof AssetApiGetAllAssets + */ + readonly take?: number + /** * * @type {string} @@ -8744,17 +8786,17 @@ export interface AssetApiGetAllAssetsRequest { /** * - * @type {number} + * @type {string} * @memberof AssetApiGetAllAssets */ - readonly skip?: number + readonly updatedAfter?: string /** * * @type {string} * @memberof AssetApiGetAllAssets */ - readonly updatedAfter?: string + readonly updatedBefore?: string /** * ETag of data already cached on the client @@ -9274,13 +9316,6 @@ export interface AssetApiUploadFileRequest { */ readonly fileModifiedAt: string - /** - * - * @type {boolean} - * @memberof AssetApiUploadFile - */ - readonly isFavorite: boolean - /** * * @type {string} @@ -9309,6 +9344,13 @@ export interface AssetApiUploadFileRequest { */ readonly isExternal?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiUploadFile + */ + readonly isFavorite?: boolean + /** * * @type {boolean} @@ -9432,7 +9474,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9681,7 +9723,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public uploadFile(requestParameters: AssetApiUploadFileRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.isFavorite, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).uploadFile(requestParameters.assetData, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.fileCreatedAt, requestParameters.fileModifiedAt, requestParameters.key, requestParameters.duration, requestParameters.isArchived, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.libraryId, requestParameters.livePhotoData, requestParameters.sidecarData, options).then((request) => request(this.axios, this.basePath)); } } @@ -10471,7 +10513,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10551,7 +10593,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { + signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath)); }, /** @@ -15062,6 +15104,51 @@ export const SystemConfigApiAxiosParamCreator = function (configuration?: Config + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {MapTheme} theme + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMapStyle: async (theme: MapTheme, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'theme' is not null or undefined + assertParamExists('getMapStyle', 'theme', theme) + const localVarPath = `/system-config/map/style.json`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (theme !== undefined) { + localVarQueryParameter['theme'] = theme; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -15181,6 +15268,16 @@ export const SystemConfigApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getConfigDefaults(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {MapTheme} theme + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getMapStyle(theme: MapTheme, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getMapStyle(theme, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -15226,6 +15323,15 @@ export const SystemConfigApiFactory = function (configuration?: Configuration, b getConfigDefaults(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getConfigDefaults(options).then((request) => request(axios, basePath)); }, + /** + * + * @param {SystemConfigApiGetMapStyleRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMapStyle(requestParameters: SystemConfigApiGetMapStyleRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getMapStyle(requestParameters.theme, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -15246,6 +15352,20 @@ export const SystemConfigApiFactory = function (configuration?: Configuration, b }; }; +/** + * Request parameters for getMapStyle operation in SystemConfigApi. + * @export + * @interface SystemConfigApiGetMapStyleRequest + */ +export interface SystemConfigApiGetMapStyleRequest { + /** + * + * @type {MapTheme} + * @memberof SystemConfigApiGetMapStyle + */ + readonly theme: MapTheme +} + /** * Request parameters for updateConfig operation in SystemConfigApi. * @export @@ -15287,6 +15407,17 @@ export class SystemConfigApi extends BaseAPI { return SystemConfigApiFp(this.configuration).getConfigDefaults(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {SystemConfigApiGetMapStyleRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SystemConfigApi + */ + public getMapStyle(requestParameters: SystemConfigApiGetMapStyleRequest, options?: AxiosRequestConfig) { + return SystemConfigApiFp(this.configuration).getMapStyle(requestParameters.theme, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index 9a534e7bd..b513ad201 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index 8997b2d52..910b3970e 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index 8058881d1..58dce4201 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index d0651f28a..e2baa279b 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.84.0 + * The version of the OpenAPI document: 1.85.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte index 0efc839ad..7093a0eeb 100644 --- a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte +++ b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte @@ -9,9 +9,9 @@ import { fade } from 'svelte/transition'; import SettingAccordion from '../setting-accordion.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte'; - import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingSwitch from '../setting-switch.svelte'; import SettingSelect from '../setting-select.svelte'; + import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; export let config: SystemConfigDto; // this is the config that is being edited export let disabled = false; @@ -34,7 +34,8 @@ ...current, map: { enabled: config.map.enabled, - tileUrl: config.map.tileUrl, + lightStyle: config.map.lightStyle, + darkStyle: config.map.darkStyle, }, reverseGeocoding: { enabled: config.reverseGeocoding.enabled, @@ -95,12 +96,19 @@ + diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte new file mode 100644 index 000000000..1a3e43892 --- /dev/null +++ b/web/src/lib/components/album-page/album-options.svelte @@ -0,0 +1,76 @@ + + + dispatch('close')}> +
+
+
+
+

Options

+
+ dispatch('close')} /> +
+
+ +
+
+

SHARING

+
+ dispatch('toggleEnableActivity')} + /> +
+
+
+
PEOPLE
+
+ +
+
+ +
+
{`${user.firstName} ${user.lastName}`}
+
Owner
+
+ {#each album.sharedUsers as user (user.id)} +
+
+ +
+
{`${user.firstName} ${user.lastName}`}
+
+ {/each} +
+
+
+
+
+
+
diff --git a/web/src/lib/components/asset-viewer/activity-status.svelte b/web/src/lib/components/asset-viewer/activity-status.svelte new file mode 100644 index 000000000..47e29ff97 --- /dev/null +++ b/web/src/lib/components/asset-viewer/activity-status.svelte @@ -0,0 +1,34 @@ + + +
+ + +
diff --git a/web/src/lib/components/asset-viewer/activity-viewer.svelte b/web/src/lib/components/asset-viewer/activity-viewer.svelte index 234f708f7..f8663eceb 100644 --- a/web/src/lib/components/asset-viewer/activity-viewer.svelte +++ b/web/src/lib/components/asset-viewer/activity-viewer.svelte @@ -1,9 +1,9 @@ diff --git a/web/src/lib/components/shared-components/control-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte index a7c7da6ba..f8d5627fa 100644 --- a/web/src/lib/components/shared-components/control-app-bar.svelte +++ b/web/src/lib/components/shared-components/control-app-bar.svelte @@ -40,7 +40,7 @@ }); -
+
- import { onDestroy, onMount } from 'svelte'; - import { Control, type ControlPosition } from 'leaflet'; - import { getMapContext } from './map.svelte'; - - export let position: ControlPosition | undefined = undefined; - let className: string | undefined = undefined; - export { className as class }; - - let control: Control; - let target: HTMLDivElement; - - const map = getMapContext(); - - onMount(() => { - const ControlClass = Control.extend({ - position, - onAdd: () => target, - }); - - control = new ControlClass().addTo(map); - }); - - onDestroy(() => { - control.remove(); - }); - - $: if (control && position) { - control.setPosition(position); - } - - -
- -
diff --git a/web/src/lib/components/shared-components/leaflet/index.ts b/web/src/lib/components/shared-components/leaflet/index.ts deleted file mode 100644 index 896c976e2..000000000 --- a/web/src/lib/components/shared-components/leaflet/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as Control } from './control.svelte'; -export { default as Map } from './map.svelte'; -export { default as AssetMarkerCluster } from './marker-cluster/asset-marker-cluster.svelte'; -export { default as Marker } from './marker.svelte'; -export { default as TileLayer } from './tile-layer.svelte'; diff --git a/web/src/lib/components/shared-components/leaflet/map.svelte b/web/src/lib/components/shared-components/leaflet/map.svelte deleted file mode 100644 index f0d5fecc8..000000000 --- a/web/src/lib/components/shared-components/leaflet/map.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - -
- {#if map} - - {/if} -
- - diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css deleted file mode 100644 index 73f0abc11..000000000 --- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css +++ /dev/null @@ -1,31 +0,0 @@ -.asset-marker-icon { - @apply rounded-full; - @apply object-cover; - @apply border; - @apply border-immich-primary; - @apply transition-all; - box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px, rgba(0, 0, 0, 0.07) 0px 4px 8px, - rgba(0, 0, 0, 0.07) 0px 8px 16px, rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px; -} - -.marker-cluster-icon { - @apply h-full; - @apply w-full; - @apply flex; - @apply justify-center; - @apply items-center; - @apply rounded-full; - @apply font-bold; - @apply bg-violet-50; - @apply border; - @apply border-immich-primary; - @apply text-immich-primary; - box-shadow: rgba(5, 5, 122, 0.12) 0px 2px 4px 0px, rgba(4, 4, 230, 0.32) 0px 2px 16px 0px; -} - -.dark .map-dark .marker-cluster-icon { - @apply bg-blue-200; - @apply text-black; - @apply border-blue-200; - box-shadow: none; -} diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte deleted file mode 100644 index 4d408a5a7..000000000 --- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte +++ /dev/null @@ -1,102 +0,0 @@ - - - diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts deleted file mode 100644 index c5ded9c93..000000000 --- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { api, MapMarkerResponseDto } from '@api'; -import { Icon, Map, Marker } from 'leaflet'; - -export default class AssetMarker extends Marker { - id: string; - private iconCreated = false; - - constructor(marker: MapMarkerResponseDto) { - super([marker.lat, marker.lon]); - this.id = marker.id; - } - - onAdd(map: Map) { - // Set icon when the marker gets actually added to the map. This only - // gets called for individual assets and when selecting a cluster, so - // creating an icon for every marker in advance is pretty wasteful. - if (!this.iconCreated) { - this.iconCreated = true; - this.setIcon(this.getIcon()); - } - - return super.onAdd(map); - } - - getIcon() { - return new Icon({ - iconUrl: api.getAssetThumbnailUrl(this.id), - iconRetinaUrl: api.getAssetThumbnailUrl(this.id), - iconSize: [60, 60], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - tooltipAnchor: [16, -28], - shadowSize: [41, 41], - className: 'asset-marker-icon', - }); - } -} diff --git a/web/src/lib/components/shared-components/leaflet/marker.svelte b/web/src/lib/components/shared-components/leaflet/marker.svelte deleted file mode 100644 index 8731e66d0..000000000 --- a/web/src/lib/components/shared-components/leaflet/marker.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/web/src/lib/components/shared-components/leaflet/tile-layer.svelte b/web/src/lib/components/shared-components/leaflet/tile-layer.svelte deleted file mode 100644 index 18038559c..000000000 --- a/web/src/lib/components/shared-components/leaflet/tile-layer.svelte +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte new file mode 100644 index 000000000..6501e89ea --- /dev/null +++ b/web/src/lib/components/shared-components/map/map.svelte @@ -0,0 +1,146 @@ + + +{#await style then style} + + + {#if !simplified} + + + + + {/if} + {#if showSettingsModal !== undefined} + + + (showSettingsModal = true)}> + + + {/if} + { + return asFeature(marker); + }), + }} + id="geojson" + cluster={{ maxZoom: 14, radius: 500 }} + > + { + handleClusterClick(event.detail.feature.properties.cluster_id, map); + }} + > +
+ {feature.properties?.point_count} +
+
+ { + $$slots.popup || handleAssetClick(event.detail.feature.properties.id, map); + }} + > + {`Image + {#if $$slots.popup} + + + + {/if} + +
+
+{/await} diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 222654c59..580277fba 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -93,7 +93,7 @@ {#if $assetStore.timelineHeight > height}
(0); + +export const setNumberOfComments = (number: number) => { + numberOfComments.set(number); +}; + +export const updateNumberOfComments = (addOrRemove: 1 | -1) => { + numberOfComments.update((n) => n + addOrRemove); +}; diff --git a/web/src/lib/stores/server-config.store.ts b/web/src/lib/stores/server-config.store.ts index 723ad49bd..5d9ae027a 100644 --- a/web/src/lib/stores/server-config.store.ts +++ b/web/src/lib/stores/server-config.store.ts @@ -24,7 +24,6 @@ export type ServerConfig = ServerConfigDto & { loaded: boolean }; export const serverConfig = writable({ loaded: false, oauthButtonText: '', - mapTileUrl: '', loginPageMessage: '', trashDays: 30, isInitialized: false, diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index 1697af522..09eb66e95 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -35,7 +35,7 @@ import { downloadArchive } from '$lib/utils/asset-utils'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { handleError } from '$lib/utils/handle-error'; - import { UserResponseDto, api } from '@api'; + import { ActivityResponseDto, ReactionLevel, ReactionType, UserResponseDto, api } from '@api'; import Icon from '$lib/components/elements/icon.svelte'; import type { PageData } from './$types'; import { clickOutside } from '$lib/utils/click-outside'; @@ -45,11 +45,17 @@ mdiDotsVertical, mdiArrowLeft, mdiFileImagePlusOutline, - mdiShareVariantOutline, - mdiDeleteOutline, mdiFolderDownloadOutline, mdiLink, + mdiShareVariantOutline, + mdiDeleteOutline, } from '@mdi/js'; + import { onMount } from 'svelte'; + import { fly } from 'svelte/transition'; + import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte'; + import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte'; + import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store'; + import AlbumOptions from '$lib/components/album-page/album-options.svelte'; export let data: PageData; @@ -59,6 +65,12 @@ let album = data.album; $: album = data.album; + $: { + if (!album.isActivityEnabled && $numberOfComments === 0) { + isShowActivity = false; + } + } + enum ViewMode { CONFIRM_DELETE = 'confirm-delete', LINK_SHARING = 'link-sharing', @@ -68,6 +80,7 @@ ALBUM_OPTIONS = 'album-options', VIEW_USERS = 'view-users', VIEW = 'view', + OPTIONS = 'options', } let backUrl: string = AppRoute.ALBUMS; @@ -77,6 +90,12 @@ let isCreatingSharedAlbum = false; let currentAlbumName = ''; let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 }; + let isShowActivity = false; + let isLiked: ActivityResponseDto | null = null; + let reactions: ActivityResponseDto[] = []; + let user = data.user; + let globalWidth: number; + let assetGridWidth: number; const assetStore = new AssetStore({ albumId: album.id }); const assetInteractionStore = createAssetInteractionStore(); @@ -89,6 +108,15 @@ $: isOwned = data.user.id == album.ownerId; $: isAllUserOwned = Array.from($selectedAssets).every((asset) => asset.ownerId === data.user.id); $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); + $: { + if (isShowActivity) { + assetGridWidth = globalWidth - (globalWidth < 768 ? 360 : 460); + } else { + assetGridWidth = globalWidth; + } + } + $: showActivityStatus = + album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0); afterNavigate(({ from }) => { assetViewingStore.showAssetViewer(false); @@ -110,6 +138,81 @@ } }); + const handleToggleEnableActivity = async () => { + try { + const { data } = await api.albumApi.updateAlbumInfo({ + id: album.id, + updateAlbumDto: { + isActivityEnabled: !album.isActivityEnabled, + }, + }); + album = data; + notificationController.show({ + type: NotificationType.Info, + message: `Activity is ${album.isActivityEnabled ? 'enabled' : 'disabled'}`, + }); + } catch (error) { + handleError(error, `Can't ${!album.isActivityEnabled ? 'enable' : 'disable'} activity`); + } + }; + + const handleFavorite = async () => { + try { + if (isLiked) { + const activityId = isLiked.id; + await api.activityApi.deleteActivity({ id: activityId }); + reactions = reactions.filter((reaction) => reaction.id !== activityId); + isLiked = null; + } else { + const { data } = await api.activityApi.createActivity({ + activityCreateDto: { albumId: album.id, type: ReactionType.Like }, + }); + isLiked = data; + reactions = [...reactions, isLiked]; + } + } catch (error) { + handleError(error, "Can't change favorite for asset"); + } + }; + + const getFavorite = async () => { + if (user) { + try { + const { data } = await api.activityApi.getActivities({ + userId: user.id, + albumId: album.id, + type: ReactionType.Like, + level: ReactionLevel.Album, + }); + if (data.length > 0) { + isLiked = data[0]; + } + } catch (error) { + handleError(error, "Can't get Favorite"); + } + } + }; + + const getNumberOfComments = async () => { + try { + const { data } = await api.activityApi.getActivityStatistics({ albumId: album.id }); + setNumberOfComments(data.comments); + } catch (error) { + handleError(error, "Can't get number of comments"); + } + }; + + const handleOpenAndCloseActivityTab = () => { + isShowActivity = !isShowActivity; + }; + + onMount(async () => { + if (album.sharedUsers.length > 0) { + getFavorite(); + getNumberOfComments(); + } + }); + const handleStartSlideshow = async () => { const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; if (asset) { @@ -299,6 +402,7 @@ }, }); currentAlbumName = album.albumName; + notificationController.show({ type: NotificationType.Info, message: 'New album name has been saved' }); } catch (error) { handleError(error, 'Unable to update album name'); } @@ -321,239 +425,279 @@ }; -
- {#if $isMultiSelectState} - assetInteractionStore.clearMultiselect()}> - - - - - - - - {#if isAllUserOwned} - - {/if} - - - {#if isOwned || isAllUserOwned} - handleRemoveAssets(assetIds)} /> - {/if} - {#if isAllUserOwned} - assetStore.removeAsset(assetId)} /> - {/if} - - - {:else} - {#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS} - goto(backUrl)}> - - (viewMode = ViewMode.SELECT_ASSETS)} - icon={mdiFileImagePlusOutline} - /> - - {#if isOwned} - (viewMode = ViewMode.SELECT_USERS)} - icon={mdiShareVariantOutline} - /> - (viewMode = ViewMode.CONFIRM_DELETE)} - icon={mdiDeleteOutline} - /> +
+
+ {#if $isMultiSelectState} + assetInteractionStore.clearMultiselect()}> + + + + + + + + {#if isAllUserOwned} + {/if} - - {#if album.assetCount > 0} - + + + {#if isOwned || isAllUserOwned} + handleRemoveAssets(assetIds)} /> + {/if} + {#if isAllUserOwned} + assetStore.removeAsset(assetId)} /> + {/if} + + + {:else} + {#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS} + goto(backUrl)}> + + (viewMode = ViewMode.SELECT_ASSETS)} + icon={mdiFileImagePlusOutline} + /> {#if isOwned} -
(viewMode = ViewMode.VIEW)}> - - {#if viewMode === ViewMode.ALBUM_OPTIONS} - - {#if album.assetCount !== 0} - - {/if} - (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" /> - - {/if} - -
+ (viewMode = ViewMode.SELECT_USERS)} + icon={mdiShareVariantOutline} + /> + (viewMode = ViewMode.CONFIRM_DELETE)} + icon={mdiDeleteOutline} + /> {/if} - {/if} - {#if isCreatingSharedAlbum && album.sharedUsers.length === 0} - - {/if} -
-
- {/if} - - {#if viewMode === ViewMode.SELECT_ASSETS} - - -

- {#if $timelineSelected.size == 0} - Add to album - {:else} - {$timelineSelected.size.toLocaleString($locale)} selected - {/if} -

-
- - - - - -
- {/if} - - {#if viewMode === ViewMode.SELECT_THUMBNAIL} - (viewMode = ViewMode.VIEW)}> - Select Album Cover - - {/if} - {/if} -
- -
- {#if viewMode === ViewMode.SELECT_ASSETS} - - {:else} - 0} - isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL} - singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL} - on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)} - on:escape={handleEscape} - > - {#if viewMode !== ViewMode.SELECT_THUMBNAIL} - -
- e.key == 'Enter' && titleInput.blur()} - on:blur={handleUpdateName} - class="w-[99%] border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned - ? 'hover:border-gray-400' - : 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray" - type="text" - bind:value={album.albumName} - disabled={!isOwned} - bind:this={titleInput} - title="Edit Title" - /> - - - {#if album.assetCount > 0} - -

{getDateRange()}

-

·

-

{album.assetCount} items

-
- {/if} - - - {#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)} -
- - {#if album.hasSharedLink && isOwned} - (viewMode = ViewMode.LINK_SHARING)} - /> - {/if} - - - - - - {#each album.sharedUsers as user (user.id)} - - {/each} + {#if album.assetCount > 0} + {#if isOwned} - (viewMode = ViewMode.SELECT_USERS)} - title="Add more users" - /> +
(viewMode = ViewMode.VIEW)}> + + {#if viewMode === ViewMode.ALBUM_OPTIONS} + + {#if album.assetCount !== 0} + + {/if} + (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" /> + (viewMode = ViewMode.OPTIONS)} text="Options" /> + + {/if} + +
{/if} -
- {/if} + {/if} - - {#if isOwned || album.description} - - {/if} -
+ {#if isCreatingSharedAlbum && album.sharedUsers.length === 0} + + {/if} + + {/if} - {#if album.assetCount === 0} -
-
-

ADD PHOTOS

+ {#if viewMode === ViewMode.SELECT_ASSETS} + + +

+ {#if $timelineSelected.size === 0} + Add to album + {:else} + {$timelineSelected.size.toLocaleString($locale)} selected + {/if} +

+
+ + -
-
+ + + {/if} -
+ + {#if viewMode === ViewMode.SELECT_THUMBNAIL} + (viewMode = ViewMode.VIEW)}> + Select Album Cover + + {/if} + {/if} + +
+ {#if viewMode === ViewMode.SELECT_ASSETS} + + {:else} + 0} + isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL} + singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL} + on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)} + on:escape={handleEscape} + > + {#if viewMode !== ViewMode.SELECT_THUMBNAIL} + +
+ e.key === 'Enter' && titleInput.blur()} + on:blur={handleUpdateName} + class="w-[99%] border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned + ? 'hover:border-gray-400' + : 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray" + type="text" + bind:value={album.albumName} + disabled={!isOwned} + bind:this={titleInput} + title="Edit Title" + /> + + + {#if album.assetCount > 0} + +

{getDateRange()}

+

·

+

{album.assetCount} items

+
+ {/if} + + + {#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)} +
+ + {#if album.hasSharedLink && isOwned} + (viewMode = ViewMode.LINK_SHARING)} + /> + {/if} + + + + + + {#each album.sharedUsers as user (user.id)} + + {/each} + + {#if isOwned} + (viewMode = ViewMode.SELECT_USERS)} + title="Add more users" + /> + {/if} +
+ {/if} + + + {#if isOwned || album.description} + + {/if} +
+ {/if} + + {#if album.assetCount === 0} +
+
+

ADD PHOTOS

+ +
+
+ {/if} +
+ {/if} + + {#if showActivityStatus} +
+ +
+ {/if} +
+
+ {#if album.sharedUsers.length > 0 && album && isShowActivity && user && !$showAssetViewer} +
+
+ updateNumberOfComments(1)} + on:deleteComment={() => updateNumberOfComments(-1)} + on:deleteLike={() => (isLiked = null)} + on:close={handleOpenAndCloseActivityTab} + /> +
+
{/if} - - +
{#if viewMode === ViewMode.SELECT_USERS} {/if} +{#if viewMode === ViewMode.OPTIONS} + (viewMode = ViewMode.VIEW)} + on:toggleEnableActivity={handleToggleEnableActivity} + on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)} + /> +{/if} + {#if isEditingDescription} { loadMapMarkers().then((data) => (mapMarkers = data)); - import('$lib/components/shared-components/leaflet').then((data) => (leaflet = data)); }); onDestroy(() => { @@ -84,10 +81,10 @@ } } - function onViewAssets(assetIds: string[], activeAssetIndex: number) { - assetViewingStore.setAssetId(assetIds[activeAssetIndex]); + function onViewAssets(assetIds: string[]) { + assetViewingStore.setAssetId(assetIds[0]); viewingAssets = assetIds; - viewingAssetCursor = activeAssetIndex; + viewingAssetCursor = 0; } function navigateNext() { @@ -106,44 +103,9 @@ {#if $featureFlags.loaded && $featureFlags.map}
- {#if leaflet} - {@const { Map, TileLayer, AssetMarkerCluster, Control } = leaflet} - - OpenStreetMap', - }} - /> - onViewAssets(detail.assetIds, detail.activeAssetIndex)} - /> - - - - - {/if} -
-
- + onViewAssets(event.detail)} /> +
{#if $showAssetViewer} assetViewingStore.showAssetViewer(false)} + isShared={false} /> {/if} diff --git a/web/src/test-data/factories/album-factory.ts b/web/src/test-data/factories/album-factory.ts index d87a09626..d457a5455 100644 --- a/web/src/test-data/factories/album-factory.ts +++ b/web/src/test-data/factories/album-factory.ts @@ -17,4 +17,5 @@ export const albumFactory = Sync.makeFactory({ shared: false, sharedUsers: [], hasSharedLink: false, + isActivityEnabled: true, }); diff --git a/web/vite.config.js b/web/vite.config.js index 2e9e32a03..4406c0ad0 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -24,7 +24,7 @@ const config = { }, plugins: [sveltekit()], optimizeDeps: { - entries: ['src/**/*.{svelte, ts, html}'], + entries: ['src/**/*.{svelte,ts,html}'], }, };