Skip to content

Commit cd9e620

Browse files
authored
Merge pull request #115 from varun-raj/pre-release
Release 0.13.0
2 parents 0c36e06 + 44f64dc commit cd9e620

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1551
-433
lines changed

.env.sample

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ SECURE_COOKIE=false # Set to true to enable secure cookies
1111

1212
# Optional
1313
GOOGLE_MAPS_API_KEY="" # Google Maps API Key for heatmap
14-
GEMINI_API_KEY="" # Gemini API Key for parsing search query in "Find"
14+
GEMINI_API_KEY="" # Gemini API Key for parsing search query in "Find"
15+
16+
# Immich Share Link
17+
IMMICH_SHARE_LINK_KEY="" # Share link key for Immich
18+
POWER_TOOLS_ENDPOINT_URL="" # URL of the Power Tools endpoint (Used for share links)

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ A unofficial immich client to provide better tools to organize and manage your i
66

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

9+
### 🎒Features
10+
- **Manage people data in bulk 👫**: Options to update people data in bulk, and with advance filters
11+
- **People Merge Suggestion 🎭**: Option to bulk merge people with suggested faces based on similarity.
12+
- **Update Missing Locations 📍**: Find assets in your library those are without location and update them with the location of the asset.
13+
- **Potential Albums 🖼️**: Find albums that are potential to be created based on the assets and people in your library.
14+
- **Analytics 📈**: Get analytics on your library like assets over time, exif data, etc.
15+
- **Smart Search 🔎**: Search your library with natural language, supports queries like "show me all my photos from 2024 of <person name>"
16+
- **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.
17+
18+
### Support me 🙏
19+
20+
If you find this tool useful, please consider supporting me by buying me a coffee.
21+
22+
[![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/varunraj)
23+
924
## 💭 Back story
1025

1126
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.
@@ -51,7 +66,11 @@ Refer here for obtaining Immich API Key: https://immich.app/docs/features/comman
5166
If you're using portainer, run the docker using `docker run` and add the power tools to the same network as immich.
5267

5368
```bash
69+
# Run the power tools from docker
5470
docker run -d --name immich_power_tools -p 8001:3000 --env-file .env ghcr.io/varun-raj/immich-power-tools:latest
71+
72+
# Add Power tools to the same network as immich
73+
docker network connect immich_default immich_power_tools
5574
```
5675

5776

@@ -90,7 +109,8 @@ bun run dev
90109
- [x] Manage People
91110
- [x] Smart Merge
92111
- [x] Manage Albums
93-
- [ ] Bulk Delete
112+
- [x] Bulk Delete
113+
- [x] Bulk Share
94114
- [ ] Bulk Edit
95115
- [ ] Filters
96116
- [x] Potential Albums
@@ -127,7 +147,6 @@ Google Gemini 1.5 Flash model is used for parsing your search query in "Find" pa
127147

128148
> Code where Gemini is used: [src/helpers/gemini.helper.ts](./src/helpers/gemini.helper.ts)
129149
130-
131150
## Contributing
132151

133152
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.

bun.lockb

2.9 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,15 @@
4848
"cookie": "^0.6.0",
4949
"date-fns": "^3.6.0",
5050
"drizzle-orm": "^0.33.0",
51+
"jsonwebtoken": "^9.0.2",
5152
"leaflet": "^1.9.4",
5253
"leaflet-defaulticon-compatibility": "0.1.2",
5354
"lucide-react": "^0.428.0",
5455
"next": "14.2.5",
5556
"next-themes": "^0.3.0",
5657
"pg": "^8.12.0",
5758
"qs": "^6.13.0",
58-
"rc-mentions": "^2.18.0",
5959
"react": "^18",
60-
"react-calendar-heatmap": "^1.9.0",
6160
"react-day-picker": "9.0.8",
6261
"react-dom": "^18",
6362
"react-grid-gallery": "^1.0.1",

src/components/albums/AlbumCreateDialog.tsx

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/components/albums/AlbumSelectorDialog.tsx

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useMemo, useState } from "react";
1+
import React, { useCallback, useEffect, useMemo, useState } from "react";
22
import {
33
Dialog,
44
DialogContent,
@@ -9,18 +9,28 @@ import {
99
} from "../ui/dialog";
1010
import { Button } from "../ui/button";
1111
import { listAlbums } from "@/handlers/api/album.handler";
12-
import { IAlbum } from "@/types/album";
12+
import { IAlbum, IAlbumCreate } from "@/types/album";
1313
import { Input } from "../ui/input";
14+
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
15+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
16+
import toast from "react-hot-toast";
17+
import { Label } from "../ui/label";
1418

1519
interface IProps {
1620
onSelected: (album: IAlbum) => Promise<void>;
21+
onCreated?: (album: IAlbum) => Promise<void>;
22+
onSubmit?: (data: IAlbumCreate) => Promise<any>;
1723
}
18-
export default function AlbumSelectorDialog({ onSelected }: IProps) {
24+
export default function AlbumSelectorDialog({ onSelected, onCreated, onSubmit }: IProps) {
1925
const [open, setOpen] = useState(false);
2026
const [albums, setAlbums] = useState<IAlbum[]>([]);
2127
const [loading, setLoading] = useState(false);
2228
const [errorMessage, setErrorMessage] = useState<string | null>(null);
2329
const [search, setSearch] = useState("");
30+
const { selectedIds, assets } = usePotentialAlbumContext();
31+
const [formData, setFormData] = useState({
32+
albumName: "",
33+
});
2434

2535
const fetchData = () => {
2636
return listAlbums()
@@ -42,11 +52,31 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
4252
});
4353
}
4454

55+
56+
const handleSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => {
57+
e.preventDefault();
58+
if (!onSubmit) return;
59+
setLoading(true);
60+
onSubmit(formData)
61+
.then(() => {
62+
toast.success("Album created successfully");
63+
setFormData({ albumName: "" });
64+
setOpen(false);
65+
})
66+
.catch((e) => {
67+
toast.error("Failed to create album");
68+
})
69+
.finally(() => {
70+
setLoading(false);
71+
});
72+
}, [onSubmit, formData]);
73+
74+
4575
useEffect(() => {
4676
if (open && !albums.length) fetchData();
4777
}, [open]);
4878

49-
const renderContent = () => {
79+
const renderContent = useCallback(() => {
5080
if (loading) return <p>Loading...</p>;
5181
if (errorMessage) return <p>{errorMessage}</p>;
5282
return (
@@ -73,12 +103,36 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
73103
</div>
74104
</div>
75105
);
76-
};
106+
}, [albums, filteredAlbums, handleSelect, loading, errorMessage]);
107+
108+
const renderCreateContent = useCallback(() => {
109+
return (
110+
<form onSubmit={handleSubmit} className="flex flex-col gap-4 max-h-[500px] min-h-[500px]">
111+
<p className="text-sm text-zinc-500 dark:text-zinc-400">
112+
Create a new album and add the selected assets to it
113+
</p>
114+
<div className="flex flex-col gap-2">
115+
<Label>Album Name</Label>
116+
<Input
117+
placeholder="Album Name"
118+
onChange={(e) => {
119+
setFormData({ ...formData, albumName: e.target.value });
120+
}}
121+
/>
122+
</div>
123+
<div className="self-end">
124+
<Button disabled={loading} type="submit">
125+
{loading ? "Creating Album" : "Create Album"}
126+
</Button>
127+
</div>
128+
</form>
129+
)
130+
}, [loading, formData, handleSubmit]);
77131

78132
return (
79133
<Dialog open={open} onOpenChange={setOpen}>
80134
<DialogTrigger asChild>
81-
<Button size={"sm"}>Select Album</Button>
135+
<Button size={"sm"} disabled={!selectedIds.length}>Add to Album</Button>
82136
</DialogTrigger>
83137
<DialogContent>
84138
<DialogHeader>
@@ -87,7 +141,18 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
87141
Select the albums you want to add the selected assets to
88142
</DialogDescription>
89143
</DialogHeader>
90-
{renderContent()}
144+
<Tabs defaultValue="albums" className="w-full">
145+
<TabsList className="w-full">
146+
<TabsTrigger className="w-full" value="albums">Albums</TabsTrigger>
147+
<TabsTrigger className="w-full" value="create">Create</TabsTrigger>
148+
</TabsList>
149+
<TabsContent value="albums">
150+
{renderContent()}
151+
</TabsContent>
152+
<TabsContent value="create">
153+
{renderCreateContent()}
154+
</TabsContent>
155+
</Tabs>
91156
</DialogContent>
92157
</Dialog>
93158
);

0 commit comments

Comments
 (0)