Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions blocklets/image-bin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.14.18 (December 22, 2025)

- feat(uploader): add headless API for programmatic control

## 0.14.17 (December 20, 2025)

- feat: support app shell in dashboard
Expand Down
2 changes: 1 addition & 1 deletion blocklets/image-bin/blocklet.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: image-bin
version: 0.14.17
version: 0.14.18
title: Media Kit
description: Self-hosted media management that replaces expensive cloud services
while keeping you in complete control of your digital assets.
Expand Down
2 changes: 1 addition & 1 deletion blocklets/image-bin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "image-bin",
"version": "0.14.17",
"version": "0.14.18",
"private": true,
"scripts": {
"dev": "blocklet dev",
Expand Down
2 changes: 1 addition & 1 deletion blocklets/image-bin/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.14.17
0.14.18
4 changes: 4 additions & 0 deletions packages/uploader-server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.17 (December 22, 2025)

- feat(uploader): add headless API for programmatic control

## 0.3.16 (December 17, 2025)

- fix(uploader): improve runtime path handling and EXIF removal logic
Expand Down
2 changes: 1 addition & 1 deletion packages/uploader-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blocklet/uploader-server",
"version": "0.3.16",
"version": "0.3.17",
"description": "blocklet upload server",
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion packages/uploader-server/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.16
0.3.17
4 changes: 4 additions & 0 deletions packages/uploader/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.17 (December 22, 2025)

- feat(uploader): add headless API for programmatic control

## 0.3.16 (December 17, 2025)

- fix(uploader): improve runtime path handling and EXIF removal logic
Expand Down
2 changes: 1 addition & 1 deletion packages/uploader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blocklet/uploader",
"version": "0.3.16",
"version": "0.3.17",
"description": "blocklet upload component",
"publishConfig": {
"access": "public"
Expand Down
94 changes: 88 additions & 6 deletions packages/uploader/src/react/uploader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UploaderProps } from '../types';
import { UploaderProps, UploaderRef } from '../types';
import keyBy from 'lodash/keyBy';
import { useReactive, useRequest } from 'ahooks';
import { createRoot } from 'react-dom/client';
Expand Down Expand Up @@ -914,18 +914,100 @@ export function Uploader({
}, 500);
}

const addFilesToUppy = (files: File[], source: string, autoUpload: boolean) => {
files.forEach((file) => {
state.uppy.addFile({
name: file.name,
type: file.type,
data: file,
source,
});
});
if (autoUpload) {
state.uppy.upload();
}
};

const triggerFileInput = (options?: { accept?: string; multiple?: boolean; autoUpload?: boolean }) => {
const input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
input.accept = options?.accept ?? state.restrictions?.allowedFileTypes?.join(',') ?? '';
input.multiple = options?.multiple ?? state.restrictions?.maxNumberOfFiles !== 1;

input.onchange = (e: Event) => {
const target = e.target as HTMLInputElement;
const files = Array.from(target.files || []);
addFilesToUppy(files, 'local', options?.autoUpload !== false);
document.body.removeChild(input);
};

document.body.appendChild(input);
input.click();
};

const getDropzoneProps = (options?: { autoUpload?: boolean; noClick?: boolean }) => {
const autoUpload = options?.autoUpload !== false;
let dragCounter = 0;

return {
onDragEnter: (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
dragCounter++;
if (e.dataTransfer?.items?.length) {
e.currentTarget.setAttribute('data-dragging', 'true');
}
},
onDragLeave: (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
dragCounter--;
if (dragCounter === 0) {
e.currentTarget.removeAttribute('data-dragging');
}
},
onDragOver: (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
},
onDrop: (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
e.currentTarget.removeAttribute('data-dragging');
dragCounter = 0;
const files = Array.from(e.dataTransfer?.files || []);
if (files.length) {
addFilesToUppy(files, 'drop', autoUpload);
}
},
onClick: options?.noClick
? undefined
: () => {
triggerFileInput({ autoUpload });
},
};
};

useImperativeHandle(
ref,
() =>
({
getUploader: () => state.uppy,
open,
close,
} as {
getUploader: Function;
open: Function;
close: Function;
})
// Headless API
triggerFileInput,
getDropzoneProps,
addFiles: (files: File[], options?: { autoUpload?: boolean }) => {
addFilesToUppy(files, 'local', options?.autoUpload !== false);
},
upload: () => state.uppy.upload(),
getProgress: () => state.uppy.getState().totalProgress,
getFiles: () => state.uppy.getFiles(),
removeFile: (fileId: string) => state.uppy.removeFile(fileId),
cancelAll: () => state.uppy.cancelAll(),
} as UploaderRef)
);

const Wrapper = popup ? Modal : Fragment;
Expand Down
38 changes: 36 additions & 2 deletions packages/uploader/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
import type { UppyOptions } from '@uppy/core';
import type Uppy from '@uppy/core';
import type { UppyOptions, UppyFile } from '@uppy/core';
import type { DashboardOptions } from '@uppy/dashboard';
import type { TusOptions } from '@uppy/tus';
import type { ImageEditorOptions } from '@uppy/image-editor';
import type DropTarget from '@uppy/drop-target';
import type { HTMLAttributes } from 'react';
import type { HTMLAttributes, DragEvent } from 'react';
import type { SxProps, Theme } from '@mui/material/styles';

export interface DropzoneProps {
onDragEnter: (e: DragEvent) => void;
onDragLeave: (e: DragEvent) => void;
onDragOver: (e: DragEvent) => void;
onDrop: (e: DragEvent) => void;
onClick?: () => void;
}

export type UploaderRef = {
/** 获取底层 Uppy 实例 */
getUploader: () => Uppy;
/** 打开上传器 (Dashboard) */
open: (pluginName?: string) => void;
/** 关闭上传器 */
close: () => void;
/** 触发系统文件选择器 */
triggerFileInput: (options?: { accept?: string; multiple?: boolean; autoUpload?: boolean }) => void;
/** 获取拖拽区域 props,绑定到元素即可支持拖拽+点击上传 */
getDropzoneProps: (options?: { autoUpload?: boolean; noClick?: boolean }) => DropzoneProps;
/** 批量添加文件 */
addFiles: (files: File[], options?: { autoUpload?: boolean }) => void;
/** 开始上传 */
upload: () => Promise<any>;
/** 获取当前总进度 0-100 */
getProgress: () => number;
/** 获取当前文件列表 */
getFiles: () => UppyFile[];
/** 移除指定文件 */
removeFile: (fileId: string) => void;
/** 取消所有上传 */
cancelAll: () => void;
};

export type UploaderProps = {
id?: string;
popup?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/uploader/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.16
0.3.17
4 changes: 4 additions & 0 deletions packages/xss/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.15 (December 22, 2025)

- feat(uploader): add headless API for programmatic control

## 0.3.14 (December 17, 2025)

- fix(uploader): improve runtime path handling and EXIF removal logic
Expand Down
2 changes: 1 addition & 1 deletion packages/xss/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blocklet/xss",
"version": "0.3.14",
"version": "0.3.15",
"description": "blocklet prevent xss attack",
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion packages/xss/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.14
0.3.15
Loading