Merge pull request #305 from 11cafe/fix-gallery
cleanup code, display all images search res
This commit is contained in:
@@ -163,7 +163,7 @@ export namespace TwowaySyncAPI {
|
||||
name: name,
|
||||
json: JSON.stringify(jsonObj),
|
||||
};
|
||||
console.log("createWorkflow", input);
|
||||
|
||||
try {
|
||||
const response = await fetch("/workspace/create_workflow_file", {
|
||||
method: "POST",
|
||||
|
||||
@@ -30,11 +30,13 @@ export default function MediaPreview({
|
||||
? `/workspace/preview_media?filename=${mediaLocalPath}`
|
||||
: `/workspace/view_media?filename=${mediaLocalPath}`,
|
||||
);
|
||||
if (!response.ok) throw new Error("Media not found");
|
||||
setIsVisible(true);
|
||||
if (response.status == 404) {
|
||||
setIsVisible(false);
|
||||
onBrokenLink?.();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
setIsVisible(false);
|
||||
onBrokenLink?.();
|
||||
console.error("Error checking media exists", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { CSSProperties } from "react";
|
||||
type Props = {
|
||||
searchValue: string;
|
||||
onUpdateSearchValue: (value: string) => void;
|
||||
placeholder?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
const IconSearchStyle: CSSProperties = {
|
||||
@@ -18,11 +20,11 @@ const IconSearchStyle: CSSProperties = {
|
||||
};
|
||||
|
||||
export default function SearchInput(props: Props) {
|
||||
const { searchValue, onUpdateSearchValue } = props;
|
||||
const { searchValue, onUpdateSearchValue, style } = props;
|
||||
const isSearchValueNotEmpty = searchValue !== "";
|
||||
|
||||
return (
|
||||
<Box style={{ position: "relative" }} mb={2}>
|
||||
<Box style={{ position: "relative", ...style }} mb={2}>
|
||||
<IconSearch style={IconSearchStyle} />
|
||||
{isSearchValueNotEmpty && (
|
||||
<IconButton
|
||||
@@ -40,7 +42,7 @@ export default function SearchInput(props: Props) {
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
placeholder="Search"
|
||||
placeholder={props.placeholder ?? "Search"}
|
||||
size={"sm"}
|
||||
paddingLeft={"35px"}
|
||||
paddingBlock={"5px"}
|
||||
|
||||
@@ -64,6 +64,13 @@ export class TableBase<T extends TableBaseModel> {
|
||||
return list as any[];
|
||||
}
|
||||
|
||||
async filter(filterFn: (obj: T) => boolean): Promise<T[]> {
|
||||
const list = await indexdb[this.tableName]
|
||||
.filter(filterFn as any)
|
||||
.toArray();
|
||||
return list as any[];
|
||||
}
|
||||
|
||||
public async batchQuery(ids: string[]): Promise<T[]> {
|
||||
const list = await indexdb[this.tableName].bulkGet(ids);
|
||||
return list as any[];
|
||||
|
||||
@@ -20,9 +20,10 @@ import { IconArrowLeft, IconX } from "@tabler/icons-react";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import { Media } from "../types/dbTypes";
|
||||
import { MetaDataInfo } from "./components/GalleryLeftCarousel.tsx";
|
||||
import GalleryMediaItem from "./components/GalleryMediaItem.tsx";
|
||||
import SearchInput from "../components/SearchInput.tsx";
|
||||
import { MediaWithMetaData } from "./components/GalleryRightTopbar.tsx";
|
||||
import GalleryGridView from "./components/GalleryGridView.tsx";
|
||||
import { useDebounce } from "../customHooks/useDebounce.ts";
|
||||
import SearchInput from "../components/SearchInput.tsx";
|
||||
|
||||
export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
const { curFlowID } = useContext(WorkspaceContext);
|
||||
@@ -33,9 +34,7 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
const [images, setImages] = useState<Media[]>([]);
|
||||
const [metaData, setMetaData] = useState<MediaWithMetaData>();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const onUpdateSearchValue = (val: string) => {
|
||||
setSearchValue(val);
|
||||
};
|
||||
const debounceSearchValue = useDebounce(searchValue, 300);
|
||||
|
||||
const loadData = async () => {
|
||||
if (curFlowID == null) return;
|
||||
@@ -57,28 +56,10 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const calcImages = searchValue.length
|
||||
? images?.filter((v) =>
|
||||
/*!v?.workflowJSON || */ v.workflowJSON?.includes(searchValue ?? ""),
|
||||
)
|
||||
: images;
|
||||
|
||||
if (curFlowID == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClickMedia = (media: Media) => {
|
||||
if (isSelecting) {
|
||||
if (selectedID.includes(media.id)) {
|
||||
setSelectedID(selectedID.filter((id) => id !== media.id));
|
||||
} else {
|
||||
setSelectedID([...selectedID, media.id]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setMetaData(media);
|
||||
// window.open(`/workspace/view_media?filename=${media.localPath}`);
|
||||
};
|
||||
const isAllSelected =
|
||||
images.length > 0 && selectedID.length === images.length;
|
||||
|
||||
@@ -90,13 +71,14 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
<HStack gap={2} mb={2}>
|
||||
<Heading size={"md"} mr={2}>
|
||||
Gallery - {workflowName}
|
||||
<Flex gap={2} display={"inline-flex"} ml={2}>
|
||||
{/* <SearchInput
|
||||
searchValue={searchValue}
|
||||
onUpdateSearchValue={onUpdateSearchValue}
|
||||
/> */}
|
||||
</Flex>
|
||||
</Heading>
|
||||
|
||||
<SearchInput
|
||||
searchValue={searchValue}
|
||||
onUpdateSearchValue={(val) => setSearchValue(val)}
|
||||
placeholder="Search prompt, model name, etc."
|
||||
style={{ width: "300px" }}
|
||||
/>
|
||||
</HStack>
|
||||
{isSelecting && (
|
||||
<HStack gap={3}>
|
||||
@@ -124,26 +106,11 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody overflowY={"auto"}>
|
||||
{/* {!metaData ? (
|
||||
<HStack wrap={"wrap"}>
|
||||
{calcImages.map((media) => {
|
||||
return (
|
||||
<GalleryMediaItem
|
||||
key={media.id}
|
||||
selectedID={selectedID}
|
||||
media={media}
|
||||
isSelecting={isSelecting}
|
||||
onClickMedia={onClickMedia}
|
||||
onRefreshImagesList={loadData}
|
||||
coverPath={coverPath}
|
||||
setCoverPath={setCoverPath}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
) : ( */}
|
||||
<MetaDataInfo mediaList={images} media={metaData ?? null} />
|
||||
{/* )} */}
|
||||
{debounceSearchValue.length ? (
|
||||
<GalleryGridView searchQuery={debounceSearchValue} />
|
||||
) : (
|
||||
<MetaDataInfo mediaList={images} media={metaData ?? null} />
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import GalleryMediaItem from "./GalleryMediaItem";
|
||||
import { HStack } from "@chakra-ui/react";
|
||||
import type { Media } from "../../types/dbTypes";
|
||||
import { mediaTable } from "../../db-tables/WorkspaceDB";
|
||||
|
||||
export default function GalleryGridView({
|
||||
searchQuery,
|
||||
}: {
|
||||
searchQuery: string;
|
||||
}) {
|
||||
const [medias, setMedias] = useState<Media[]>([]);
|
||||
useEffect(() => {
|
||||
if (searchQuery === "") {
|
||||
mediaTable?.listAll().then((data) => {
|
||||
setMedias(data.slice(0, 50) ?? []);
|
||||
});
|
||||
} else {
|
||||
mediaTable
|
||||
?.filter((v) => v.workflowJSON?.includes(searchQuery) ?? false)
|
||||
.then((data) => {
|
||||
setMedias(data);
|
||||
});
|
||||
}
|
||||
}, [searchQuery]);
|
||||
|
||||
const onClickMedia = (media: Media) => {};
|
||||
return (
|
||||
<HStack wrap={"wrap"}>
|
||||
{medias.map((media) => {
|
||||
return (
|
||||
<GalleryMediaItem
|
||||
key={media.id}
|
||||
selectedID={[]}
|
||||
media={media}
|
||||
isSelecting={false}
|
||||
onClickMedia={onClickMedia}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Box, Flex, Grid, Image } from "@chakra-ui/react";
|
||||
import Carousel from "../../components/Carousel/Carousel.tsx";
|
||||
import { MetaInfoBox } from "./GalleryRightTopbar.tsx";
|
||||
import MediaPreview from "../../components/MediaPreview.tsx";
|
||||
import { mediaTable } from "../../db-tables/WorkspaceDB.ts";
|
||||
|
||||
interface MetaDataInfoProps {
|
||||
media: Media | null;
|
||||
@@ -53,6 +54,10 @@ export const MetaDataInfo: FC<MetaDataInfoProps> = ({ mediaList, media }) => {
|
||||
mediaLocalPath={media.localPath}
|
||||
size={GALLERY_IMAGE_SIZE}
|
||||
objectFit="contain"
|
||||
hideBrokenImage
|
||||
onBrokenLink={() => {
|
||||
mediaTable?.delete(media.id);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -27,9 +27,6 @@ interface GalleryMediaItemProps {
|
||||
isSelecting: boolean;
|
||||
selectedID: string[];
|
||||
onClickMedia: (media: Media) => void;
|
||||
onRefreshImagesList: () => void;
|
||||
coverPath: string;
|
||||
setCoverPath: (path: string) => void;
|
||||
}
|
||||
|
||||
const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
@@ -37,9 +34,6 @@ const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
isSelecting,
|
||||
selectedID,
|
||||
onClickMedia,
|
||||
onRefreshImagesList,
|
||||
coverPath,
|
||||
setCoverPath,
|
||||
}) => {
|
||||
const IMAGE_SIZE = 180; // Define your image size
|
||||
const { curFlowID, loadFilePath } = useContext(WorkspaceContext);
|
||||
@@ -47,7 +41,6 @@ const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
if (media.localPath == null) {
|
||||
return null;
|
||||
}
|
||||
const isCover = coverPath != null && coverPath === media.localPath;
|
||||
if (curFlowID == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -65,8 +58,9 @@ const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
mediaLocalPath={media.localPath}
|
||||
size={IMAGE_SIZE}
|
||||
onBrokenLink={() => {
|
||||
// setIsVisible(false);
|
||||
mediaTable?.delete(media.id);
|
||||
}}
|
||||
hideBrokenImage
|
||||
autoPlay={true}
|
||||
isPreview
|
||||
/>
|
||||
@@ -84,20 +78,6 @@ const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<HStack hidden={isSelecting} gap={0}>
|
||||
<Tooltip label="Set as cover">
|
||||
<IconButton
|
||||
size={"sm"}
|
||||
variant={"ghost"}
|
||||
icon={isCover ? <IconPinFilled size={19} /> : <IconPin size={19} />}
|
||||
aria-label="set as cover"
|
||||
onClick={() => {
|
||||
workflowsTable?.updateMetaInfo(curFlowID, {
|
||||
coverMediaPath: media.localPath,
|
||||
});
|
||||
setCoverPath(media.localPath);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
flexGrow={1}
|
||||
onClick={() =>
|
||||
@@ -146,13 +126,6 @@ const GalleryMediaItem: React.FC<GalleryMediaItemProps> = ({
|
||||
);
|
||||
if (!res) return;
|
||||
await mediaTable?.delete(media.id);
|
||||
if (isCover) {
|
||||
await workflowsTable?.updateMetaInfo(curFlowID, {
|
||||
coverMediaPath: undefined,
|
||||
});
|
||||
setCoverPath("");
|
||||
}
|
||||
onRefreshImagesList();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
import { IconDownload, IconPin, IconPinFilled } from "@tabler/icons-react";
|
||||
import { formatTimestamp } from "../../utils.tsx";
|
||||
import MetaBox from "./MetaBox/GalleryRightMetadataForm.tsx";
|
||||
|
||||
@@ -46,6 +46,20 @@ export const MetaInfoBox = ({ media }: { media?: MediaWithMetaData }) => {
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
{/* <Tooltip label="Set as cover">
|
||||
<IconButton
|
||||
size={"sm"}
|
||||
variant={"ghost"}
|
||||
icon={isCover ? <IconPinFilled size={19} /> : <IconPin size={19} />}
|
||||
aria-label="set as cover"
|
||||
onClick={() => {
|
||||
workflowsTable?.updateMetaInfo(curFlowID, {
|
||||
coverMediaPath: media.localPath,
|
||||
});
|
||||
setCoverPath(media.localPath);
|
||||
}}
|
||||
/>
|
||||
</Tooltip> */}
|
||||
</SimpleGrid>
|
||||
<MetaBox media={media ?? null} />
|
||||
</Flex>
|
||||
|
||||
@@ -45,7 +45,6 @@ export default function MetaBox({ media }: { media: Media | null }) {
|
||||
getMetadataFromUrl(
|
||||
`/workspace/view_media?filename=${media.localPath}`,
|
||||
).then((data) => {
|
||||
console.log("setMediaMetaData by fetch", data);
|
||||
setImagePrompt(data.prompt);
|
||||
});
|
||||
} else {
|
||||
@@ -53,14 +52,12 @@ export default function MetaBox({ media }: { media: Media | null }) {
|
||||
.graphToPrompt(app.graph)
|
||||
.then((data: { output: any; workflow: any }) => {
|
||||
setImagePrompt(data.output);
|
||||
console.log("setMediaMetaData by graph", data);
|
||||
});
|
||||
}
|
||||
}, [media]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!imagePrompt) return;
|
||||
console.log("imagePrompt", imagePrompt);
|
||||
const calcInput = calcInputListRecursive(imagePrompt);
|
||||
setCalcInputList(calcInput);
|
||||
}, [imagePrompt]);
|
||||
@@ -74,6 +71,7 @@ export default function MetaBox({ media }: { media: Media | null }) {
|
||||
?.topFieldsConfig;
|
||||
if (topFieldsConfig) {
|
||||
setTopFields(topFieldsConfig);
|
||||
setShowAllInputs(false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user