feat: added preview in find

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj
2024-12-30 22:02:44 +05:30
parent 9f2f0ef7a6
commit 31e58793be
8 changed files with 98 additions and 70 deletions
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+1
View File
@@ -2,6 +2,7 @@
const config = { const config = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {},
}, },
}; };
+6 -12
View File
@@ -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>
)} )}
+19 -14
View File
@@ -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))',
}, },
}, },
}, },
+3 -2
View File
@@ -1,3 +1,4 @@
export default { export default {
backgroundColor: '#cee4e5', backgroundColor: 'hsla(var(--chart-2) / 0.3)',
} position: 'relative',
}
+2 -2
View File
@@ -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,
}, },
+66 -33
View File
@@ -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>
)} )}
+1 -1
View File
@@ -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%;
} }
} }