'use client'; import { useState } from 'react'; import { downloadMedia } from '../lib/download'; import { Icon } from '../icons'; import { useGallery, useGalleryStoreApi } from '../store/context'; import type { AlbumId, MediaId, ShareRecord, ViewId } from '../types'; import { closeModal, openModal } from './Modal'; /* ----------------------------- Rename / name prompt ----------------------------- */ function NamePrompt({ title, initial, confirmLabel, placeholder, onConfirm, }: { title: string; initial: string; confirmLabel: string; placeholder?: string; onConfirm: (name: string) => void; }) { const [value, setValue] = useState(initial); const submit = () => { const trimmed = value.trim(); if (trimmed) onConfirm(trimmed); closeModal(); }; return (
{title}
setValue(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') submit(); }} />
); } export function promptAlbumName( title: string, initial: string, onConfirm: (name: string) => void, opts?: { placeholder?: string; confirmLabel?: string }, ) { const confirmLabel = opts?.confirmLabel ?? (title.toLowerCase().includes('rename') ? 'Rename' : 'Create'); openModal( , ); } /* ----------------------------- Album picker ----------------------------- */ function AlbumPickerModal({ ids, mode = 'copy', fromAlbumId, }: { ids: MediaId[]; mode?: 'copy' | 'move'; fromAlbumId?: string; }) { const api = useGalleryStoreApi(); const albums = useGallery((s) => s.albums.filter((a) => (a.kind === 'user' || a.kind === 'folder') && a.id !== fromAlbumId), ); const moving = mode === 'move' && !!fromAlbumId; const place = (albumId: string) => { if (moving) api.getState().moveToAlbum(fromAlbumId!, albumId, ids); else api.getState().addToAlbum(albumId, ids); closeModal(); }; const createAndPlace = () => { closeModal(); promptAlbumName('New Album', '', (name) => { const id = api.getState().createAlbum(name); if (moving) api.getState().moveToAlbum(fromAlbumId!, id, ids); else api.getState().addToAlbum(id, ids); api.getState().setView(`album:${id}` as ViewId); }); }; return (
{moving ? 'Move' : 'Add'} {ids.length} item{ids.length === 1 ? '' : 's'} to…
{albums.map((a) => ( ))} {albums.length === 0 ? (
No albums yet — create one above.
) : null}
); } export function addToAlbumPicker(ids: MediaId[]) { if (!ids.length) return; openModal(); } /** Move items out of `fromAlbumId` into a chosen album. */ export function moveToAlbumPicker(fromAlbumId: string, ids: MediaId[]) { if (!ids.length) return; openModal(); } /* ----------------------------- Confirm dialog ----------------------------- */ export function confirmAction(opts: { title: string; message: string; confirmLabel?: string; danger?: boolean; onConfirm: () => void; }) { openModal(
{opts.title}
{opts.message}
, ); } /* ----------------------------- Security (lock) settings ----------------------------- */ function SecuritySettingsModal() { const api = useGalleryStoreApi(); const hasPw = useGallery((s) => s.lockConfigured); // A server-backed lock reads/writes the host's backend, not this device. const serverBacked = useGallery((s) => Boolean(s.config.lockProvider)); const [p1, setP1] = useState(''); const [p2, setP2] = useState(''); const [err, setErr] = useState(null); const [busy, setBusy] = useState(false); const save = async () => { if (p1.trim().length < 4) return setErr('Use at least 4 characters.'); if (p1 !== p2) return setErr('Passwords do not match.'); setBusy(true); const state = api.getState(); state.clearLockError(); await state.setLockPassword(p1.trim()); setBusy(false); // The store reports an unreachable provider rather than throwing. if (api.getState().lockError === 'unavailable') { setErr("Couldn't reach the server. Try again."); return; } closeModal(); }; const remove = async () => { setBusy(true); const state = api.getState(); state.clearLockError(); await state.removeLockPassword(); setBusy(false); if (api.getState().lockError === 'unavailable') { setErr("Couldn't reach the server. Try again."); return; } closeModal(); }; return (
{hasPw ? 'Recently Deleted is Locked' : 'Lock Recently Deleted'}
{serverBacked ? hasPw ? 'Set a new password, or remove the lock. It applies to your account on every device.' : 'Protect Recently Deleted with a password. It applies to your account on every device.' : hasPw ? 'Set a new password, or remove the lock. The password is stored only on this device.' : 'Protect Recently Deleted with a password. It is stored only on this device (not uploaded).'}
{ setP1(e.target.value); setErr(null); }} /> { setP2(e.target.value); setErr(null); }} onKeyDown={(e) => { if (e.key === 'Enter') void save(); }} /> {err ?
{err}
: null}
{hasPw ? ( ) : null}
); } export function openSecuritySettings() { openModal(); } /* ----------------------------- Share ----------------------------- */ function ShareModal({ selectionIds, albumId }: { selectionIds: MediaId[]; albumId?: AlbumId }) { const api = useGalleryStoreApi(); const albums = useGallery((s) => s.albums.filter((a) => a.kind === 'user' || a.kind === 'folder')); const media = useGallery((s) => s.media); const [picked, setPicked] = useState>(new Set(albumId ? [albumId] : [])); const [created, setCreated] = useState(null); const [copied, setCopied] = useState(false); const albumMediaIds = (id: AlbumId) => media.filter((m) => !m.deletedAt && m.albumIds.includes(id)).map((m) => m.id); const finish = (share: ShareRecord) => { setCreated(share); void navigator.clipboard ?.writeText(share.url) .then(() => setCopied(true)) .catch(() => setCopied(false)); }; const sharePhotos = () => { finish(api.getState().createShare(selectionIds.length === 1 ? 'photo' : 'photos', selectionIds)); }; const shareAlbums = () => { const ids = [...picked]; if (ids.length === 0) return; const union = [...new Set(ids.flatMap(albumMediaIds))]; finish(api.getState().createShare('album', union, ids.length === 1 ? ids[0] : undefined)); }; const togglePick = (id: AlbumId) => setPicked((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); const download = () => { const ids = created?.mediaIds ?? []; for (const id of ids) { const m = media.find((x) => x.id === id); if (m) downloadMedia(m); } }; if (created) { return (
Link Ready
“{created.title}” — anyone with this link can view {created.mediaIds.length} item {created.mediaIds.length === 1 ? '' : 's'}.
e.currentTarget.select()} />
); } return (
Share
{selectionIds.length > 0 ? ( ) : null}
Or share album{albums.length === 1 ? '' : 's'} (tick one or more):
{albums.map((a) => ( ))} {albums.length === 0 ? (
No albums yet.
) : null}
); } /** Open the Share modal. Pass selected media ids and/or the current album id. */ export function openShareModal(selectionIds: MediaId[] = [], albumId?: AlbumId) { openModal(); }