Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Share Links, Find Page Bulk Options, Design Upgrades, Better errors #114

Merged
merged 23 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5b2b779
feat: added bulk share links generation
varun-raj Jan 5, 2025
bb6ffeb
feat: added option to group by albums for missing locations
varun-raj Jan 5, 2025
1b986a8
fix: gemini response and query fixes
varun-raj Jan 5, 2025
9bd314f
fix: minor design fixes
varun-raj Jan 5, 2025
fdda358
feat: option to bulk delete albums
varun-raj Jan 5, 2025
a3e12c8
feat: moved db config from url to individual keys
varun-raj Jan 5, 2025
4820f35
feat: multi select in potential album and missing location using shif…
varun-raj Jan 11, 2025
1132a04
fix: album list fix
varun-raj Jan 11, 2025
34f32e4
feat: shared album links
varun-raj Jan 17, 2025
1fea9cc
feat: people selection in share link
varun-raj Jan 18, 2025
29f80b8
feat: people search added
varun-raj Jan 18, 2025
098701b
feat: option to delete assets from missing locations page
varun-raj Jan 18, 2025
72dcb92
feat: unified album creation and option to delete assets from potenti…
varun-raj Jan 18, 2025
237857b
docs: readme update
varun-raj Jan 18, 2025
b60660f
fix: minor design fix
varun-raj Jan 18, 2025
2150aca
feat: bulk delete in find page and find page clean up
varun-raj Jan 19, 2025
19d147d
docs: fixed readme
varun-raj Jan 19, 2025
fdef491
docs: fixed readme
varun-raj Jan 19, 2025
d5c6f0a
feat: added option for link expiry
varun-raj Jan 19, 2025
9fcda54
feat: added proper validations for errors with messages
varun-raj Jan 19, 2025
59fb16b
fix: removed unused components
varun-raj Jan 19, 2025
18274a3
fix: conflicts resolved
varun-raj Jan 19, 2025
e7687b9
fix: minor condition fix
varun-raj Jan 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ SECURE_COOKIE=false # Set to true to enable secure cookies

# Optional
GOOGLE_MAPS_API_KEY="" # Google Maps API Key for heatmap
GEMINI_API_KEY="" # Gemini API Key for parsing search query in "Find"
GEMINI_API_KEY="" # Gemini API Key for parsing search query in "Find"

# Immich Share Link
IMMICH_SHARE_LINK_KEY="" # Share link key for Immich
POWER_TOOLS_ENDPOINT_URL="" # URL of the Power Tools endpoint (Used for share links)
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ A unofficial immich client to provide better tools to organize and manage your i

[![Immich Power Tools](./screenshots/screenshot-1.png)](https://www.loom.com/embed/13aa90d8ab2e4acab0993bdc8703a750?sid=71498690-b745-473f-b239-a7bdbe6efc21)

### 🎒Features
- **Manage people data in bulk 👫**: Options to update people data in bulk, and with advance filters
- **People Merge Suggestion 🎭**: Option to bulk merge people with suggested faces based on similarity.
- **Update Missing Locations 📍**: Find assets in your library those are without location and update them with the location of the asset.
- **Potential Albums 🖼️**: Find albums that are potential to be created based on the assets and people in your library.
- **Analytics 📈**: Get analytics on your library like assets over time, exif data, etc.
- **Smart Search 🔎**: Search your library with natural language, supports queries like "show me all my photos from 2024 of <person name>"
- **Bulk Date Offset 📅**: Offset the date of selected assets by a given amount of time. Majorly used to fix the date of assets that are out of sync with the actual date.

### Support me 🙏

If you find this tool useful, please consider supporting me by buying me a coffee.

[![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/varunraj)

## 💭 Back story

Recently I've migrated my entire Google photos library to Immich, I was able to successfully migrate all my assets along with its albums to immich. But there were few things like people match that was lacking. I loved Immich UI on the whole but for organizing content I felt its quite restricted and I had to do a lot of things in bulk instead of opening each asset and doing it. Hence I built this tool (continuing to itereate) to make my life and any other Immich user's life easier.
Expand Down Expand Up @@ -51,7 +66,11 @@ Refer here for obtaining Immich API Key: https://immich.app/docs/features/comman
If you're using portainer, run the docker using `docker run` and add the power tools to the same network as immich.

```bash
# Run the power tools from docker
docker run -d --name immich_power_tools -p 8001:3000 --env-file .env ghcr.io/varun-raj/immich-power-tools:latest

# Add Power tools to the same network as immich
docker network connect immich_default immich_power_tools
```


Expand Down Expand Up @@ -90,7 +109,8 @@ bun run dev
- [x] Manage People
- [x] Smart Merge
- [x] Manage Albums
- [ ] Bulk Delete
- [x] Bulk Delete
- [x] Bulk Share
- [ ] Bulk Edit
- [ ] Filters
- [x] Potential Albums
Expand Down Expand Up @@ -127,7 +147,6 @@ Google Gemini 1.5 Flash model is used for parsing your search query in "Find" pa

> Code where Gemini is used: [src/helpers/gemini.helper.ts](./src/helpers/gemini.helper.ts)


## Contributing

Feel free to contribute to this project, I'm open to any suggestions and improvements. Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
Binary file modified bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@google/generative-ai": "^0.21.0",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.1",
Expand Down Expand Up @@ -47,19 +48,19 @@
"cookie": "^0.6.0",
"date-fns": "^3.6.0",
"drizzle-orm": "^0.33.0",
"jsonwebtoken": "^9.0.2",
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "0.1.2",
"lucide-react": "^0.428.0",
"next": "14.2.5",
"next-themes": "^0.3.0",
"pg": "^8.12.0",
"qs": "^6.13.0",
"rc-mentions": "^2.18.0",
"react": "^18",
"react-calendar-heatmap": "^1.9.0",
"react-day-picker": "9.0.8",
"react-dom": "^18",
"react-grid-gallery": "^1.0.1",
"react-hot-toast": "^2.5.1",
"react-leaflet": "^4.2.1",
"react-mentions": "^4.4.10",
"react-query": "^3.39.3",
Expand Down
86 changes: 0 additions & 86 deletions src/components/albums/AlbumCreateDialog.tsx

This file was deleted.

79 changes: 72 additions & 7 deletions src/components/albums/AlbumSelectorDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
Dialog,
DialogContent,
Expand All @@ -9,18 +9,28 @@ import {
} from "../ui/dialog";
import { Button } from "../ui/button";
import { listAlbums } from "@/handlers/api/album.handler";
import { IAlbum } from "@/types/album";
import { IAlbum, IAlbumCreate } from "@/types/album";
import { Input } from "../ui/input";
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import toast from "react-hot-toast";
import { Label } from "../ui/label";

interface IProps {
onSelected: (album: IAlbum) => Promise<void>;
onCreated?: (album: IAlbum) => Promise<void>;
onSubmit?: (data: IAlbumCreate) => Promise<any>;
}
export default function AlbumSelectorDialog({ onSelected }: IProps) {
export default function AlbumSelectorDialog({ onSelected, onCreated, onSubmit }: IProps) {
const [open, setOpen] = useState(false);
const [albums, setAlbums] = useState<IAlbum[]>([]);
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [search, setSearch] = useState("");
const { selectedIds, assets } = usePotentialAlbumContext();
const [formData, setFormData] = useState({
albumName: "",
});

const fetchData = () => {
return listAlbums()
Expand All @@ -42,11 +52,31 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
});
}


const handleSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!onSubmit) return;
setLoading(true);
onSubmit(formData)
.then(() => {
toast.success("Album created successfully");
setFormData({ albumName: "" });
setOpen(false);
})
.catch((e) => {
toast.error("Failed to create album");
})
.finally(() => {
setLoading(false);
});
}, [onSubmit, formData]);


useEffect(() => {
if (open && !albums.length) fetchData();
}, [open]);

const renderContent = () => {
const renderContent = useCallback(() => {
if (loading) return <p>Loading...</p>;
if (errorMessage) return <p>{errorMessage}</p>;
return (
Expand All @@ -73,12 +103,36 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
</div>
</div>
);
};
}, [albums, filteredAlbums, handleSelect, loading, errorMessage]);

const renderCreateContent = useCallback(() => {
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-4 max-h-[500px] min-h-[500px]">
<p className="text-sm text-zinc-500 dark:text-zinc-400">
Create a new album and add the selected assets to it
</p>
<div className="flex flex-col gap-2">
<Label>Album Name</Label>
<Input
placeholder="Album Name"
onChange={(e) => {
setFormData({ ...formData, albumName: e.target.value });
}}
/>
</div>
<div className="self-end">
<Button disabled={loading} type="submit">
{loading ? "Creating Album" : "Create Album"}
</Button>
</div>
</form>
)
}, [loading, formData, handleSubmit]);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Select Album</Button>
<Button size={"sm"} disabled={!selectedIds.length}>Add to Album</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
Expand All @@ -87,7 +141,18 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
Select the albums you want to add the selected assets to
</DialogDescription>
</DialogHeader>
{renderContent()}
<Tabs defaultValue="albums" className="w-full">
<TabsList className="w-full">
<TabsTrigger className="w-full" value="albums">Albums</TabsTrigger>
<TabsTrigger className="w-full" value="create">Create</TabsTrigger>
</TabsList>
<TabsContent value="albums">
{renderContent()}
</TabsContent>
<TabsContent value="create">
{renderCreateContent()}
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
);
Expand Down
9 changes: 4 additions & 5 deletions src/components/albums/info/AlbumImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,21 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
index={index}
close={() => setIndex(-1)}
/>
<div className="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 p-2">
<div className="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-2 p-2">
{images.map((image) => (
<div
key={image.id}
className="w-full h-[200px] overflow-hidden relative"
className="w-full h-[180px] overflow-hidden relative"
>
<LazyImage
loading="lazy"
key={image.id}
src={image.original}
alt={image.originalFileName}
className='overflow-hidden'
className='overflow-hidden max-h-[180px] max-w-[180px] min-h-[180px] min-w-[180px]'
style={{
objectPosition: 'center',
objectFit: 'cover',
height: '100%',
objectFit: 'cover'
}}
onClick={() => handleClick(images.indexOf(image))}
/>
Expand Down
Loading
Loading