generated from Dicken/dickendock
round19: Startseite aufgeraeumt, Favicon-Fallback ueber Ports, Favicon-Picker, Live-Status per Ping
This commit is contained in:
@@ -7,6 +7,7 @@ import { Button, Favicon } from "@launchpad/ui";
|
||||
import type { Bookmark } from "@launchpad/shared";
|
||||
import { suggestBookmarkCategory } from "@launchpad/shared";
|
||||
import { useBookmarks } from "../../hooks/useBookmarks.js";
|
||||
import { useServices } from "../../hooks/useServices.js";
|
||||
import { useCategories } from "../../hooks/useCategories.js";
|
||||
import { AdminPageHeader } from "./AdminPageHeader.js";
|
||||
|
||||
@@ -205,6 +206,8 @@ const EDIT_FORM_COLSPAN = 7;
|
||||
|
||||
function EditForm({ bookmark, onDone }: { bookmark: Bookmark; onDone: () => void }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: allBookmarks } = useBookmarks();
|
||||
const { data: allServices } = useServices();
|
||||
const [displayName, setDisplayName] = useState(bookmark.displayName);
|
||||
const [url, setUrl] = useState(bookmark.url);
|
||||
const [category, setCategory] = useState(bookmark.category ?? "");
|
||||
@@ -213,8 +216,20 @@ function EditForm({ bookmark, onDone }: { bookmark: Bookmark; onDone: () => void
|
||||
|
||||
const [favicon, setFavicon] = useState<string | null | undefined>(undefined);
|
||||
const [faviconError, setFaviconError] = useState<string | null>(null);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const FAVICON_MAX_BYTES = 300 * 1024;
|
||||
|
||||
const existingFavicons = useMemo(() => {
|
||||
const seen = new Map<string, string>();
|
||||
for (const b of allBookmarks ?? []) {
|
||||
if (b.favicon && b.id !== bookmark.id && !seen.has(b.favicon)) seen.set(b.favicon, b.displayName);
|
||||
}
|
||||
for (const s of allServices ?? []) {
|
||||
if (s.favicon && !seen.has(s.favicon)) seen.set(s.favicon, s.displayName);
|
||||
}
|
||||
return Array.from(seen.entries());
|
||||
}, [allBookmarks, allServices, bookmark.id]);
|
||||
|
||||
function handleFaviconFile(file: File | undefined) {
|
||||
setFaviconError(null);
|
||||
if (!file) return;
|
||||
@@ -268,6 +283,16 @@ function EditForm({ bookmark, onDone }: { bookmark: Bookmark; onDone: () => void
|
||||
onChange={(e) => handleFaviconFile(e.target.files?.[0])}
|
||||
/>
|
||||
</label>
|
||||
{existingFavicons.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerOpen((v) => !v)}
|
||||
className="rounded-lg border border-black/10 px-2 py-1 text-xs text-black/70
|
||||
hover:bg-black/5 dark:border-white/10 dark:text-white/70 dark:hover:bg-white/10"
|
||||
>
|
||||
Vorhandenes wählen
|
||||
</button>
|
||||
) : null}
|
||||
{faviconPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -279,6 +304,25 @@ function EditForm({ bookmark, onDone }: { bookmark: Bookmark; onDone: () => void
|
||||
) : null}
|
||||
</div>
|
||||
{faviconError ? <p className="mt-1 text-xs text-red-500">{faviconError}</p> : null}
|
||||
{pickerOpen ? (
|
||||
<div className="mt-2 flex max-w-xs flex-wrap gap-1.5 rounded-lg border border-black/10 p-2
|
||||
dark:border-white/10">
|
||||
{existingFavicons.map(([iconUrl, name]) => (
|
||||
<button
|
||||
key={iconUrl}
|
||||
type="button"
|
||||
title={name}
|
||||
onClick={() => {
|
||||
setFavicon(iconUrl);
|
||||
setPickerOpen(false);
|
||||
}}
|
||||
className="rounded p-0.5 hover:bg-black/5 dark:hover:bg-white/10"
|
||||
>
|
||||
<Favicon src={iconUrl} fallbackLetter={name} size="sm" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs text-black/50 dark:text-white/50">Name</label>
|
||||
|
||||
Reference in New Issue
Block a user