feat(wallpapers): add image crop support and i18n strings

Adds per-image image-crop support with persistent settings.
Rendering adapts to crop mode, zoom and position per image.
Locales the UI strings and notifications; app name updated.

- Cropping workflow with per-image crop state and rendering
- Localizes strings and notifications for en and de
- Introduces set_image_crop command and its permission schema
This commit is contained in:
2026-08-20 23:26:15 +02:00
parent 2cfc7c5dcb
commit 197090ddd2
23 changed files with 622 additions and 74 deletions
+28 -5
View File
@@ -13,6 +13,10 @@ export type GalleryImage = {
id: string;
url: string;
selected: boolean;
cropMode: "cover" | "contain";
cropZoom: number;
cropPositionX: number;
cropPositionY: number;
};
export type GalleryPage = {
@@ -30,6 +34,8 @@ const demoState: WallpaperState = {
};
const inTauri = () => "__TAURI_INTERNALS__" in window;
const demoCrops = new Map<string, Pick<GalleryImage, "cropMode" | "cropZoom" | "cropPositionX" | "cropPositionY">>();
const defaultCrop = { cropMode: "cover" as const, cropZoom: 1, cropPositionX: 0.5, cropPositionY: 0.5 };
export async function getState(): Promise<WallpaperState> {
return inTauri() ? invoke<WallpaperState>("plugin:wallpaper|get_state") : demoState;
@@ -39,11 +45,10 @@ export async function getGallery(offset = 0, limit = 48): Promise<GalleryPage> {
if (inTauri()) return invoke<GalleryPage>("plugin:wallpaper|get_gallery", { offset, limit });
return {
total: demoState.imageUrls.length,
items: demoState.imageUrls.slice(offset, offset + limit).map((url, index) => ({
id: `demo-${offset + index}`,
url,
selected: offset + index === demoState.currentIndex,
})),
items: demoState.imageUrls.slice(offset, offset + limit).map((url, index) => {
const id = `demo-${offset + index}`;
return { id, url, selected: offset + index === demoState.currentIndex, ...(demoCrops.get(id) ?? defaultCrop) };
}),
};
}
@@ -63,6 +68,24 @@ export async function deleteImage(id: string): Promise<WallpaperState> {
return { ...demoState, imageUrls: [...demoState.imageUrls] };
}
export async function setImageCrop(image: GalleryImage): Promise<GalleryImage> {
const payload = {
id: image.id,
mode: image.cropMode,
zoom: image.cropZoom,
positionX: image.cropPositionX,
positionY: image.cropPositionY,
};
if (inTauri()) return invoke<GalleryImage>("plugin:wallpaper|set_image_crop", payload);
demoCrops.set(image.id, {
cropMode: image.cropMode,
cropZoom: image.cropZoom,
cropPositionX: image.cropPositionX,
cropPositionY: image.cropPositionY,
});
return { ...image };
}
export async function setSetting(name: "enabled" | "shuffle" | "lockScreenOnly", value: boolean) {
if (!inTauri()) return { ...demoState, [name]: value };
return invoke<WallpaperState>("plugin:wallpaper|set_setting", { name, value });