-
{
- const thumbnail = JSON.parse(marker.thumbnail)
+ const thumbnail = JSON.parse(marker.thumbnail) as { url: string }
const presentMedia = () => {
dispatchMarkerMedia({
diff --git a/ui/src/Pages/PlacesPage/PlacesPage.tsx b/ui/src/Pages/PlacesPage/PlacesPage.tsx
index b41f4328..e34132bc 100644
--- a/ui/src/Pages/PlacesPage/PlacesPage.tsx
+++ b/ui/src/Pages/PlacesPage/PlacesPage.tsx
@@ -7,7 +7,7 @@ import styled from 'styled-components'
import Layout from '../../components/layout/Layout'
import { registerMediaMarkers } from '../../components/mapbox/mapboxHelperFunctions'
import useMapboxMap from '../../components/mapbox/MapboxMap'
-import { urlPresentModeSetupHook } from '../../components/photoGallery/photoGalleryReducer'
+import { urlPresentModeSetupHook } from '../../components/photoGallery/mediaGalleryReducer'
import MapPresentMarker from './MapPresentMarker'
import { PlacesAction, placesReducer } from './placesReducer'
import { mediaGeoJson } from './__generated__/mediaGeoJson'
@@ -108,7 +108,7 @@ const configureMapbox =
map.addSource('media', {
type: 'geojson',
- data: mapboxData?.myMediaGeoJson,
+ data: mapboxData?.myMediaGeoJson as never,
cluster: true,
clusterRadius: 50,
clusterProperties: {
diff --git a/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts b/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts
index 5f6d5da5..f1306ef6 100644
--- a/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts
+++ b/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts
@@ -11,5 +11,5 @@ export interface mediaGeoJson {
/**
* Get media owned by the logged in user, returned in GeoJson format
*/
- myMediaGeoJson: any
+ myMediaGeoJson: Any
}
diff --git a/ui/src/Pages/PlacesPage/placesReducer.ts b/ui/src/Pages/PlacesPage/placesReducer.ts
index a92de081..08be8a27 100644
--- a/ui/src/Pages/PlacesPage/placesReducer.ts
+++ b/ui/src/Pages/PlacesPage/placesReducer.ts
@@ -1,11 +1,11 @@
import { PresentMarker } from './PlacesPage'
import {
- PhotoGalleryState,
+ MediaGalleryState,
PhotoGalleryAction,
- photoGalleryReducer,
-} from './../../components/photoGallery/photoGalleryReducer'
+ mediaGalleryReducer,
+} from '../../components/photoGallery/mediaGalleryReducer'
-export interface PlacesState extends PhotoGalleryState {
+export interface PlacesState extends MediaGalleryState {
presentMarker?: PresentMarker
}
@@ -36,6 +36,6 @@ export function placesReducer(
}
}
default:
- return photoGalleryReducer(state, action)
+ return mediaGalleryReducer(state, action)
}
}
diff --git a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx
new file mode 100644
index 00000000..42b42da8
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import { MockedProvider } from '@apollo/client/testing'
+
+import { render, screen } from '@testing-library/react'
+
+import {
+ CONCURRENT_WORKERS_QUERY,
+ SET_CONCURRENT_WORKERS_MUTATION,
+ ScannerConcurrentWorkers,
+} from './ScannerConcurrentWorkers'
+
+test('load ScannerConcurrentWorkers', () => {
+ const graphqlMocks = [
+ {
+ request: {
+ query: CONCURRENT_WORKERS_QUERY,
+ },
+ result: {
+ data: {
+ siteInfo: { concurrentWorkers: 3 },
+ },
+ },
+ },
+ {
+ request: {
+ query: SET_CONCURRENT_WORKERS_MUTATION,
+ variables: {
+ workers: '1',
+ },
+ },
+ result: {
+ data: {},
+ },
+ },
+ ]
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Scanner concurrent workers')).toBeInTheDocument()
+})
diff --git a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.tsx b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.tsx
index 01e8d477..8a10dcb5 100644
--- a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.tsx
+++ b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.tsx
@@ -9,7 +9,7 @@ import {
} from './__generated__/setConcurrentWorkers'
import { TextField } from '../../primitives/form/Input'
-const CONCURRENT_WORKERS_QUERY = gql`
+export const CONCURRENT_WORKERS_QUERY = gql`
query concurrentWorkersQuery {
siteInfo {
concurrentWorkers
@@ -17,15 +17,18 @@ const CONCURRENT_WORKERS_QUERY = gql`
}
`
-const SET_CONCURRENT_WORKERS_MUTATION = gql`
+export const SET_CONCURRENT_WORKERS_MUTATION = gql`
mutation setConcurrentWorkers($workers: Int!) {
setScannerConcurrentWorkers(workers: $workers)
}
`
-const ScannerConcurrentWorkers = () => {
+export const ScannerConcurrentWorkers = () => {
const { t } = useTranslation()
+ const workerAmountServerValue = useRef(null)
+ const [workerAmount, setWorkerAmount] = useState(0)
+
const workerAmountQuery = useQuery(
CONCURRENT_WORKERS_QUERY,
{
@@ -41,9 +44,6 @@ const ScannerConcurrentWorkers = () => {
setConcurrentWorkersVariables
>(SET_CONCURRENT_WORKERS_MUTATION)
- const workerAmountServerValue = useRef(null)
- const [workerAmount, setWorkerAmount] = useState(0)
-
const updateWorkerAmount = (workerAmount: number) => {
if (workerAmountServerValue.current != workerAmount) {
workerAmountServerValue.current = workerAmount
@@ -86,5 +86,3 @@ const ScannerConcurrentWorkers = () => {
)
}
-
-export default ScannerConcurrentWorkers
diff --git a/ui/src/Pages/SettingsPage/ScannerSection.tsx b/ui/src/Pages/SettingsPage/ScannerSection.tsx
index cced4376..733b0077 100644
--- a/ui/src/Pages/SettingsPage/ScannerSection.tsx
+++ b/ui/src/Pages/SettingsPage/ScannerSection.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import { useMutation, gql } from '@apollo/client'
import PeriodicScanner from './PeriodicScanner'
-import ScannerConcurrentWorkers from './ScannerConcurrentWorkers'
+import { ScannerConcurrentWorkers } from './ScannerConcurrentWorkers'
import { SectionTitle, InputLabelDescription } from './SettingsPage'
import { useTranslation } from 'react-i18next'
import { scanAllMutation } from './__generated__/scanAllMutation'
diff --git a/ui/src/Pages/SettingsPage/SettingsPage.tsx b/ui/src/Pages/SettingsPage/SettingsPage.tsx
index 9da1757d..45301987 100644
--- a/ui/src/Pages/SettingsPage/SettingsPage.tsx
+++ b/ui/src/Pages/SettingsPage/SettingsPage.tsx
@@ -5,6 +5,7 @@ import { useIsAdmin } from '../../components/routes/AuthorizedRoute'
import Layout from '../../components/layout/Layout'
import ScannerSection from './ScannerSection'
import UserPreferences from './UserPreferences'
+import ThumbnailPreferences from './ThumbnailPreferences'
import UsersTable from './Users/UsersTable'
import VersionInfo from './VersionInfo'
import classNames from 'classnames'
@@ -46,6 +47,7 @@ const SettingsPage = () => {
<>
+
>
)}
diff --git a/ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx b/ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx
new file mode 100644
index 00000000..879ba166
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import { MockedProvider } from '@apollo/client/testing'
+
+import { render, screen } from '@testing-library/react'
+
+import { ThumbnailFilter } from '../../__generated__/globalTypes'
+
+import ThumbnailPreferences, {
+ THUMBNAIL_METHOD_QUERY,
+ SET_THUMBNAIL_METHOD_MUTATION,
+} from './ThumbnailPreferences'
+
+test('load ThumbnailPreferences', () => {
+ const graphqlMocks = [
+ {
+ request: {
+ query: THUMBNAIL_METHOD_QUERY,
+ },
+ result: {
+ data: {
+ siteInfo: { method: ThumbnailFilter.NearestNeighbor },
+ },
+ },
+ },
+ {
+ request: {
+ query: SET_THUMBNAIL_METHOD_MUTATION,
+ variables: {
+ method: ThumbnailFilter.Lanczos,
+ },
+ },
+ result: {
+ data: {},
+ },
+ },
+ ]
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Downsampling method')).toBeInTheDocument()
+})
diff --git a/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx b/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx
new file mode 100644
index 00000000..520a794c
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx
@@ -0,0 +1,135 @@
+import { gql } from '@apollo/client'
+import React, { useRef, useState } from 'react'
+import { useMutation, useQuery } from '@apollo/client'
+import {
+ SectionTitle,
+ InputLabelDescription,
+ InputLabelTitle,
+} from './SettingsPage'
+import { useTranslation } from 'react-i18next'
+import { ThumbnailFilter } from '../../__generated__/globalTypes'
+import { thumbnailMethodQuery } from './__generated__/thumbnailMethodQuery'
+import {
+ setThumbnailMethodMutation,
+ setThumbnailMethodMutationVariables,
+} from './__generated__/setThumbnailMethodMutation'
+import Dropdown, { DropdownItem } from '../../primitives/form/Dropdown'
+import Loader from '../../primitives/Loader'
+
+export const THUMBNAIL_METHOD_QUERY = gql`
+ query thumbnailMethodQuery {
+ siteInfo {
+ thumbnailMethod
+ }
+ }
+`
+
+export const SET_THUMBNAIL_METHOD_MUTATION = gql`
+ mutation setThumbnailMethodMutation($method: ThumbnailFilter!) {
+ setThumbnailDownsampleMethod(method: $method)
+ }
+`
+
+const ThumbnailPreferences = () => {
+ const { t } = useTranslation()
+
+ const downsampleMethodServerValue = useRef
(null)
+ const [downsampleMethod, setDownsampleMethod] = useState(0)
+
+ const downsampleMethodQuery = useQuery(
+ THUMBNAIL_METHOD_QUERY,
+ {
+ onCompleted(data) {
+ setDownsampleMethod(data.siteInfo.thumbnailMethod)
+ downsampleMethodServerValue.current = data.siteInfo.thumbnailMethod
+ },
+ }
+ )
+
+ const [setDownsampleMutation, downsampleMutationData] = useMutation<
+ setThumbnailMethodMutation,
+ setThumbnailMethodMutationVariables
+ >(SET_THUMBNAIL_METHOD_MUTATION)
+
+ const updateDownsampleMethod = (downsampleMethod: number) => {
+ if (downsampleMethodServerValue.current != downsampleMethod) {
+ downsampleMethodServerValue.current = downsampleMethod
+ setDownsampleMutation({
+ variables: {
+ method: downsampleMethod,
+ },
+ })
+ }
+ }
+
+ const methodItems: DropdownItem[] = [
+ {
+ label: t(
+ 'settings.thumbnails.method.filter.nearest_neighbor',
+ 'Nearest Neighbor (default)'
+ ),
+ value: ThumbnailFilter.NearestNeighbor,
+ },
+ {
+ label: t('settings.thumbnails.method.filter.box', 'Box'),
+ value: ThumbnailFilter.Box,
+ },
+ {
+ label: t('settings.thumbnails.method.filter.linear', 'Linear'),
+ value: ThumbnailFilter.Linear,
+ },
+ {
+ label: t(
+ 'settings.thumbnails.method.filter.mitchell_netravali',
+ 'Mitchell-Netravali'
+ ),
+ value: ThumbnailFilter.MitchellNetravali,
+ },
+ {
+ label: t('settings.thumbnails.method.filter.catmull_rom', 'Catmull-Rom'),
+ value: ThumbnailFilter.CatmullRom,
+ },
+ {
+ label: t(
+ 'settings.thumbnails.method.filter.Lanczos',
+ 'Lanczos (highest quality)'
+ ),
+ value: ThumbnailFilter.Lanczos,
+ },
+ ]
+
+ return (
+
+
+ {t('settings.thumbnails.title', 'Thumbnail preferences')}
+
+
+ {
+ setDownsampleMethod(value)
+ updateDownsampleMethod(value)
+ }}
+ />
+
+
+ )
+}
+
+export default ThumbnailPreferences
diff --git a/ui/src/Pages/SettingsPage/UserPreferences.tsx b/ui/src/Pages/SettingsPage/UserPreferences.tsx
index 79e79849..6cf67957 100644
--- a/ui/src/Pages/SettingsPage/UserPreferences.tsx
+++ b/ui/src/Pages/SettingsPage/UserPreferences.tsx
@@ -47,6 +47,12 @@ const languagePreferences = [
flag: 'pt',
value: LanguageTranslation.Portuguese,
},
+ {
+ key: 13,
+ label: 'Euskara',
+ flag: 'eu',
+ value: LanguageTranslation.Basque,
+ },
]
const themePreferences = (t: TranslationFn) => [
diff --git a/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx b/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx
index eadd6437..0ffabb06 100644
--- a/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx
+++ b/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx
@@ -33,8 +33,8 @@ const gqlMock = [
]
test('Add user with username and path', async () => {
- const userAdded = jest.fn()
- const setShow = jest.fn()
+ const userAdded = vi.fn()
+ const setShow = vi.fn()
render(
@@ -62,8 +62,8 @@ test('Add user with username and path', async () => {
})
test('Add user with only username', async () => {
- const userAdded = jest.fn()
- const setShow = jest.fn()
+ const userAdded = vi.fn()
+ const setShow = vi.fn()
render(
diff --git a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
index 4ff90361..ea35f1e6 100644
--- a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
+++ b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
@@ -4,6 +4,11 @@ import { useTranslation } from 'react-i18next'
import Checkbox from '../../../primitives/form/Checkbox'
import { TextField, Button, ButtonGroup } from '../../../primitives/form/Input'
import { TableRow, TableCell } from '../../../primitives/Table'
+import { createUser, createUserVariables } from './__generated__/createUser'
+import {
+ userAddRootPath,
+ userAddRootPathVariables,
+} from './__generated__/userAddRootPath'
export const CREATE_USER_MUTATION = gql`
mutation createUser($username: String!, $admin: Boolean!) {
@@ -46,35 +51,35 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
onUserAdded()
}
- const [addRootPath, { loading: addRootPathLoading }] = useMutation(
- USER_ADD_ROOT_PATH_MUTATION,
- {
- onCompleted: () => {
- finished()
- },
- onError: () => {
- finished()
- },
- }
- )
+ const [addRootPath, { loading: addRootPathLoading }] = useMutation<
+ userAddRootPath,
+ userAddRootPathVariables
+ >(USER_ADD_ROOT_PATH_MUTATION, {
+ onCompleted: () => {
+ finished()
+ },
+ onError: () => {
+ finished()
+ },
+ })
- const [createUser, { loading: createUserLoading }] = useMutation(
- CREATE_USER_MUTATION,
- {
- onCompleted: ({ createUser: { id } }) => {
- if (state.rootPath) {
- addRootPath({
- variables: {
- id: id,
- rootPath: state.rootPath,
- },
- })
- } else {
- finished()
- }
- },
- }
- )
+ const [createUser, { loading: createUserLoading }] = useMutation<
+ createUser,
+ createUserVariables
+ >(CREATE_USER_MUTATION, {
+ onCompleted: ({ createUser: { id } }) => {
+ if (state.rootPath) {
+ addRootPath({
+ variables: {
+ id: id,
+ rootPath: state.rootPath,
+ },
+ })
+ } else {
+ finished()
+ }
+ },
+ })
const loading = addRootPathLoading || createUserLoading
diff --git a/ui/src/Pages/SettingsPage/Users/UserChangePassword.tsx b/ui/src/Pages/SettingsPage/Users/UserChangePassword.tsx
index 8ba95390..76f18da8 100644
--- a/ui/src/Pages/SettingsPage/Users/UserChangePassword.tsx
+++ b/ui/src/Pages/SettingsPage/Users/UserChangePassword.tsx
@@ -40,7 +40,7 @@ const ChangePasswordModal = ({
title={t('settings.users.password_reset.title', 'Change password')}
description={
- Change password for {{ username: user.username }}
+ Change password for {user.username}
}
actions={[
diff --git a/ui/src/Pages/SettingsPage/Users/UserRow.tsx b/ui/src/Pages/SettingsPage/Users/UserRow.tsx
index 8ab8a9da..9f0573a3 100644
--- a/ui/src/Pages/SettingsPage/Users/UserRow.tsx
+++ b/ui/src/Pages/SettingsPage/Users/UserRow.tsx
@@ -67,7 +67,7 @@ export type UserRowChildProps = {
export type UserRowProps = {
user: settingsUsersQuery_user
- refetchUsers(): void
+ refetchUsers: () => void
}
const UserRow = ({ user, refetchUsers }: UserRowProps) => {
diff --git a/ui/src/Pages/SettingsPage/Users/__generated__/scanUser.ts b/ui/src/Pages/SettingsPage/Users/__generated__/scanUser.ts
index 37f0c92b..55a73d14 100644
--- a/ui/src/Pages/SettingsPage/Users/__generated__/scanUser.ts
+++ b/ui/src/Pages/SettingsPage/Users/__generated__/scanUser.ts
@@ -8,17 +8,17 @@
// ====================================================
export interface scanUser_scanUser {
- __typename: "ScannerResult";
- success: boolean;
+ __typename: 'ScannerResult'
+ success: boolean
}
export interface scanUser {
/**
* Scan a single user for new media
*/
- scanUser: scanUser_scanUser;
+ scanUser: scanUser_scanUser
}
export interface scanUserVariables {
- userId: string;
+ userId: string
}
diff --git a/ui/src/Pages/SettingsPage/VersionInfo.tsx b/ui/src/Pages/SettingsPage/VersionInfo.tsx
index 5fc3c853..b673ca17 100644
--- a/ui/src/Pages/SettingsPage/VersionInfo.tsx
+++ b/ui/src/Pages/SettingsPage/VersionInfo.tsx
@@ -7,10 +7,10 @@ import {
SectionTitle,
} from './SettingsPage'
-const VERSION = process.env.REACT_APP_BUILD_VERSION ?? 'undefined'
-const BUILD_DATE = process.env.REACT_APP_BUILD_DATE ?? 'undefined'
+const VERSION = import.meta.env.REACT_APP_BUILD_VERSION ?? 'undefined'
+const BUILD_DATE = import.meta.env.REACT_APP_BUILD_DATE ?? 'undefined'
-const COMMIT_SHA = process.env.REACT_APP_BUILD_COMMIT_SHA as string | undefined
+const COMMIT_SHA = import.meta.env.REACT_APP_BUILD_COMMIT_SHA
let commitLink: ReactElement
if (COMMIT_SHA) {
diff --git a/ui/src/Pages/SettingsPage/__generated__/changeScanIntervalMutation.ts b/ui/src/Pages/SettingsPage/__generated__/changeScanIntervalMutation.ts
index f1ff3202..a9d9aae0 100644
--- a/ui/src/Pages/SettingsPage/__generated__/changeScanIntervalMutation.ts
+++ b/ui/src/Pages/SettingsPage/__generated__/changeScanIntervalMutation.ts
@@ -12,9 +12,9 @@ export interface changeScanIntervalMutation {
* Set how often, in seconds, the server should automatically scan for new media,
* a value of 0 will disable periodic scans
*/
- setPeriodicScanInterval: number;
+ setPeriodicScanInterval: number
}
export interface changeScanIntervalMutationVariables {
- interval: number;
+ interval: number
}
diff --git a/ui/src/Pages/SettingsPage/__generated__/concurrentWorkersQuery.ts b/ui/src/Pages/SettingsPage/__generated__/concurrentWorkersQuery.ts
index b1bfe3e0..a8759222 100644
--- a/ui/src/Pages/SettingsPage/__generated__/concurrentWorkersQuery.ts
+++ b/ui/src/Pages/SettingsPage/__generated__/concurrentWorkersQuery.ts
@@ -8,13 +8,13 @@
// ====================================================
export interface concurrentWorkersQuery_siteInfo {
- __typename: "SiteInfo";
+ __typename: 'SiteInfo'
/**
* How many max concurrent scanner jobs that should run at once
*/
- concurrentWorkers: number;
+ concurrentWorkers: number
}
export interface concurrentWorkersQuery {
- siteInfo: concurrentWorkersQuery_siteInfo;
+ siteInfo: concurrentWorkersQuery_siteInfo
}
diff --git a/ui/src/Pages/SettingsPage/__generated__/scanAllMutation.ts b/ui/src/Pages/SettingsPage/__generated__/scanAllMutation.ts
index 95fa9b32..a52bdede 100644
--- a/ui/src/Pages/SettingsPage/__generated__/scanAllMutation.ts
+++ b/ui/src/Pages/SettingsPage/__generated__/scanAllMutation.ts
@@ -8,14 +8,14 @@
// ====================================================
export interface scanAllMutation_scanAll {
- __typename: "ScannerResult";
- success: boolean;
- message: string | null;
+ __typename: 'ScannerResult'
+ success: boolean
+ message: string | null
}
export interface scanAllMutation {
/**
* Scan all users for new media
*/
- scanAll: scanAllMutation_scanAll;
+ scanAll: scanAllMutation_scanAll
}
diff --git a/ui/src/Pages/SettingsPage/__generated__/scanIntervalQuery.ts b/ui/src/Pages/SettingsPage/__generated__/scanIntervalQuery.ts
index 9128437d..20dae032 100644
--- a/ui/src/Pages/SettingsPage/__generated__/scanIntervalQuery.ts
+++ b/ui/src/Pages/SettingsPage/__generated__/scanIntervalQuery.ts
@@ -8,13 +8,13 @@
// ====================================================
export interface scanIntervalQuery_siteInfo {
- __typename: "SiteInfo";
+ __typename: 'SiteInfo'
/**
* How often automatic scans should be initiated in seconds
*/
- periodicScanInterval: number;
+ periodicScanInterval: number
}
export interface scanIntervalQuery {
- siteInfo: scanIntervalQuery_siteInfo;
+ siteInfo: scanIntervalQuery_siteInfo
}
diff --git a/ui/src/Pages/SettingsPage/__generated__/setConcurrentWorkers.ts b/ui/src/Pages/SettingsPage/__generated__/setConcurrentWorkers.ts
index 7c6207c2..19881eb5 100644
--- a/ui/src/Pages/SettingsPage/__generated__/setConcurrentWorkers.ts
+++ b/ui/src/Pages/SettingsPage/__generated__/setConcurrentWorkers.ts
@@ -11,9 +11,9 @@ export interface setConcurrentWorkers {
/**
* Set max number of concurrent scanner jobs running at once
*/
- setScannerConcurrentWorkers: number;
+ setScannerConcurrentWorkers: number
}
export interface setConcurrentWorkersVariables {
- workers: number;
+ workers: number
}
diff --git a/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts b/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts
new file mode 100644
index 00000000..bd7f061b
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts
@@ -0,0 +1,21 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+import { ThumbnailFilter } from './../../../__generated__/globalTypes'
+
+// ====================================================
+// GraphQL mutation operation: setThumbnailMethodMutation
+// ====================================================
+
+export interface setThumbnailMethodMutation {
+ /**
+ * Set the filter to be used when generating thumbnails
+ */
+ setThumbnailDownsampleMethod: ThumbnailFilter
+}
+
+export interface setThumbnailMethodMutationVariables {
+ method: ThumbnailFilter
+}
diff --git a/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts b/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts
new file mode 100644
index 00000000..7c9cb00a
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts
@@ -0,0 +1,22 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+import { ThumbnailFilter } from './../../../__generated__/globalTypes'
+
+// ====================================================
+// GraphQL query operation: thumbnailMethodQuery
+// ====================================================
+
+export interface thumbnailMethodQuery_siteInfo {
+ __typename: 'SiteInfo'
+ /**
+ * The filter to use when generating thumbnails
+ */
+ thumbnailMethod: ThumbnailFilter
+}
+
+export interface thumbnailMethodQuery {
+ siteInfo: thumbnailMethodQuery_siteInfo
+}
diff --git a/ui/src/Pages/SharePage/AlbumSharePage.tsx b/ui/src/Pages/SharePage/AlbumSharePage.tsx
index 063a5a34..0e6f5da8 100644
--- a/ui/src/Pages/SharePage/AlbumSharePage.tsx
+++ b/ui/src/Pages/SharePage/AlbumSharePage.tsx
@@ -68,6 +68,7 @@ export const SHARE_ALBUM_QUERY = gql`
}
exif {
id
+ description
camera
maker
lens
diff --git a/ui/src/Pages/SharePage/PasswordProtectedShare.tsx b/ui/src/Pages/SharePage/PasswordProtectedShare.tsx
index f519f8c7..f1262529 100644
--- a/ui/src/Pages/SharePage/PasswordProtectedShare.tsx
+++ b/ui/src/Pages/SharePage/PasswordProtectedShare.tsx
@@ -25,7 +25,7 @@ const PasswordProtectedShare = ({
const [invalidPassword, setInvalidPassword] = useState(false)
const onSubmit = () => {
- refetchWithPassword(watch('password'))
+ refetchWithPassword(watch('password') as string)
setInvalidPassword(true)
}
@@ -35,7 +35,7 @@ const PasswordProtectedShare = ({
'share_page.wrong_password',
'Wrong password, please try again.'
)
- } else if (errors.password?.type === 'required') {
+ } else if (errors.password) {
errorMessage = t(
'share_page.protected_share.password_required_error',
'Password is required'
diff --git a/ui/src/Pages/SharePage/SharePage.test.js b/ui/src/Pages/SharePage/SharePage.test.tsx
similarity index 97%
rename from ui/src/Pages/SharePage/SharePage.test.js
rename to ui/src/Pages/SharePage/SharePage.test.tsx
index dff8e932..435bc008 100644
--- a/ui/src/Pages/SharePage/SharePage.test.js
+++ b/ui/src/Pages/SharePage/SharePage.test.tsx
@@ -17,7 +17,7 @@ import {
import { SIDEBAR_DOWNLOAD_QUERY } from '../../components/sidebar/SidebarDownloadMedia'
import { SHARE_ALBUM_QUERY } from './AlbumSharePage'
-jest.mock('../../hooks/useScrollPagination')
+vi.mock('../../hooks/useScrollPagination')
describe('load correct share page, based on graphql query', () => {
const token = 'TOKEN123'
@@ -96,7 +96,7 @@ describe('load correct share page, based on graphql query', () => {
>
- } />
+ } />
@@ -173,7 +173,7 @@ describe('load correct share page, based on graphql query', () => {
>
- } />
+ } />
diff --git a/ui/src/Pages/SharePage/SharePage.tsx b/ui/src/Pages/SharePage/SharePage.tsx
index 4828e263..d93fb0a7 100644
--- a/ui/src/Pages/SharePage/SharePage.tsx
+++ b/ui/src/Pages/SharePage/SharePage.tsx
@@ -11,6 +11,14 @@ import MediaSharePage from './MediaSharePage'
import { useTranslation } from 'react-i18next'
import PasswordProtectedShare from './PasswordProtectedShare'
import { isNil } from '../../helpers/utils'
+import {
+ SharePageToken,
+ SharePageTokenVariables,
+} from './__generated__/SharePageToken'
+import {
+ ShareTokenValidatePassword,
+ ShareTokenValidatePasswordVariables,
+} from './__generated__/ShareTokenValidatePassword'
export const SHARE_TOKEN_QUERY = gql`
query SharePageToken($token: String!, $password: String) {
@@ -49,6 +57,7 @@ export const SHARE_TOKEN_QUERY = gql`
}
exif {
id
+ description
camera
maker
lens
@@ -89,17 +98,20 @@ const AuthorizedTokenRoute = () => {
const token = tokenFromParams()
const password = getSharePassword(token)
- const { loading, error, data } = useQuery(SHARE_TOKEN_QUERY, {
+ const { loading, error, data } = useQuery<
+ SharePageToken,
+ SharePageTokenVariables
+ >(SHARE_TOKEN_QUERY, {
variables: {
token,
password,
},
})
- if (error) return {error.message}
+ if (!isNil(error)) return {error.message}
if (loading) return {t('general.loading.default', 'Loading...')}
- if (data.shareToken.album) {
+ if (data?.shareToken.album) {
const SharedSubAlbumPage = () => {
const { subAlbum } = useParams()
if (isNil(subAlbum))
@@ -127,7 +139,7 @@ const AuthorizedTokenRoute = () => {
)
}
- if (data.shareToken.media) {
+ if (data?.shareToken.media) {
return
}
@@ -143,16 +155,16 @@ export const TokenRoute = () => {
const { t } = useTranslation()
const token = tokenFromParams()
- const { loading, error, data, refetch } = useQuery(
- VALIDATE_TOKEN_PASSWORD_QUERY,
- {
- notifyOnNetworkStatusChange: true,
- variables: {
- token: token,
- password: getSharePassword(token),
- },
- }
- )
+ const { loading, error, data, refetch } = useQuery<
+ ShareTokenValidatePassword,
+ ShareTokenValidatePasswordVariables
+ >(VALIDATE_TOKEN_PASSWORD_QUERY, {
+ notifyOnNetworkStatusChange: true,
+ variables: {
+ token: token,
+ password: getSharePassword(token),
+ },
+ })
if (error) {
if (error.message == 'GraphQL error: share not found') {
diff --git a/ui/src/Pages/SharePage/__generated__/SharePageToken.ts b/ui/src/Pages/SharePage/__generated__/SharePageToken.ts
index dd4f8716..a974afac 100644
--- a/ui/src/Pages/SharePage/__generated__/SharePageToken.ts
+++ b/ui/src/Pages/SharePage/__generated__/SharePageToken.ts
@@ -106,6 +106,10 @@ export interface SharePageToken_shareToken_media_exif_coordinates {
export interface SharePageToken_shareToken_media_exif {
__typename: 'MediaEXIF'
id: string
+ /**
+ * The description of the image
+ */
+ description: string | null
/**
* The model name of the camera
*/
@@ -118,7 +122,7 @@ export interface SharePageToken_shareToken_media_exif {
* The name of the lens
*/
lens: string | null
- dateShot: any | null
+ dateShot: Time | null
/**
* The exposure time of the image
*/
diff --git a/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts b/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts
index 26b476e0..5448f60a 100644
--- a/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts
+++ b/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts
@@ -120,6 +120,10 @@ export interface shareAlbumQuery_album_media_exif_coordinates {
export interface shareAlbumQuery_album_media_exif {
__typename: 'MediaEXIF'
id: string
+ /**
+ * The description of the image
+ */
+ description: string | null
/**
* The model name of the camera
*/
@@ -132,7 +136,7 @@ export interface shareAlbumQuery_album_media_exif {
* The name of the lens
*/
lens: string | null
- dateShot: any | null
+ dateShot: Time | null
/**
* The exposure time of the image
*/
diff --git a/ui/src/__generated__/globalTypes.ts b/ui/src/__generated__/globalTypes.ts
index 7771fbb7..89eae65f 100644
--- a/ui/src/__generated__/globalTypes.ts
+++ b/ui/src/__generated__/globalTypes.ts
@@ -11,6 +11,7 @@
* Supported language translations of the user interface
*/
export enum LanguageTranslation {
+ Basque = 'Basque',
Danish = 'Danish',
English = 'English',
French = 'French',
@@ -47,6 +48,18 @@ export enum OrderDirection {
DESC = 'DESC',
}
+/**
+ * Supported downsampling filters for thumbnail generation
+ */
+export enum ThumbnailFilter {
+ Box = 'Box',
+ CatmullRom = 'CatmullRom',
+ Lanczos = 'Lanczos',
+ Linear = 'Linear',
+ MitchellNetravali = 'MitchellNetravali',
+ NearestNeighbor = 'NearestNeighbor',
+}
+
//==============================================================
// END Enums and Input Objects
//==============================================================
diff --git a/ui/src/apolloClient.ts b/ui/src/apolloClient.ts
index dfc4b1e5..71ee1ffc 100644
--- a/ui/src/apolloClient.ts
+++ b/ui/src/apolloClient.ts
@@ -17,8 +17,8 @@ import { MessageState } from './components/messages/Messages'
import { Message } from './components/messages/SubscriptionsHook'
import { NotificationType } from './__generated__/globalTypes'
-export const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT
- ? (process.env.REACT_APP_API_ENDPOINT as string)
+export const API_ENDPOINT = import.meta.env.REACT_APP_API_ENDPOINT
+ ? (import.meta.env.REACT_APP_API_ENDPOINT as string)
: urlJoin(location.origin, '/api')
export const GRAPHQL_ENDPOINT = urlJoin(API_ENDPOINT, '/graphql')
@@ -56,19 +56,24 @@ const link = split(
const linkError = onError(({ graphQLErrors, networkError }) => {
const errorMessages = []
+ const formatPath = (path: readonly (string | number)[] | undefined) =>
+ path?.join('::') ?? 'undefined'
+
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(
locations
- )} Path: ${path}`
+ )} Path: ${formatPath(path)}`
)
)
if (graphQLErrors.length == 1) {
errorMessages.push({
header: 'Something went wrong',
- content: `Server error: ${graphQLErrors[0].message} at (${graphQLErrors[0].path})`,
+ content: `Server error: ${graphQLErrors[0].message} at (${formatPath(
+ graphQLErrors[0].path
+ )})`,
})
} else if (graphQLErrors.length > 1) {
errorMessages.push({
@@ -88,7 +93,8 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
console.log(`[Network error]: ${JSON.stringify(networkError)}`)
clearTokenCookie()
- const errors = (networkError as ServerError)?.result.errors || []
+ const errors =
+ ((networkError as ServerError)?.result.errors as Error[]) || []
if (errors.length == 1) {
errorMessages.push({
@@ -120,7 +126,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
type PaginateCacheType = {
keyArgs: string[]
- merge: FieldMergeFunction
+ merge: FieldMergeFunction
}
// Modified version of Apollo's offsetLimitPagination()
@@ -130,7 +136,7 @@ const paginateCache = (keyArgs: string[]) =>
merge(existing, incoming, { args, fieldName }) {
const merged = existing ? existing.slice(0) : []
if (args?.paginate) {
- const { offset = 0 } = args.paginate
+ const { offset = 0 } = args.paginate as { offset: number }
for (let i = 0; i < incoming.length; ++i) {
merged[offset + i] = incoming[i]
}
diff --git a/ui/src/components/albumGallery/AlbumGallery.tsx b/ui/src/components/albumGallery/AlbumGallery.tsx
index f640f409..2081c5d8 100644
--- a/ui/src/components/albumGallery/AlbumGallery.tsx
+++ b/ui/src/components/albumGallery/AlbumGallery.tsx
@@ -1,49 +1,46 @@
import React, { useEffect, useReducer } from 'react'
import AlbumTitle from '../album/AlbumTitle'
-import PhotoGallery from '../photoGallery/PhotoGallery'
+import MediaGallery, {
+ MEDIA_GALLERY_FRAGMENT,
+} from '../photoGallery/MediaGallery'
import AlbumBoxes from './AlbumBoxes'
import AlbumFilter from '../album/AlbumFilter'
import {
- albumQuery_album_media_highRes,
- albumQuery_album_media_thumbnail,
- albumQuery_album_media_videoWeb,
- albumQuery_album_subAlbums,
-} from '../../Pages/AlbumPage/__generated__/albumQuery'
-import {
- photoGalleryReducer,
+ mediaGalleryReducer,
urlPresentModeSetupHook,
-} from '../photoGallery/photoGalleryReducer'
+} from '../photoGallery/mediaGalleryReducer'
import { MediaOrdering, SetOrderingFn } from '../../hooks/useOrderingParams'
-import { MediaType } from '../../__generated__/globalTypes'
+import { gql } from '@apollo/client'
+import { AlbumGalleryFields } from './__generated__/AlbumGalleryFields'
-type AlbumGalleryAlbum = {
- __typename: 'Album'
- id: string
- title: string
- subAlbums: albumQuery_album_subAlbums[]
- media: {
- __typename: 'Media'
- id: string
- type: MediaType
- /**
- * URL to display the media in a smaller resolution
- */
- thumbnail: albumQuery_album_media_thumbnail | null
- /**
- * URL to display the photo in full resolution, will be null for videos
- */
- highRes: albumQuery_album_media_highRes | null
- /**
- * URL to get the video in a web format that can be played in the browser, will be null for photos
- */
- videoWeb: albumQuery_album_media_videoWeb | null
- favorite?: boolean
- blurhash: string | null
- }[]
-}
+export const ALBUM_GALLERY_FRAGMENT = gql`
+ ${MEDIA_GALLERY_FRAGMENT}
+
+ fragment AlbumGalleryFields on Album {
+ id
+ title
+ subAlbums(order: { order_by: "title", order_direction: $orderDirection }) {
+ id
+ title
+ thumbnail {
+ id
+ thumbnail {
+ url
+ }
+ }
+ }
+ media(
+ paginate: { limit: $limit, offset: $offset }
+ order: { order_by: $mediaOrderBy, order_direction: $orderDirection }
+ onlyFavorites: $onlyFavorites
+ ) {
+ ...MediaGalleryFields
+ }
+ }
+`
type AlbumGalleryProps = {
- album?: AlbumGalleryAlbum
+ album?: AlbumGalleryFields
loading?: boolean
customAlbumLink?(albumID: string): string
showFilter?: boolean
@@ -68,7 +65,7 @@ const AlbumGallery = React.forwardRef(
}: AlbumGalleryProps,
ref: React.ForwardedRef
) => {
- const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, {
+ const [mediaState, dispatchMedia] = useReducer(mediaGalleryReducer, {
presenting: false,
activeIndex: -1,
media: album?.media || [],
@@ -114,7 +111,7 @@ const AlbumGallery = React.forwardRef(
)}
{subAlbumElement}
- {
diff --git a/ui/src/components/layout/Layout.test.js b/ui/src/components/layout/Layout.test.tsx
similarity index 82%
rename from ui/src/components/layout/Layout.test.js
rename to ui/src/components/layout/Layout.test.tsx
index b053203f..ddbe3186 100644
--- a/ui/src/components/layout/Layout.test.js
+++ b/ui/src/components/layout/Layout.test.tsx
@@ -2,9 +2,9 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import Layout from './Layout'
-test('Layout component', async () => {
+test('Layout component', () => {
render(
-
+
layout_content
)
diff --git a/ui/src/components/layout/Layout.tsx b/ui/src/components/layout/Layout.tsx
index a0be6535..ab52973f 100644
--- a/ui/src/components/layout/Layout.tsx
+++ b/ui/src/components/layout/Layout.tsx
@@ -1,5 +1,4 @@
import { gql } from '@apollo/client'
-import PropTypes from 'prop-types'
import React, { useContext } from 'react'
import { Helmet } from 'react-helmet'
import Header from '../header/Header'
@@ -41,7 +40,6 @@ const Layout = ({ children, title, ...otherProps }: LayoutProps) => {
id="layout-content"
>
{children}
- {/* */}