diff --git a/src/app/components/image-pack-view/ImagePackContent.tsx b/src/app/components/image-pack-view/ImagePackContent.tsx
index cff18ae6..bec2d3b2 100644
--- a/src/app/components/image-pack-view/ImagePackContent.tsx
+++ b/src/app/components/image-pack-view/ImagePackContent.tsx
@@ -1,244 +1,20 @@
-import React, { FormEventHandler, useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
+import { as, Box, Text, config, Button, Menu } from 'folds';
import {
- as,
- Box,
- Text,
- config,
- Avatar,
- AvatarImage,
- AvatarFallback,
- Button,
- Icon,
- Icons,
- Input,
- TextArea,
- Chip,
- Menu,
-} from 'folds';
-import Linkify from 'linkify-react';
-import { ImagePack, ImageUsage, packMetaEqual, PackMetaReader } from '../../plugins/custom-emoji';
-import { mxcUrlToHttp } from '../../utils/matrix';
+ ImagePack,
+ ImageUsage,
+ PackImageReader,
+ packMetaEqual,
+ PackMetaReader,
+} from '../../plugins/custom-emoji';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
import { SequenceCard } from '../sequence-card';
-import { nameInitials } from '../../utils/common';
-import { BreakWord } from '../../styles/Text.css';
-import { LINKIFY_OPTS } from '../../plugins/react-custom-html-parser';
-import { ContainerColor } from '../../styles/ContainerColor.css';
-import { ImageTile } from './ImageTile';
+import { ImageTile, ImageTileEdit } from './ImageTile';
import { SettingTile } from '../setting-tile';
import { UsageSwitcher } from './UsageSwitcher';
-import { useFilePicker } from '../../hooks/useFilePicker';
-import { useObjectURL } from '../../hooks/useObjectURL';
-import { createUploadAtom, UploadSuccess } from '../../state/upload';
-import { CompactUploadCardRenderer } from '../upload-card';
+import { ImagePackProfile, ImagePackProfileEdit } from './PackMeta';
import * as css from './style.css';
-type ImagePackAvatarProps = {
- url?: string;
- name?: string;
-};
-function ImagePackAvatar({ url, name }: ImagePackAvatarProps) {
- return (
-
- {url ? (
-
- ) : (
-
- {nameInitials(name ?? 'Unknown')}
-
- )}
-
- );
-}
-
-type ImagePackProfileProps = {
- meta: PackMetaReader;
- canEdit?: boolean;
- onEdit?: () => void;
-};
-function ImagePackProfile({ meta, canEdit, onEdit }: ImagePackProfileProps) {
- const mx = useMatrixClient();
- const useAuthentication = useMediaAuthentication();
- const avatarUrl = meta.avatar
- ? mxcUrlToHttp(mx, meta.avatar, useAuthentication) ?? undefined
- : undefined;
-
- return (
-
-
-
-
- {meta.name ?? 'Unknown'}
-
- {meta.attribution && (
-
- {meta.attribution}
-
- )}
-
- {canEdit && (
-
- }
- onClick={onEdit}
- outlined
- >
- Edit
-
-
- )}
-
-
-
-
-
- );
-}
-
-type ImagePackProfileEditProps = {
- meta: PackMetaReader;
- onCancel: () => void;
- onSave: (meta: PackMetaReader) => void;
-};
-function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfileEditProps) {
- const mx = useMatrixClient();
- const useAuthentication = useMediaAuthentication();
- const [avatar, setAvatar] = useState(meta.avatar);
-
- const avatarUrl = avatar ? mxcUrlToHttp(mx, avatar, useAuthentication) ?? undefined : undefined;
-
- const [imageFile, setImageFile] = useState();
- const avatarFileUrl = useObjectURL(imageFile);
- const uploadingAvatar = avatarFileUrl ? avatar === meta.avatar : false;
- const uploadAtom = useMemo(() => {
- if (imageFile) return createUploadAtom(imageFile);
- return undefined;
- }, [imageFile]);
-
- const pickFile = useFilePicker(setImageFile, false);
-
- const handleRemoveUpload = useCallback(() => {
- setImageFile(undefined);
- setAvatar(meta.avatar);
- }, [meta.avatar]);
-
- const handleUploaded = useCallback((upload: UploadSuccess) => {
- setAvatar(upload.mxc);
- }, []);
-
- const handleSubmit: FormEventHandler = (evt) => {
- evt.preventDefault();
- if (uploadingAvatar) return;
-
- const target = evt.target as HTMLFormElement | undefined;
- const nameInput = target?.nameInput as HTMLInputElement | undefined;
- const attributionTextArea = target?.attributionTextArea as HTMLTextAreaElement | undefined;
- if (!nameInput || !attributionTextArea) return;
-
- const name = nameInput.value.trim();
- const attribution = attributionTextArea.value.trim();
- if (!name) return;
-
- const metaReader = new PackMetaReader({
- avatar_url: avatar,
- display_name: name,
- attribution,
- });
- onSave(metaReader);
- };
-
- return (
-
-
-
- Pack Avatar
- {uploadAtom ? (
-
-
-
- ) : (
-
-
- {!avatar && meta.avatar && (
-
- )}
- {avatar && (
-
- )}
-
- )}
-
-
-
-
-
-
- Name
-
-
-
- Attribution
-
-
-
-
-
-
-
- );
-}
-
export type ImagePackContentProps = {
imagePack: ImagePack;
canEdit?: boolean;
@@ -250,13 +26,17 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
const images = useMemo(() => Array.from(imagePack.images.collection.values()), [imagePack]);
const [metaEditing, setMetaEditing] = useState(false);
- const [unsavedMeta, setUnsavedMeta] = useState();
- const currentMeta = unsavedMeta ?? imagePack.meta;
+ const [savedMeta, setSavedMeta] = useState();
+ const currentMeta = savedMeta ?? imagePack.meta;
+
+ const [imagesEditing, setImagesEditing] = useState>(new Set());
+ const [savedImages, setSavedImages] = useState