diff --git a/app/package.json b/app/package.json
index b19a44e..71ca15f 100644
--- a/app/package.json
+++ b/app/package.json
@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"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.",
"homepage": "https://bknd.io",
"repository": {
diff --git a/app/src/data/index.ts b/app/src/data/index.ts
index db70e28..a5de079 100644
--- a/app/src/data/index.ts
+++ b/app/src/data/index.ts
@@ -28,3 +28,5 @@ export const DatabaseEvents = {
export { MutatorEvents, RepositoryEvents };
export * as DataPermissions from "./permissions";
+
+export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
diff --git a/app/src/ui/components/overlay/Popover.tsx b/app/src/ui/components/overlay/Popover.tsx
index 09e71bd..563841b 100644
--- a/app/src/ui/components/overlay/Popover.tsx
+++ b/app/src/ui/components/overlay/Popover.tsx
@@ -47,7 +47,7 @@ export function Popover({
Promise
;
openFileInput: () => void;
};
- dropzoneProps: Pick;
+ dropzoneProps: Pick;
};
export type DropzoneProps = {
getUploadInfo: (file: FileWithPath) => { url: string; headers?: Headers; method?: string };
handleDelete: (file: FileState) => Promise;
initialItems?: FileState[];
+ flow?: "start" | "end";
maxItems?: number;
overwrite?: boolean;
autoUpload?: boolean;
onRejected?: (files: FileWithPath[]) => void;
onDeleted?: (file: FileState) => void;
- onUploaded?: (file: FileState) => void;
+ onUploaded?: (files: FileState[]) => void;
placeholder?: {
show?: boolean;
text?: string;
@@ -61,6 +62,7 @@ export function Dropzone({
getUploadInfo,
handleDelete,
initialItems = [],
+ flow = "start",
maxItems,
overwrite,
autoUpload,
@@ -133,7 +135,7 @@ export function Dropzone({
progress: 0
}));
- return [..._prev, ...filteredFiles];
+ return flow === "start" ? [...filteredFiles, ..._prev] : [..._prev, ...filteredFiles];
});
if (autoUpload) {
@@ -164,6 +166,8 @@ export function Dropzone({
for (const file of pendingFiles) {
await uploadFileProgress(file);
}
+ setUploading(false);
+ onUploaded?.(files);
}
})();
}
@@ -259,7 +263,6 @@ export function Dropzone({
if (xhr.status === 200) {
//setFileState(file.path, "uploaded", 1);
console.log("Upload complete");
- onUploaded?.(file);
try {
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 showPlaceholder = Boolean(
placeholder?.show === true || !maxItems || (maxItems && files.length < maxItems)
@@ -332,14 +340,15 @@ export function Dropzone({
showPlaceholder
},
actions: {
- uploadFile: uploadFileProgress,
+ uploadFile,
deleteFile,
openFileInput
},
dropzoneProps: {
maxItems,
placeholder,
- autoUpload
+ autoUpload,
+ flow
}
};
@@ -351,8 +360,12 @@ const DropzoneInner = ({
inputProps,
state: { files, isOver, isOverAccepted, showPlaceholder },
actions: { uploadFile, deleteFile, openFileInput },
- dropzoneProps: { placeholder }
+ dropzoneProps: { placeholder, flow }
}: DropzoneRenderProps) => {
+ const Placeholder = showPlaceholder && (
+
+ );
+
return (
+ {flow === "start" && Placeholder}
{files.map((file) => (
))}
- {showPlaceholder && (
-
- )}
+ {flow === "end" && Placeholder}
diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx
index fa41f90..ba140ed 100644
--- a/app/src/ui/elements/media/DropzoneContainer.tsx
+++ b/app/src/ui/elements/media/DropzoneContainer.tsx
@@ -1,8 +1,9 @@
+import type { Api } from "bknd/client";
import type { RepoQueryIn } from "data";
import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema";
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 { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone";
import { mediaItemsToFileStates } from "./helper";
@@ -31,28 +32,32 @@ export function DropzoneContainer({
}: DropzoneContainerProps) {
const id = useId();
const api = useApi();
- const baseUrl = api.baseUrl;
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";
//console.log("dropzone:baseUrl", baseUrl);
- const $q = useEntityQuery(
- entity_name,
- undefined,
- {
- ...query,
- limit,
- where: entity
- ? {
+ const selectApi = (api: Api) =>
+ entity
+ ? api.data.readManyByReference(entity.name, entity.id, entity.field, {
+ ...defaultQuery,
+ ...query,
+ where: {
reference: `${entity.name}.${entity.field}`,
entity_id: entity.id,
...query?.where
}
- : query?.where
- },
- { enabled: !initialItems }
- );
+ })
+ : api.data.readMany(entity_name, {
+ ...defaultQuery,
+ ...query
+ });
+
+ const $q = useApiQuery(selectApi, { enabled: !initialItems });
const getUploadInfo = useEvent((file) => {
const url = entity
@@ -67,10 +72,7 @@ export function DropzoneContainer({
});
const refresh = useEvent(async () => {
- if (entity) {
- invalidate((api) => api.data.readOne(entity.name, entity.id));
- }
- await $q.mutate();
+ await invalidate($q.promise.key({ search: false }));
});
const handleDelete = useEvent(async (file: FileState) => {