fix: popover regression & dropzone rerender lost state

This commit is contained in:
dswbx
2025-02-10 14:52:14 +01:00
parent 09d35d3cc0
commit d79da9d204
5 changed files with 47 additions and 31 deletions

View File

@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"bin": "./dist/cli/index.js", "bin": "./dist/cli/index.js",
"version": "0.7.0-rc.11", "version": "0.7.1",
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
"homepage": "https://bknd.io", "homepage": "https://bknd.io",
"repository": { "repository": {

View File

@@ -28,3 +28,5 @@ export const DatabaseEvents = {
export { MutatorEvents, RepositoryEvents }; export { MutatorEvents, RepositoryEvents };
export * as DataPermissions from "./permissions"; export * as DataPermissions from "./permissions";
export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";

View File

@@ -47,7 +47,7 @@ export function Popover({
<div <div
{...overlayProps} {...overlayProps}
className={twMerge( className={twMerge(
"animate-fade-in absolute z-20 flex flex-col bg-background border border-muted px-1 py-1 rounded-lg shadow-lg backdrop-blur-sm min-w-0 max-w-20", "animate-fade-in absolute z-20 flex flex-col bg-background border border-muted px-1 py-1 rounded-lg shadow-lg backdrop-blur-sm min-w-full max-w-20",
pos, pos,
overlayProps?.className overlayProps?.className
)} )}

View File

@@ -37,19 +37,20 @@ export type DropzoneRenderProps = {
deleteFile: (file: FileState) => Promise<void>; deleteFile: (file: FileState) => Promise<void>;
openFileInput: () => void; openFileInput: () => void;
}; };
dropzoneProps: Pick<DropzoneProps, "maxItems" | "placeholder" | "autoUpload">; dropzoneProps: Pick<DropzoneProps, "maxItems" | "placeholder" | "autoUpload" | "flow">;
}; };
export type DropzoneProps = { export type DropzoneProps = {
getUploadInfo: (file: FileWithPath) => { url: string; headers?: Headers; method?: string }; getUploadInfo: (file: FileWithPath) => { url: string; headers?: Headers; method?: string };
handleDelete: (file: FileState) => Promise<boolean>; handleDelete: (file: FileState) => Promise<boolean>;
initialItems?: FileState[]; initialItems?: FileState[];
flow?: "start" | "end";
maxItems?: number; maxItems?: number;
overwrite?: boolean; overwrite?: boolean;
autoUpload?: boolean; autoUpload?: boolean;
onRejected?: (files: FileWithPath[]) => void; onRejected?: (files: FileWithPath[]) => void;
onDeleted?: (file: FileState) => void; onDeleted?: (file: FileState) => void;
onUploaded?: (file: FileState) => void; onUploaded?: (files: FileState[]) => void;
placeholder?: { placeholder?: {
show?: boolean; show?: boolean;
text?: string; text?: string;
@@ -61,6 +62,7 @@ export function Dropzone({
getUploadInfo, getUploadInfo,
handleDelete, handleDelete,
initialItems = [], initialItems = [],
flow = "start",
maxItems, maxItems,
overwrite, overwrite,
autoUpload, autoUpload,
@@ -133,7 +135,7 @@ export function Dropzone({
progress: 0 progress: 0
})); }));
return [..._prev, ...filteredFiles]; return flow === "start" ? [...filteredFiles, ..._prev] : [..._prev, ...filteredFiles];
}); });
if (autoUpload) { if (autoUpload) {
@@ -164,6 +166,8 @@ export function Dropzone({
for (const file of pendingFiles) { for (const file of pendingFiles) {
await uploadFileProgress(file); await uploadFileProgress(file);
} }
setUploading(false);
onUploaded?.(files);
} }
})(); })();
} }
@@ -259,7 +263,6 @@ export function Dropzone({
if (xhr.status === 200) { if (xhr.status === 200) {
//setFileState(file.path, "uploaded", 1); //setFileState(file.path, "uploaded", 1);
console.log("Upload complete"); console.log("Upload complete");
onUploaded?.(file);
try { try {
const response = JSON.parse(xhr.responseText); const response = JSON.parse(xhr.responseText);
@@ -312,6 +315,11 @@ export function Dropzone({
} }
} }
async function uploadFile(file: FileState) {
await uploadFileProgress(file);
onUploaded?.([file]);
}
const openFileInput = () => inputRef.current?.click(); const openFileInput = () => inputRef.current?.click();
const showPlaceholder = Boolean( const showPlaceholder = Boolean(
placeholder?.show === true || !maxItems || (maxItems && files.length < maxItems) placeholder?.show === true || !maxItems || (maxItems && files.length < maxItems)
@@ -332,14 +340,15 @@ export function Dropzone({
showPlaceholder showPlaceholder
}, },
actions: { actions: {
uploadFile: uploadFileProgress, uploadFile,
deleteFile, deleteFile,
openFileInput openFileInput
}, },
dropzoneProps: { dropzoneProps: {
maxItems, maxItems,
placeholder, placeholder,
autoUpload autoUpload,
flow
} }
}; };
@@ -351,8 +360,12 @@ const DropzoneInner = ({
inputProps, inputProps,
state: { files, isOver, isOverAccepted, showPlaceholder }, state: { files, isOver, isOverAccepted, showPlaceholder },
actions: { uploadFile, deleteFile, openFileInput }, actions: { uploadFile, deleteFile, openFileInput },
dropzoneProps: { placeholder } dropzoneProps: { placeholder, flow }
}: DropzoneRenderProps) => { }: DropzoneRenderProps) => {
const Placeholder = showPlaceholder && (
<UploadPlaceholder onClick={openFileInput} text={placeholder?.text} />
);
return ( return (
<div <div
ref={wrapperRef} ref={wrapperRef}
@@ -367,6 +380,7 @@ const DropzoneInner = ({
</div> </div>
<div className="flex flex-1 flex-col"> <div className="flex flex-1 flex-col">
<div className="flex flex-row flex-wrap gap-2 md:gap-3"> <div className="flex flex-row flex-wrap gap-2 md:gap-3">
{flow === "start" && Placeholder}
{files.map((file) => ( {files.map((file) => (
<Preview <Preview
key={file.path} key={file.path}
@@ -375,9 +389,7 @@ const DropzoneInner = ({
handleDelete={deleteFile} handleDelete={deleteFile}
/> />
))} ))}
{showPlaceholder && ( {flow === "end" && Placeholder}
<UploadPlaceholder onClick={openFileInput} text={placeholder?.text} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,9 @@
import type { Api } from "bknd/client";
import type { RepoQueryIn } from "data"; import type { RepoQueryIn } from "data";
import type { MediaFieldSchema } from "media/AppMedia"; import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema"; import type { TAppMediaConfig } from "media/media-schema";
import { type ReactNode, createContext, useContext, useId } from "react"; import { type ReactNode, createContext, useContext, useId } from "react";
import { useApi, useEntityQuery, useInvalidate } from "ui/client"; import { useApi, useApiQuery, useInvalidate } from "ui/client";
import { useEvent } from "ui/hooks/use-event"; import { useEvent } from "ui/hooks/use-event";
import { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone"; import { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone";
import { mediaItemsToFileStates } from "./helper"; import { mediaItemsToFileStates } from "./helper";
@@ -31,28 +32,32 @@ export function DropzoneContainer({
}: DropzoneContainerProps) { }: DropzoneContainerProps) {
const id = useId(); const id = useId();
const api = useApi(); const api = useApi();
const baseUrl = api.baseUrl;
const invalidate = useInvalidate(); const invalidate = useInvalidate();
const limit = query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50; const baseUrl = api.baseUrl;
const defaultQuery = {
limit: query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50,
sort: "-id"
};
const entity_name = (media?.entity_name ?? "media") as "media"; const entity_name = (media?.entity_name ?? "media") as "media";
//console.log("dropzone:baseUrl", baseUrl); //console.log("dropzone:baseUrl", baseUrl);
const $q = useEntityQuery( const selectApi = (api: Api) =>
entity_name, entity
undefined, ? api.data.readManyByReference(entity.name, entity.id, entity.field, {
{ ...defaultQuery,
...query, ...query,
limit, where: {
where: entity
? {
reference: `${entity.name}.${entity.field}`, reference: `${entity.name}.${entity.field}`,
entity_id: entity.id, entity_id: entity.id,
...query?.where ...query?.where
} }
: query?.where })
}, : api.data.readMany(entity_name, {
{ enabled: !initialItems } ...defaultQuery,
); ...query
});
const $q = useApiQuery(selectApi, { enabled: !initialItems });
const getUploadInfo = useEvent((file) => { const getUploadInfo = useEvent((file) => {
const url = entity const url = entity
@@ -67,10 +72,7 @@ export function DropzoneContainer({
}); });
const refresh = useEvent(async () => { const refresh = useEvent(async () => {
if (entity) { await invalidate($q.promise.key({ search: false }));
invalidate((api) => api.data.readOne(entity.name, entity.id));
}
await $q.mutate();
}); });
const handleDelete = useEvent(async (file: FileState) => { const handleDelete = useEvent(async (file: FileState) => {