feat: added preview in find
Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
const config = {
|
const config = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import style from './findInputStyle'
|
import inputStyle from './findInputStyle'
|
||||||
import { MentionsInput, Mention } from 'react-mentions'
|
import { MentionsInput, Mention } from 'react-mentions'
|
||||||
import { searchPeople } from '@/handlers/api/people.handler';
|
import { searchPeople } from '@/handlers/api/people.handler';
|
||||||
import mentionStyle from './findMentionStyle';
|
import mentionStyle from './findMentionStyle';
|
||||||
import { PERSON_THUBNAIL_PATH } from '@/config/routes';
|
import classNames from './FindInput.module.css';
|
||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
interface FindInputProps {
|
interface FindInputProps {
|
||||||
onSearch: (query: string) => void;
|
onSearch: (query: string) => void;
|
||||||
@@ -26,13 +26,8 @@ export default function FindInput({ onSearch }: FindInputProps) {
|
|||||||
<MentionsInput
|
<MentionsInput
|
||||||
value={query}
|
value={query}
|
||||||
singleLine={true}
|
singleLine={true}
|
||||||
style={style}
|
|
||||||
className='w-full border border-gray-200 rounded-md p-2'
|
|
||||||
classNames={{
|
|
||||||
control: 'w-full border border-gray-200 rounded-md p-2',
|
|
||||||
input: 'w-full border border-gray-200 rounded-md p-2 w-full',
|
|
||||||
}}
|
|
||||||
placeholder='Search for photos, use @ to search for people'
|
placeholder='Search for photos, use @ to search for people'
|
||||||
|
style={inputStyle}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
onSearch(query);
|
onSearch(query);
|
||||||
@@ -41,14 +36,13 @@ export default function FindInput({ onSearch }: FindInputProps) {
|
|||||||
onChange={(e) => setQuery(e.target.value)}>
|
onChange={(e) => setQuery(e.target.value)}>
|
||||||
<Mention
|
<Mention
|
||||||
trigger="@"
|
trigger="@"
|
||||||
data={handleSearchPeople}
|
|
||||||
style={mentionStyle}
|
style={mentionStyle}
|
||||||
|
data={handleSearchPeople}
|
||||||
renderSuggestion={(
|
renderSuggestion={(
|
||||||
highlightedDisplay,
|
highlightedDisplay,
|
||||||
focused
|
focused
|
||||||
) => (
|
) => (
|
||||||
<div className={`user ${focused ? 'focused' : ''}`}>
|
<div className={`user ${focused ? 'focused' : ''} `}>
|
||||||
|
|
||||||
{highlightedDisplay.display}
|
{highlightedDisplay.display}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,37 +1,42 @@
|
|||||||
const findInputStyle = {
|
const findInputStyle = {
|
||||||
control: {
|
control: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'hsl(var(--input))',
|
||||||
fontSize: 14,
|
fontSize: 20,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
|
outline: 'none',
|
||||||
|
borderRadius: '15px',
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'&singleLine': {
|
'&singleLine': {
|
||||||
display: 'inline-block',
|
// display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
|
||||||
highlighter: {
|
highlighter: {
|
||||||
padding: '10px',
|
padding: '10px 15px',
|
||||||
border: '2px inset transparent',
|
border: 'none',
|
||||||
|
outline: 'none',
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
width: "100%",
|
padding: '10px 15px',
|
||||||
padding: '10px',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
outline: 'none',
|
||||||
border: '1px solid #e0e0e0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
suggestions: {
|
suggestions: {
|
||||||
|
|
||||||
list: {
|
list: {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'hsl(var(--background))',
|
||||||
border: '1px solid rgba(0,0,0,0.15)',
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
border: '1px solid hsl(var(--border))',
|
||||||
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
padding: '5px 15px',
|
padding: '5px 15px',
|
||||||
borderBottom: '1px solid rgba(0,0,0,0.15)',
|
border: 'none',
|
||||||
'&focused': {
|
'&focused': {
|
||||||
backgroundColor: '#cee4e5',
|
backgroundColor: 'hsl(var(--chart-2))',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export default {
|
export default {
|
||||||
backgroundColor: '#cee4e5',
|
backgroundColor: 'hsla(var(--chart-2) / 0.3)',
|
||||||
|
position: 'relative',
|
||||||
}
|
}
|
||||||
@@ -23,11 +23,11 @@ export default function PageLayout({ children, className, title }: IProps) {
|
|||||||
: children;
|
: children;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col w-full h-full">
|
||||||
{header}
|
{header}
|
||||||
<main
|
<main
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 max-h-[calc(100vh-60px)] flex-col gap-4 overflow-y-auto mx-auto",
|
"flex flex-1 max-h-[calc(100vh-60px)] flex-col gap-4 overflow-y-auto mx-auto w-full",
|
||||||
{
|
{
|
||||||
"mb-14": !!header,
|
"mb-14": !!header,
|
||||||
},
|
},
|
||||||
|
|||||||
+65
-32
@@ -1,22 +1,39 @@
|
|||||||
|
import "yet-another-react-lightbox/styles.css";
|
||||||
import FindInput from '@/components/find/FindInput';
|
import FindInput from '@/components/find/FindInput';
|
||||||
import PageLayout from '@/components/layouts/PageLayout';
|
import PageLayout from '@/components/layouts/PageLayout';
|
||||||
import Header from '@/components/shared/Header';
|
import Header from '@/components/shared/Header';
|
||||||
import Loader from '@/components/ui/loader';
|
import Loader from '@/components/ui/loader';
|
||||||
import { ASSET_THUMBNAIL_PATH } from '@/config/routes';
|
import { ASSET_PREVIEW_PATH, ASSET_THUMBNAIL_PATH } from '@/config/routes';
|
||||||
import { useConfig } from '@/contexts/ConfigContext';
|
import { useConfig } from '@/contexts/ConfigContext';
|
||||||
import { findAssets } from '@/handlers/api/asset.handler';
|
import { findAssets } from '@/handlers/api/asset.handler';
|
||||||
import { IAsset } from '@/types/asset';
|
import { IAsset } from '@/types/asset';
|
||||||
import { Search, TriangleAlert, WandSparkles } from 'lucide-react';
|
import { Captions, Megaphone, Search, Speaker, TriangleAlert, WandSparkles } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import React, { useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
|
import Lightbox from 'yet-another-react-lightbox';
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
|
||||||
export default function FindPage() {
|
export default function FindPage() {
|
||||||
const { geminiEnabled } = useConfig();
|
|
||||||
|
const [index, setIndex] = useState(-1);
|
||||||
|
const { geminiEnabled, exImmichUrl } = useConfig();
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [assets, setAssets] = useState<IAsset[]>([]);
|
const [assets, setAssets] = useState<IAsset[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const slides = useMemo(
|
||||||
|
() =>
|
||||||
|
assets.map((asset) => ({
|
||||||
|
src: ASSET_PREVIEW_PATH(asset.id),
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
})),
|
||||||
|
[assets]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const handleSearch = (query: string) => {
|
const handleSearch = (query: string) => {
|
||||||
setQuery(query);
|
setQuery(query);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -42,6 +59,9 @@ export default function FindPage() {
|
|||||||
<p className='text-sm text-muted-foreground'>
|
<p className='text-sm text-muted-foreground'>
|
||||||
Example: Sunset photos from last week. Use <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>@</kbd> to search for photos of a specific person.
|
Example: Sunset photos from last week. Use <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>@</kbd> to search for photos of a specific person.
|
||||||
</p>
|
</p>
|
||||||
|
<p className='text-xs text-muted-foreground text-center flex gap-1 items-center'>
|
||||||
|
<span>Power tools uses Google Gemini only for parsing your query. None of your data is sent to Gemini.</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (assets.length === 0) {
|
else if (assets.length === 0) {
|
||||||
@@ -50,23 +70,37 @@ export default function FindPage() {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-5 gap-4 p-2">
|
<>
|
||||||
{assets.map((asset) => (
|
<Lightbox
|
||||||
<div key={asset.id} className='w-full h-[200px] rounded-md overflow-hidden'>
|
slides={slides}
|
||||||
<Image
|
open={index >= 0}
|
||||||
src={ASSET_THUMBNAIL_PATH(asset.id)}
|
index={index}
|
||||||
alt={asset.id}
|
close={() => setIndex(-1)}
|
||||||
style={{
|
/>
|
||||||
objectFit: 'cover',
|
<div className="grid grid-cols-5 gap-4 p-2">
|
||||||
width: '100%',
|
{assets.map((asset, idx) => (
|
||||||
height: '100%',
|
<div key={asset.id} className='w-full h-[200px] rounded-md overflow-hidden relative'>
|
||||||
}}
|
<Link href={`${exImmichUrl}/photos/${asset.id}`}
|
||||||
width={200}
|
target="_blank"
|
||||||
height={200}
|
className='absolute top-2 right-2 text-xs'>
|
||||||
/>
|
Open in Immich
|
||||||
</div>
|
</Link>
|
||||||
))}
|
<Image
|
||||||
</div>
|
src={ASSET_THUMBNAIL_PATH(asset.id)}
|
||||||
|
alt={asset.id}
|
||||||
|
style={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
onClick={() => setIndex(idx)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +108,24 @@ export default function FindPage() {
|
|||||||
<PageLayout>
|
<PageLayout>
|
||||||
<Header leftComponent="Find" />
|
<Header leftComponent="Find" />
|
||||||
{geminiEnabled ? (
|
{geminiEnabled ? (
|
||||||
<div>
|
<>
|
||||||
<div className="flex flex-col gap-4 p-2">
|
<div className="flex flex-col gap-4 p-2">
|
||||||
<FindInput onSearch={handleSearch} />
|
<FindInput onSearch={handleSearch} />
|
||||||
</div>
|
</div>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center py-10 items-center h-full flex-col gap-2">
|
<div className="flex justify-center py-10 items-center h-full flex-col gap-2">
|
||||||
<WandSparkles className='w-10 h-10 text-muted-foreground' />
|
<WandSparkles className='w-10 h-10 text-muted-foreground' />
|
||||||
<p className='text-lg font-semibold'>Google Gemini is not enabled</p>
|
<p className='text-lg font-semibold'>Google Gemini is not enabled</p>
|
||||||
<p className='text-sm text-muted-foreground max-w-md text-center'>
|
<p className='text-sm text-muted-foreground max-w-md text-center'>
|
||||||
Currently, the Power Tools Find is relying on Google Gemini for parsing the query.
|
Currently, the Power Tools Find is relying on Google Gemini for parsing the query.
|
||||||
Please enable Gemini by adding <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>GEMINI_API_KEY</kbd> in the <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>.env</kbd> file.
|
Please enable Gemini by adding <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>GEMINI_API_KEY</kbd> in the <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>.env</kbd> file.
|
||||||
</p>
|
</p>
|
||||||
<div className="border border-l-4 border-zinc-200 dark:border-zinc-500 rounded-md p-2">
|
<div className="border border-l-4 border-zinc-200 dark:border-zinc-500 rounded-md p-2">
|
||||||
<p className='text-sm text-muted-foreground text-center flex gap-1 items-center'>
|
<p className='text-xs text-muted-foreground text-center flex gap-1 items-center'>
|
||||||
<TriangleAlert className='w-4 h-4' />
|
<span>Power tools uses Google Gemini only for parsing your query. None of your data is sent to Gemini.</span>
|
||||||
<span>Power tools uses Gemini only for parsing your query. None of your data is sent to Gemini.</span>
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -88,3 +87,4 @@
|
|||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user