mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
fix: popover regression & dropzone rerender lost state
This commit is contained in:
@@ -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": {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user