@salesforce/webapp-template-feature-react-file-upload-experimental 1.90.1 → 1.90.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -219
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/package.json +3 -3
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{api → features/fileupload/api}/fileUpload.ts +153 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadDialog.tsx +2 -2
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadFileItem.tsx +1 -1
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +25 -39
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +2 -2
- package/dist/package.json +1 -1
- package/package.json +14 -9
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/appLayout.tsx +9 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__button.tsx +39 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__dialog.tsx +102 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/api/fileUpload.ts +299 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/icon-image-close.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/image.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/success.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/symbols.svg +1 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUpload.tsx +100 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadDialog.tsx +79 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadDropZone.tsx +90 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadFileItem.tsx +99 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadIcons.tsx +90 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/hooks/useFileUpload.ts +312 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/hooks/useFileUploadDialog.ts +70 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/pages/UploadTest.tsx +56 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/types/fileUpload.ts +28 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/utils/fileUploadUtils.ts +54 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/utils/labels.ts +23 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +60 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +22 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/vite.config.ts +43 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/icon-image-close.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/image.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/success.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/symbols.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUpload.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadDropZone.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadIcons.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{hooks → features/fileupload/hooks}/useFileUpload.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{hooks → features/fileupload/hooks}/useFileUploadDialog.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{pages → features/fileupload/pages}/UploadTest.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{types → features/fileupload/types}/fileUpload.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{utils → features/fileupload/utils}/fileUploadUtils.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{utils → features/fileupload/utils}/labels.ts +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FileUpload } from "../components/FileUpload";
|
|
2
|
+
import { createContentVersion, getCurrentUserId } from "../api/fileUpload";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Upload test page for testing the file-upload feature standalone.
|
|
6
|
+
* Renders FileUpload for dialog-based file upload with progress.
|
|
7
|
+
* Demonstrates manual ContentVersion creation after file upload.
|
|
8
|
+
*/
|
|
9
|
+
export default function UploadTest() {
|
|
10
|
+
const handleUploadComplete = async (
|
|
11
|
+
files: { name: string; size: number; contentBodyId: string; contentVersionId?: string }[],
|
|
12
|
+
) => {
|
|
13
|
+
// eslint-disable-next-line no-console
|
|
14
|
+
console.log("Uploaded files:", JSON.stringify(files, null, 2));
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
for (const uploadedFile of files) {
|
|
18
|
+
if (uploadedFile.contentVersionId) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const userId = await getCurrentUserId();
|
|
22
|
+
// Create a File object from the uploaded file info (for API signature)
|
|
23
|
+
const file = new File([""], uploadedFile.name);
|
|
24
|
+
const contentVersionId = await createContentVersion(
|
|
25
|
+
file,
|
|
26
|
+
uploadedFile.contentBodyId,
|
|
27
|
+
userId,
|
|
28
|
+
);
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.log(`Created ContentVersion for ${uploadedFile.name}:`, contentVersionId);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.error("Error creating ContentVersion:", error);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12 space-y-16">
|
|
40
|
+
<section>
|
|
41
|
+
<h2 className="text-2xl font-bold text-gray-900 mb-2">File Upload (Dialog + Progress)</h2>
|
|
42
|
+
<p className="text-gray-600 mb-8">
|
|
43
|
+
Choose files to open a dialog showing upload status with progress bar for each file.
|
|
44
|
+
ContentVersion is created manually after upload using current user ID.
|
|
45
|
+
</p>
|
|
46
|
+
<FileUpload
|
|
47
|
+
multiple
|
|
48
|
+
recordId="500SG00001Bbi1dYAB"
|
|
49
|
+
accept="image/jpeg,.jpg,.jpeg,application/pdf,.pdf"
|
|
50
|
+
maxFileSize={50}
|
|
51
|
+
onUploadComplete={handleUploadComplete}
|
|
52
|
+
/>
|
|
53
|
+
</section>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload type definitions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type UploadState =
|
|
6
|
+
| "idle"
|
|
7
|
+
| "loading_config"
|
|
8
|
+
| "uploading"
|
|
9
|
+
| "creating_record"
|
|
10
|
+
| "success"
|
|
11
|
+
| "error"
|
|
12
|
+
| "cancelled";
|
|
13
|
+
|
|
14
|
+
export interface FileUploadItem {
|
|
15
|
+
file: File;
|
|
16
|
+
state: UploadState;
|
|
17
|
+
progress: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
contentBodyId?: string;
|
|
20
|
+
contentVersionId?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UploadedFile {
|
|
24
|
+
name: string;
|
|
25
|
+
size: number;
|
|
26
|
+
contentBodyId: string;
|
|
27
|
+
contentVersionId?: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload utility functions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { UploadState } from "../types/fileUpload";
|
|
6
|
+
|
|
7
|
+
/** Maximum allowed file size: 2 GB */
|
|
8
|
+
export const MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
/** Convert megabytes to bytes */
|
|
11
|
+
export function bytesFromMB(mb: number): number {
|
|
12
|
+
return mb * 1024 * 1024;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if file exceeds size limit.
|
|
17
|
+
* @param file - File to check
|
|
18
|
+
* @param maxBytes - Max size in bytes. Defaults to MAX_FILE_SIZE_BYTES when omitted.
|
|
19
|
+
*/
|
|
20
|
+
export function isFileTooLarge(file: File, maxBytes: number = MAX_FILE_SIZE_BYTES): boolean {
|
|
21
|
+
return file.size > maxBytes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const UPLOADING_STATES: UploadState[] = ["loading_config", "uploading", "creating_record"];
|
|
25
|
+
|
|
26
|
+
export function isUploading(state: UploadState): boolean {
|
|
27
|
+
return UPLOADING_STATES.includes(state);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatFileSize(bytes: number): string {
|
|
31
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
32
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
33
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
34
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getFileExtension(name: string): string {
|
|
38
|
+
const lastDot = name.lastIndexOf(".");
|
|
39
|
+
return lastDot > 0 ? name.slice(lastDot + 1).toUpperCase() : "FILE";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatUploadSummary(successCount: number, totalCount: number): string {
|
|
43
|
+
const noun = totalCount === 1 ? "file" : "files";
|
|
44
|
+
if (totalCount === 1) {
|
|
45
|
+
return successCount === 1 ? "1 of 1 file uploaded" : "1 file uploading";
|
|
46
|
+
}
|
|
47
|
+
return `${successCount} of ${totalCount} ${noun} uploaded`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getProgressWidth(state: UploadState, progress: number): number {
|
|
51
|
+
if (state === "success") return 100;
|
|
52
|
+
if (state === "error" || state === "cancelled") return 0;
|
|
53
|
+
return progress;
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** User-visible text and accessibility labels. */
|
|
2
|
+
export const LABELS = {
|
|
3
|
+
attach: "Attach",
|
|
4
|
+
uploadFiles: "Upload Files",
|
|
5
|
+
dropFilesHere: "Drop files here",
|
|
6
|
+
orDropFiles: "Or drop files",
|
|
7
|
+
chooseFileOrDrop: "Choose a file or drag & drop for reference here.",
|
|
8
|
+
formatHint: "JPEG, PNG, PDF, and MP4 formats, up to 50MB",
|
|
9
|
+
uploadFilesDialogTitle: "Upload Files",
|
|
10
|
+
done: "Done",
|
|
11
|
+
cancelled: "Cancelled",
|
|
12
|
+
dropZone: "Upload files by clicking or dragging files here",
|
|
13
|
+
cancelUpload: (fileName: string) => `Cancel upload of ${fileName}`,
|
|
14
|
+
uploadComplete: "Upload complete",
|
|
15
|
+
uploadFailed: "Upload failed",
|
|
16
|
+
uploadStatus: "Upload status",
|
|
17
|
+
uploadedFiles: "Uploaded files",
|
|
18
|
+
uploadProgress: (fileName: string) => `Upload progress for ${fileName}`,
|
|
19
|
+
fileName: (name: string) => `File name: ${name}`,
|
|
20
|
+
fileSize: (size: string) => `File size: ${size}`,
|
|
21
|
+
fileTooLarge: (maxSize: string) => `File exceeds maximum size of ${maxSize}`,
|
|
22
|
+
doneButton: "Close dialog and finish upload",
|
|
23
|
+
} as const;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feature-react-file-upload – File upload API
|
|
3
|
+
*
|
|
4
|
+
* Provides programmatic APIs for file upload with progress tracking
|
|
5
|
+
* and Salesforce ContentVersion integration.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Unified upload API that handles the complete upload flow:
|
|
12
|
+
* 1. Gets upload config
|
|
13
|
+
* 2. Uploads files with progress tracking
|
|
14
|
+
* 3. Creates ContentVersion records (if recordId provided)
|
|
15
|
+
*
|
|
16
|
+
* Returns array of results with file names, sizes, and IDs.
|
|
17
|
+
* Provides progress callbacks for each file through all stages.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const results = await upload({
|
|
22
|
+
* files: [file1, file2],
|
|
23
|
+
* recordId: accountId,
|
|
24
|
+
* onProgress: (progress) => {
|
|
25
|
+
* console.log(`${progress.fileName}: ${progress.status} ${progress.progress}%`);
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export { upload } from "./features/fileupload/api/fileUpload";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for the upload API.
|
|
34
|
+
*
|
|
35
|
+
* @see upload
|
|
36
|
+
*/
|
|
37
|
+
export type {
|
|
38
|
+
UploadOptions,
|
|
39
|
+
FileUploadResult,
|
|
40
|
+
FileUploadProgress,
|
|
41
|
+
UploadStatus,
|
|
42
|
+
} from "./features/fileupload/api/fileUpload";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create ContentVersion record programmatically. Useful when you need to create
|
|
46
|
+
* ContentVersion separately from the file upload process (e.g., after uploading
|
|
47
|
+
* file to a different location first).
|
|
48
|
+
*
|
|
49
|
+
* @param file - The file to create ContentVersion for
|
|
50
|
+
* @param contentBodyId - The ContentBody ID from a previous upload
|
|
51
|
+
* @param recordId - The record ID for FirstPublishLocationId (e.g., Account, Opportunity, or User ID)
|
|
52
|
+
* @returns ContentVersion ID if successful
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const contentBodyId = await uploadToUrl(file, token, uploadUrl, onProgress);
|
|
57
|
+
* const contentVersionId = await createContentVersion(file, contentBodyId, recordId);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export { createContentVersion, getCurrentUserId } from "./features/fileupload/api/fileUpload";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RouteObject } from "react-router";
|
|
2
|
+
import { Navigate } from "react-router";
|
|
3
|
+
import AppLayout from "./appLayout";
|
|
4
|
+
import UploadTest from "./features/fileupload/pages/UploadTest";
|
|
5
|
+
|
|
6
|
+
export const routes: RouteObject[] = [
|
|
7
|
+
{
|
|
8
|
+
path: "/",
|
|
9
|
+
element: <AppLayout />,
|
|
10
|
+
children: [
|
|
11
|
+
{
|
|
12
|
+
index: true,
|
|
13
|
+
element: <Navigate to="/upload-test" replace />,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
path: "upload-test",
|
|
17
|
+
element: <UploadTest />,
|
|
18
|
+
handle: { showInNavigation: true, label: "Upload Test (Component)" },
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
6
|
+
import salesforce from "@salesforce/vite-plugin-webapp-experimental";
|
|
7
|
+
|
|
8
|
+
export default defineConfig(({ mode }) => {
|
|
9
|
+
return {
|
|
10
|
+
plugins: [tailwindcss(), react(), salesforce({ debug: true })] as import("vite").PluginOption[],
|
|
11
|
+
|
|
12
|
+
build: {
|
|
13
|
+
outDir: resolve(__dirname, "dist"),
|
|
14
|
+
assetsDir: "assets",
|
|
15
|
+
sourcemap: false,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
resolve: {
|
|
19
|
+
dedupe: ["react", "react-dom"],
|
|
20
|
+
alias: {
|
|
21
|
+
react: path.resolve(__dirname, "node_modules/react"),
|
|
22
|
+
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
|
|
23
|
+
"@": path.resolve(__dirname, "./src"),
|
|
24
|
+
"@api": path.resolve(__dirname, "./src/api"),
|
|
25
|
+
"@components": path.resolve(__dirname, "./src/components"),
|
|
26
|
+
"@utils": path.resolve(__dirname, "./src/utils"),
|
|
27
|
+
"@styles": path.resolve(__dirname, "./src/styles"),
|
|
28
|
+
"@assets": path.resolve(__dirname, "./src/assets"),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
test: {
|
|
33
|
+
root: resolve(__dirname),
|
|
34
|
+
environment: "jsdom",
|
|
35
|
+
include: [
|
|
36
|
+
"src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
|
|
37
|
+
"src/**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
|
|
38
|
+
],
|
|
39
|
+
testTimeout: 10000,
|
|
40
|
+
globals: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|