fix: address PR review findings for widget-only integration refactor
- Fix npm scoped package support (filter time metadata keys, make description optional, exclude falsy releases from version selection) - Fix timetable date formatting (use Intl.DateTimeFormat for stable MM/DD/YYYY output) and named zod import - Guard migration 0000 against unknown provider keys - Fix migration 0003 to delete integration links by integrationId - Reset immich album carousel index when photo list changes - Add bounded limit to timetable router input - Remove stale misspelled noProviderSeleceted translation key - Fix provider name visibility in release widget settings - Update docs: add ArchiveTeam Warrior note, fix typos, correct showPhotoInfo default
This commit is contained in:
44 files changed
+46
-76
No files matched your search
@@ -48,11 +48,11 @@ You may refresh anytime and continue from a different device - the onboarding is
|
||||
### Starting from scratch
|
||||
|
||||
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
|
||||
If credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of it's boards.
|
||||
You must choose an username and password (both can later be changed).
|
||||
We enforce strict password requirements, when you enter your password, to protect your instance if you accidentially
|
||||
expose it to the Internet.
|
||||
If the credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of its boards.
|
||||
You must choose a username and password (both can later be changed).
|
||||
We enforce strict password requirements when you enter your password to protect your instance if you accidentally
|
||||
expose it to the internet.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepAdminUser}
|
||||
@@ -62,8 +62,8 @@ expose it to the Internet.
|
||||
|
||||
If you selected external providers (such as LDAP or OIDC), you'll be prompted to create an admin group.
|
||||
This group will be used to synchronize users from your provider to Homarr.
|
||||
When a user is logged in through the provider the groups of him that match groups with any in Homarr will be added to the user.
|
||||
The name of the group has to match exactly the name of the group in the provider.
|
||||
When a user logs in through the provider, any of their groups that match groups in Homarr will be assigned to the user.
|
||||
The name of the group must match exactly the name of the group in the provider.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepExternalAdminGroup}
|
||||
@@ -107,7 +107,7 @@ You can optionally auto-create bookmark apps for all discovered Docker container
|
||||
|
||||
:::note
|
||||
|
||||
Release providers and Search.ch are configured directly in their widgets instead of the integrations page.
|
||||
Release providers, Search.ch, and ArchiveTeam Warrior are configured directly in their widgets instead of the integrations page.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const immichAlbumCarouselWidget: WidgetDefinition = {
|
||||
name: "Show photo info",
|
||||
description: "Display date of the picture and the current index at the bottom of the slideshow",
|
||||
values: { type: "boolean" },
|
||||
defaultValue: "yes",
|
||||
defaultValue: "no",
|
||||
},
|
||||
{
|
||||
name: "Randomize photos",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const timetableRouter = createTRPCRouter({
|
||||
z.object({
|
||||
baseUrl: baseUrlSchema,
|
||||
stationId: z.string(),
|
||||
limit: z.number(),
|
||||
limit: z.number().int().min(1).max(100),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
|
||||
@@ -23,18 +23,19 @@ export async function migrateReleaseWidgetProviderToOptionsAsync(db: Database) {
|
||||
if (options.repositories.length === 0) continue;
|
||||
if (!options.repositories.some((repository) => "providerKey" in repository)) continue;
|
||||
|
||||
const updatedRepositories = options.repositories.map(
|
||||
({ providerKey, ...otherFields }: { providerKey: string; [key: string]: unknown }) => {
|
||||
// Ensure providerKey is camelCase
|
||||
const updatedRepositories = options.repositories
|
||||
.map(({ providerKey, ...otherFields }: { providerKey: string; [key: string]: unknown }) => {
|
||||
const provider = providerKey.charAt(0).toLowerCase() + providerKey.slice(1);
|
||||
const matchedProvider = releaseProviderKinds.find((kind) => kind === provider);
|
||||
if (!matchedProvider) return null;
|
||||
|
||||
return {
|
||||
id: createId(),
|
||||
provider: releaseProviderKinds.find((kind) => kind === provider),
|
||||
provider: matchedProvider,
|
||||
...otherFields,
|
||||
};
|
||||
},
|
||||
);
|
||||
})
|
||||
.filter((repo) => repo !== null);
|
||||
|
||||
updates.push({
|
||||
id: item.id,
|
||||
|
||||
@@ -54,21 +54,11 @@ export async function migrateWidgetOnlyIntegrationsToOptionsAsync(db: Database)
|
||||
.filter((item) => oldIntegrationById.has(item.integrationId))
|
||||
.map((item) => [item.itemId, oldIntegrationById.get(item.integrationId)]),
|
||||
);
|
||||
const oldIntegrationItemIdsByItemId = oldIntegrationItems
|
||||
.filter((item) => oldIntegrationById.has(item.integrationId))
|
||||
.reduce<Map<string, string[]>>((acc, item) => {
|
||||
const itemIds = acc.get(item.itemId) ?? [];
|
||||
itemIds.push(item.integrationId);
|
||||
acc.set(item.itemId, itemIds);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const existingItems = await db.query.items.findMany({
|
||||
where: (items, { inArray }) => inArray(items.kind, ["releases", "timetable"]),
|
||||
});
|
||||
|
||||
const updatedItems: { id: string; options: object }[] = [];
|
||||
const unlinkItemIds: string[] = [];
|
||||
|
||||
for (const item of existingItems) {
|
||||
const options = SuperJSON.parse<Record<string, unknown>>(item.options);
|
||||
@@ -89,12 +79,10 @@ export async function migrateWidgetOnlyIntegrationsToOptionsAsync(db: Database)
|
||||
};
|
||||
});
|
||||
updatedItems.push({ id: item.id, options: { ...options, repositories } });
|
||||
if (oldIntegrationItemIdsByItemId.has(item.id)) unlinkItemIds.push(item.id);
|
||||
}
|
||||
|
||||
if (item.kind === "timetable" && linkedIntegration?.kind === "searchCh") {
|
||||
updatedItems.push({ id: item.id, options: { ...options, baseUrl: linkedIntegration.url } });
|
||||
unlinkItemIds.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +93,7 @@ export async function migrateWidgetOnlyIntegrationsToOptionsAsync(db: Database)
|
||||
.where(eq(items.id, update.id));
|
||||
}
|
||||
|
||||
if (unlinkItemIds.length > 0) {
|
||||
await db.delete(integrationItems).where(inArray(integrationItems.itemId, unlinkItemIds));
|
||||
}
|
||||
|
||||
await db.delete(integrationItems).where(inArray(integrationItems.integrationId, oldIntegrationIds));
|
||||
await db.delete(integrations).where(inArray(integrations.id, oldIntegrationIds));
|
||||
|
||||
console.log(`Migrated widget-only integrations to widget options count="${updatedItems.length}"`);
|
||||
|
||||
@@ -46,8 +46,8 @@ export const getLatestRelease = (
|
||||
versionRegex?: string,
|
||||
): ReleaseProviderResponse | null => {
|
||||
const validReleases = releases.filter((result) => {
|
||||
if (result.latestRelease) return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true;
|
||||
return true;
|
||||
if (!result.latestRelease) return false;
|
||||
return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true;
|
||||
});
|
||||
|
||||
return validReleases.length === 0
|
||||
@@ -443,16 +443,20 @@ const getGitlabDetailsAsync = async (baseUrl: string, encodedIdentifier: string)
|
||||
};
|
||||
};
|
||||
|
||||
const npmTimeMetadataKeys = new Set(["created", "modified"]);
|
||||
|
||||
const npmReleasesSchema = z.object({
|
||||
time: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.string().transform((value) => new Date(value)),
|
||||
)
|
||||
.transform((version) =>
|
||||
Object.entries(version).map(([key, value]) => ({ latestRelease: key, latestReleaseAt: value })),
|
||||
.transform((entries) =>
|
||||
Object.entries(entries)
|
||||
.filter(([key]) => !npmTimeMetadataKeys.has(key))
|
||||
.map(([key, value]) => ({ latestRelease: key, latestReleaseAt: value })),
|
||||
),
|
||||
versions: z.record(z.string(), z.object({ description: z.string() })),
|
||||
versions: z.record(z.string(), z.object({ description: z.string().optional() })),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import dayjs from "dayjs";
|
||||
import z from "zod/v4";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
@@ -88,7 +88,18 @@ const getTimetableAsync = async (
|
||||
options: { stationId: string; limit: number },
|
||||
): Promise<Timetable> => {
|
||||
const now = new Date();
|
||||
const [date, time] = now.toLocaleString("en-CH", { timeZone: "Europe/Zurich" }).split(", ") as [string, string];
|
||||
const date = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: "Europe/Zurich",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
}).format(now);
|
||||
const time = new Intl.DateTimeFormat("en-GB", {
|
||||
timeZone: "Europe/Zurich",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(now);
|
||||
const response = await fetchWithTrustedCertificatesAsync(
|
||||
buildUrl(baseUrl, "/timetable/api/stationboard.json", {
|
||||
stop: options.stationId,
|
||||
@@ -96,7 +107,7 @@ const getTimetableAsync = async (
|
||||
show_delays: 1,
|
||||
show_tracks: 1,
|
||||
date,
|
||||
time: time.substring(0, 5),
|
||||
time,
|
||||
}),
|
||||
);
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "无效标识符",
|
||||
"noMatchingVersion": "没有找到匹配的版本",
|
||||
"noReleasesFound": "没有发现发布信息",
|
||||
"noProviderSeleceted": "没有选择提供者",
|
||||
"noProviderResponse": "提供者没有响应",
|
||||
"noProviderSelected": "没有选择提供者"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "crwdns3512:0crwdne3512:0",
|
||||
"noMatchingVersion": "crwdns3514:0crwdne3514:0",
|
||||
"noReleasesFound": "crwdns3516:0crwdne3516:0",
|
||||
"noProviderSeleceted": "crwdns3518:0crwdne3518:0",
|
||||
"noProviderResponse": "crwdns3520:0crwdne3520:0",
|
||||
"noProviderSelected": "crwdns3518:0crwdne3518:0"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Ugyldig identifikator",
|
||||
"noMatchingVersion": "Ingen matchende version fundet",
|
||||
"noReleasesFound": "Ingen udgivelser fundet",
|
||||
"noProviderSeleceted": "Ingen udbyder valgt",
|
||||
"noProviderResponse": "Intet svar fra udbyder",
|
||||
"noProviderSelected": "Ingen udbyder valgt"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Ungültige Kennung",
|
||||
"noMatchingVersion": "Keine passende Version gefunden",
|
||||
"noReleasesFound": "Keine Veröffentlichungen gefunden",
|
||||
"noProviderSeleceted": "Kein Anbieter ausgewählt",
|
||||
"noProviderResponse": "Keine Antwort vom Anbieter",
|
||||
"noProviderSelected": "Kein Anbieter ausgewählt"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Identificador no válido",
|
||||
"noMatchingVersion": "No se encontró una versión correspondiente",
|
||||
"noReleasesFound": "No se encontraron publicaciones",
|
||||
"noProviderSeleceted": "No se ha seleccionado ningún proveedor",
|
||||
"noProviderResponse": "No hay respuesta del proveedor",
|
||||
"noProviderSelected": "No se ha seleccionado ningún proveedor"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "Toimittajaa ei ole valittu",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": "Toimittajaa ei ole valittu"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Identifiant non valide",
|
||||
"noMatchingVersion": "Aucune version correspondante trouvée",
|
||||
"noReleasesFound": "Aucune publication trouvée",
|
||||
"noProviderSeleceted": "Aucun fournisseur sélectionné",
|
||||
"noProviderResponse": "Aucune réponse du fournisseur",
|
||||
"noProviderSelected": "Aucun fournisseur sélectionné"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "מזהה לא חוקי",
|
||||
"noMatchingVersion": "לא נמצאה גרסה תואמת",
|
||||
"noReleasesFound": "לא נמצאו שיחרורים",
|
||||
"noProviderSeleceted": "לא נבחר ספק",
|
||||
"noProviderResponse": "אין תשובה מהספק",
|
||||
"noProviderSelected": "לא נבחר ספק"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Identificatore non valido",
|
||||
"noMatchingVersion": "Nessuna versione corrispondente trovata",
|
||||
"noReleasesFound": "Nessun rilascio trovato",
|
||||
"noProviderSeleceted": "Nessun provider selezionato",
|
||||
"noProviderResponse": "Nessuna risposta dal provider",
|
||||
"noProviderSelected": "Nessun provider selezionato"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "無効な識別子",
|
||||
"noMatchingVersion": "一致するバージョンが見つかりません",
|
||||
"noReleasesFound": "リリースが見つかりませんでした",
|
||||
"noProviderSeleceted": "プロバイダーが選択されていません",
|
||||
"noProviderResponse": "プロバイダーからの応答がありません",
|
||||
"noProviderSelected": "プロバイダーが選択されていません"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Ongeldige identificator",
|
||||
"noMatchingVersion": "Geen overeenkomende versie gevonden",
|
||||
"noReleasesFound": "Geen uitgaves gevonden",
|
||||
"noProviderSeleceted": "Geen aanbieder geselecteerd",
|
||||
"noProviderResponse": "Geen reactie van aanbieder",
|
||||
"noProviderSelected": "Geen aanbieder geselecteerd"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Ugyldig identifikator",
|
||||
"noMatchingVersion": "Ingen samsvarende versjon funnet",
|
||||
"noReleasesFound": "Ingen utgivelser funnet",
|
||||
"noProviderSeleceted": "Ingen leverandør valgt",
|
||||
"noProviderResponse": "Ingen respons fra leverandør",
|
||||
"noProviderSelected": "Ingen leverandør valgt"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Nieprawidłowy identyfikator",
|
||||
"noMatchingVersion": "Nie znaleziono pasującej wersji",
|
||||
"noReleasesFound": "Nie znaleziono wydań!",
|
||||
"noProviderSeleceted": "Nie wybrano dostawcy",
|
||||
"noProviderResponse": "Brak odpowiedzi od dostawcy",
|
||||
"noProviderSelected": "Nie wybrano dostawcy"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Identificador inválido",
|
||||
"noMatchingVersion": "Nenhuma versão correspondente encontrada",
|
||||
"noReleasesFound": "Nenhum lançamento encontrado",
|
||||
"noProviderSeleceted": "Nenhum provedor selecionado",
|
||||
"noProviderResponse": "Nenhuma resposta do provedor",
|
||||
"noProviderSelected": "Nenhum provedor selecionado"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Неверный идентификатор",
|
||||
"noMatchingVersion": "Подходящая версия не найдена",
|
||||
"noReleasesFound": "Релизы не найдены",
|
||||
"noProviderSeleceted": "Провайдер не выбран",
|
||||
"noProviderResponse": "Нет ответа от провайдера",
|
||||
"noProviderSelected": "Провайдер не выбран"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "Nenašla sa žiadna zodpovedajúca verzia",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "Geçersiz tanımlayıcı",
|
||||
"noMatchingVersion": "Eşleşen sürüm bulunamadı",
|
||||
"noReleasesFound": "Hiçbir sürüm bulunamadı",
|
||||
"noProviderSeleceted": "Hiçbir sağlayıcı seçilmedi",
|
||||
"noProviderResponse": "Sağlayıcıdan yanıt alınamadı",
|
||||
"noProviderSelected": "Hiçbir sağlayıcı seçilmedi"
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -3205,7 +3205,6 @@
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "找不到匹配的版本",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": "",
|
||||
"noProviderSelected": ""
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text c="dimmed" fw={100} size="xs">
|
||||
<Text c="dimmed" size="xs">
|
||||
{providerName}
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ export default function ImmichAlbumCarouselWidget({
|
||||
return options.randomizePhotos ? shuffle(assets) : assets;
|
||||
}, [album?.assets, options.randomizePhotos]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPhotoIndex(0);
|
||||
}, [photoAssets]);
|
||||
|
||||
if (!options.albumId) {
|
||||
return <NoAlbumSelected />;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user