"
+```
+
+Then, we want to create a new API endpoint, using a `+server.js` file.
+
+
+
+
+```js
+
+import { v2 as cloudinary } from "cloudinary";
+import { env } from '$env/dynamic/private';
+import { error, json } from "@sveltejs/kit";
+
+export const POST = (async ({ request }) => {
+ const body = await request.json()
+ const { paramsToSign } = body;
+ try {
+ const signature = cloudinary.utils.api_sign_request(
+ paramsToSign,
+ env.CLOUDINARY_API_SECRET
+ );
+ return json({ signature })
+ } catch (e) {
+ console.error(e)
+ throw error(500, (e as Error).message)
+ }
+});
+
+```
+
diff --git a/docs/src/docs/components/clduploadwidget/usage.md b/docs/src/docs/components/clduploadwidget/usage.md
index 18ac88b3..a25fe259 100644
--- a/docs/src/docs/components/clduploadwidget/usage.md
+++ b/docs/src/docs/components/clduploadwidget/usage.md
@@ -6,9 +6,10 @@ order: 1
@@ -130,7 +131,11 @@ See a full example of an API endpoint used with the Svelte Cloudinary docs: http
URL: { infoUploadSecure?.secure_url }
-
+## Watch & Learn
+
## Learn More about CldUploadWidget
* [Configuration](/clduploadwidget/configuration)
diff --git a/docs/src/docs/meta.json b/docs/src/docs/meta.json
index 081d9a91..a5434d7e 100644
--- a/docs/src/docs/meta.json
+++ b/docs/src/docs/meta.json
@@ -51,7 +51,8 @@
"children": [
"usage",
"configuration",
- "examples"
+ "examples",
+ "signed-uploads"
]
},
{
diff --git a/docs/src/lib/components/CopyCodeInjector.svelte b/docs/src/lib/components/CopyCodeInjector.svelte
new file mode 100644
index 00000000..ed92e339
--- /dev/null
+++ b/docs/src/lib/components/CopyCodeInjector.svelte
@@ -0,0 +1,29 @@
+
+
+
diff --git a/docs/src/lib/components/Layout.svelte b/docs/src/lib/components/Layout.svelte
index ddc81085..37eb7bde 100644
--- a/docs/src/lib/components/Layout.svelte
+++ b/docs/src/lib/components/Layout.svelte
@@ -1,7 +1,9 @@
@@ -61,4 +63,6 @@
]}
/>
-
+
+
+
diff --git a/svelte-cloudinary/src/lib/components/CldUploadButton.svelte b/svelte-cloudinary/src/lib/components/CldUploadButton.svelte
index ddc73c64..82aac25f 100644
--- a/svelte-cloudinary/src/lib/components/CldUploadButton.svelte
+++ b/svelte-cloudinary/src/lib/components/CldUploadButton.svelte
@@ -2,22 +2,45 @@
import type { HTMLButtonAttributes } from 'svelte/elements';
import CldUploadWidget from './CldUploadWidget.svelte';
import type { CldUploadWidgetProps } from './CldUploadWidgetTypes.ts';
- import { invariant } from '$lib/util.js';
type $$Props = CldUploadWidgetProps & HTMLButtonAttributes;
// destructure the props
const {
- uploadPreset,
- signatureEndpoint,
+ children,
onError,
- onUpload,
- options,
onOpen,
+ onUpload,
+ onAbort,
+ onBatchCancelled,
onClose,
+ onDisplayChanged,
+ onPublicId,
+ onQueuesEnd,
+ onQueuesStart,
+ onRetry,
+ onShowCompleted,
+ onSourceChanged,
+ onSuccess,
+ onTags,
+ onUploadAdded,
+ options,
+ signatureEndpoint,
+ uploadPreset,
...buttonProps
} = $$props as $$Props;
let baseProps: CldUploadWidgetProps = {
+ onAbort,
+ onBatchCancelled,
+ onDisplayChanged,
+ onPublicId,
+ onQueuesEnd,
+ onQueuesStart,
+ onRetry,
+ onShowCompleted,
+ onSourceChanged,
+ onSuccess,
+ onUploadAdded,
onClose,
onOpen,
options,
@@ -26,13 +49,11 @@
uploadPreset,
signatureEndpoint
};
- // @ts-expect-error the $$slots and $$scope attributes inside the buttonsProps object
// appears because of the spread operator on line 39
// this attributes should not be passed to the button html elements since are not valid attributes
- delete buttonProps['$$slots']
+ delete buttonProps['$$slots'];
// @ts-expect-error
- delete buttonProps['$$scope']
-
+ delete buttonProps['$$scope'];
diff --git a/svelte-cloudinary/src/lib/components/CldUploadWidget.svelte b/svelte-cloudinary/src/lib/components/CldUploadWidget.svelte
index 593e4890..e781403d 100644
--- a/svelte-cloudinary/src/lib/components/CldUploadWidget.svelte
+++ b/svelte-cloudinary/src/lib/components/CldUploadWidget.svelte
@@ -3,12 +3,17 @@
import { triggerOnIdle, loadCloudinary } from '$lib/util.js';
import { checkCloudinaryCloudName } from '$lib/cloudinary.js';
import type {
- ResultsEvents,
- UploadWidget,
CldUploadWidgetProps,
-
- ResultCallback
-
+ CldUploadWidgetInstanceMethods,
+ CldUploadWidgetCloseInstanceMethodOptions,
+ CldUploadWidgetDetsroyInstanceMethodOptions,
+ CldUploadWidgetOpenWidgetSources,
+ CldUploadWidgetOpenInstanceMethodOptions,
+ CldUploadEventCallback,
+ CldUploadWidgetError,
+ CldUploadWidgetResults,
+ CldUploadWidgetWidgetInstance,
+ CldUploadWidgetCloudinaryInstance,
} from './CldUploadWidgetTypes.ts';
type $$Props = CldUploadWidgetProps;
@@ -18,11 +23,27 @@
$$props as $$Props;
// References
- let cloudinary: typeof window.cloudinary;
- let widget: UploadWidget;
+ let cloudinary: CldUploadWidgetCloudinaryInstance;
+ let widget: CldUploadWidgetWidgetInstance;
const signed = !!signatureEndpoint;
const WIDGET_WATCHED_EVENTS = ['success', 'display-changed'];
+ const WIDGET_EVENTS: { [key: string]: string } = {
+ abort: 'onAbort',
+ 'batch-cancelled': 'onBatchCancelled',
+ // 'close': 'onClose', // TODO: should follow other event patterns
+ 'display-changed': 'onDisplayChanged',
+ publicid: 'onPublicId',
+ 'queues-end': 'onQueuesEnd',
+ 'queues-start': 'onQueuesStart',
+ retry: 'onRetry',
+ 'show-completed': 'onShowCompleted',
+ 'source-changed': 'onSourceChanged',
+ success: 'onSuccess',
+ tags: 'onTags',
+ 'upload-added': 'onUploadAdded'
+ };
+
// Validation
checkCloudinaryCloudName(import.meta.env.VITE_PUBLIC_CLOUDINARY_CLOUD_NAME);
@@ -86,46 +107,36 @@
*/
function createWidget() {
- const resultCallback: ResultCallback = (uploadError, uploadResult) => {
- // The callback is a bit more chatty than failed or success so
- // only trigger when one of those are the case. You can additionally
- // create a separate handler such as onEvent and trigger it on
- // ever occurrence
- if (uploadError != null){
- handleError(uploadError)
- }
-
- if (WIDGET_WATCHED_EVENTS.includes(uploadResult?.event)) {
- handleResults(uploadResult)
- }
+ return cloudinary?.createUploadWidget(uploadOptions, (uploadError: CldUploadWidgetError, uploadResult: CldUploadWidgetResults) => {
+ if ( uploadError && uploadError !== null ) {
+ handleError(uploadError);
+ }
- }
- return cloudinary?.createUploadWidget(uploadOptions, resultCallback)
- }
+ if ( typeof uploadResult?.event === 'string' ) {
+ if ( WIDGET_WATCHED_EVENTS.includes(uploadResult?.event) ) {
+ handleResults(uploadResult);
+ }
- /**
- * open
- * @description When triggered, uses the current widget instance to open the upload modal
- */
+ const widgetEvent = WIDGET_EVENTS[uploadResult.event] as keyof typeof $$props;
- function open() {
- if (!widget) {
- widget = createWidget();
- }
-
- widget?.open();
-
- if (typeof onOpen === 'function') {
- onOpen(widget);
- }
+ if ( typeof widgetEvent === 'string' && typeof $$props[widgetEvent] === 'function' && typeof $$props[widgetEvent] ) {
+ const callback = $$props[widgetEvent] as CldUploadEventCallback;
+ callback(uploadResult, {
+ widget,
+ ...instanceMethods
+ });
+ }
+ }
+ });
}
+
function onLoadingError() {
console.error(`Failed to load Cloudinary Upload Widget`);
}
// Side effects
- function handleResults(results: ResultsEvents) {
+ function handleResults(results: CldUploadWidgetResults) {
if (results != null) {
const isSuccess = results.event === 'success';
const isClosed = results.event === 'display-changed' && results.info === 'hidden';
@@ -145,14 +156,101 @@
}
}
onMount(() => {
- if(!window.cloudinary?.createUploadWidget) {
- return loadCloudinary({ onLoad: handleOnLoad, onError: handleError })
+ if (!window.cloudinary?.createUploadWidget) {
+ return loadCloudinary({ onLoad: handleOnLoad, onError: handleError });
}
return handleOnLoad();
});
-
+ /**
+ * Instance Methods
+ * Gives the ability to interface directly with the Upload Widget instance methods like open and close
+ * https://cloudinary.com/documentation/upload_widget_reference#instance_methods
+ */
+ function invokeInstanceMethod(
+ method: TMethod,
+ options: Parameters = [] as Parameters<
+ CldUploadWidgetInstanceMethods[TMethod]
+ >
+ ) {
+ if (!widget) {
+ widget = createWidget();
+ }
+
+ if (typeof widget?.[method] === 'function') {
+ return widget?.[method](...options);
+ }
+ }
-
+ function close(options?: CldUploadWidgetCloseInstanceMethodOptions) {
+ invokeInstanceMethod('close', [options]);
+ }
+
+ function destroy(options?: CldUploadWidgetDetsroyInstanceMethodOptions) {
+ return invokeInstanceMethod('destroy', [options]);
+ }
+
+ function hide() {
+ invokeInstanceMethod('hide');
+ }
+
+ function isDestroyed() {
+ return invokeInstanceMethod('isDestroyed');
+ }
+
+ function isMinimized() {
+ return invokeInstanceMethod('isMinimized');
+ }
+
+ function isShowing() {
+ return invokeInstanceMethod('isShowing');
+ }
+
+ function minimize() {
+ invokeInstanceMethod('minimize');
+ }
+
+ /**
+ * open
+ * @description When triggered, uses the current widget instance to open the upload modal
+ * user can pass custom parameters to customize the experience, check more
+ * https://cloudinary.com/documentation/upload_widget_reference#open
+ * widgetSource can only be string | null
+ * options can be a Map or undefined
+ */
+ function open(
+ widgetSource?: CldUploadWidgetOpenWidgetSources,
+ options?: CldUploadWidgetOpenInstanceMethodOptions
+ ) {
+ invokeInstanceMethod('open', [typeof widgetSource === 'string' ? widgetSource : null , options]);
+
+ if (typeof onOpen === 'function') {
+ onOpen(widget);
+ }
+ }
+
+
+ function show() {
+ invokeInstanceMethod('show');
+ }
+
+ function update() {
+ invokeInstanceMethod('update');
+ }
+
+ const instanceMethods: CldUploadWidgetInstanceMethods = {
+ close,
+ destroy,
+ hide,
+ isDestroyed,
+ isMinimized,
+ isShowing,
+ minimize,
+ open,
+ show,
+ update
+ };
+
+
diff --git a/svelte-cloudinary/src/lib/components/CldUploadWidgetTypes.ts b/svelte-cloudinary/src/lib/components/CldUploadWidgetTypes.ts
index 24c1329c..23e50292 100644
--- a/svelte-cloudinary/src/lib/components/CldUploadWidgetTypes.ts
+++ b/svelte-cloudinary/src/lib/components/CldUploadWidgetTypes.ts
@@ -1,26 +1,125 @@
+// TODO: widget needs to be typed
+
+export type CldUploadWidgetCloudinaryInstance = any;
+export type CldUploadWidgetWidgetInstance = any;
+
+type CustomURL = `https://${string}.${string}`;
+
+export interface CldUploadWidgetResults {
+ event?: string;
+ info?: string | object;
+}
+
+export type CldUploadWidgetDetsroyInstanceMethodOptions = {
+ removeThumbnails: boolean;
+}
+
+export type CldUploadWidgetCloseInstanceMethodOptions = {
+ quiet: boolean;
+}
+
+export type CldUploadWidgetOpenInstanceMethodOptions = {
+ files: CustomURL[];
+}
+
+export type CldUploadWidgetOpenWidgetSources =
+ | 'local'
+ | 'url'
+ | 'camera'
+ | 'image_search'
+ | 'google_drive'
+ | 'dropbox'
+ | 'facebook'
+ | 'instagram'
+ | 'shutterstock'
+ | 'getty'
+ | 'istock'
+ | 'unsplash'
+ | null;
+
+type CldUploadWidgetUpdateInstanceMethodOptions = Omit<
+ CldUploadWidgetPropsOptions,
+ "secure" | "uploadSignature" | "getTags" | "preBatch" | "inlineContainer" | "fieldName"
+> & {
+ cloudName: string;
+ uploadPreset: string;
+}
+
+export interface CldUploadWidgetInstanceMethods {
+ close: (options?: CldUploadWidgetCloseInstanceMethodOptions) => void;
+ destroy: (options?: CldUploadWidgetDetsroyInstanceMethodOptions) => Promise;
+ hide: () => void;
+ isDestroyed: () => boolean;
+ isMinimized: () => boolean;
+ isShowing: () => boolean;
+ minimize: () => void;
+ open: (widgetSource?: CldUploadWidgetOpenWidgetSources, options?: CldUploadWidgetOpenInstanceMethodOptions) => void;
+ show: () => void;
+ update: (options: CldUploadWidgetUpdateInstanceMethodOptions) => void;
+}
+
+export type CldUploadWidgetError = {
+ status: string;
+ statusText: string;
+} | string | null;
+
+export interface CldUploadWidgetProps {
+ onError?: CldUploadEventCallbackError;
+ onOpen?: CldUploadEventCallbackWidgetOnly;
+ onUpload?: CldUploadEventCallbackNoOptions;
+ onAbort?: CldUploadEventCallback;
+ onBatchCancelled?: CldUploadEventCallback;
+ onClose?: CldUploadEventCallbackWidgetOnly;
+ onDisplayChanged?: CldUploadEventCallback;
+ onPublicId?: CldUploadEventCallback;
+ onQueuesEnd?: CldUploadEventCallback;
+ onQueuesStart?: CldUploadEventCallback;
+ onRetry?: CldUploadEventCallback;
+ onShowCompleted?: CldUploadEventCallback;
+ onSourceChanged?: CldUploadEventCallback;
+ onSuccess?: CldUploadEventCallback;
+ onTags?: CldUploadEventCallback;
+ onUploadAdded?: CldUploadEventCallback;
+ options?: CldUploadWidgetPropsOptions;
+ signatureEndpoint?: URL | RequestInfo;
+ uploadPreset?: string;
+}
+
+export type CldUploadWidgetPropsChildren = {
+ cloudinary: CldUploadWidgetCloudinaryInstance;
+ widget: CldUploadWidgetWidgetInstance;
+
+ error?: CldUploadWidgetError;
+ isLoading?: boolean;
+ results?: CldUploadWidgetResults;
+} & CldUploadWidgetInstanceMethods;
+
+// Parameters sourced from:
+// https://cloudinary.com/documentation/upload_widget_reference#parameters
+
export interface CldUploadWidgetPropsOptions {
// Widget
encryption?: {
key: string;
iv: string;
- };
+ }
defaultSource?: string;
maxFiles?: number;
multiple?: boolean;
sources?: Array<
- | 'camera'
- | 'dropbox'
- | 'facebook'
- | 'gettyimages'
- | 'google_drive'
- | 'image_search'
- | 'instagram'
- | 'istock'
- | 'local'
- | 'shutterstock'
- | 'unsplash'
- | 'url'
+ "camera"
+ | "dropbox"
+ | "facebook"
+ | "gettyimages"
+ | "google_drive"
+ | "image_search"
+ | "instagram"
+ | "istock"
+ | "local"
+ | "shutterstock"
+ | "unsplash"
+ | "url"
>;
// Cropping
@@ -100,100 +199,12 @@ export interface CldUploadWidgetPropsOptions {
showUploadMoreButton?: boolean;
singleUploadAutoClose?: boolean;
}
-export type ResultsEvents =
- | { event: 'abort'; info: { ids: string[]; all: boolean } }
- | { event: 'batch-cancelled'; info: { reason: 'MAX_EXCEEDED' | 'INVALID_PUBLIC_ID' } }
- | { event: 'close' }
- | { event: 'display-changed'; info: 'show' | 'hidden' | 'minimized' | 'expanded' }
- | { event: 'publicid'; info: { id: string } }
- | { event: 'queues-end'; info: Record }
- | { event: 'queues-start' }
- | {
- event: 'retry';
- info: {
- ids: string[];
- all: boolean;
- };
- }
- | {
- event: 'show-completed';
- info: {
- items: [
- {
- id: string;
- name: string;
- size: number;
- type: string;
- done: boolean;
- progress: number;
- file: Record;
- uploadInfo: Record;
- }
- ];
- };
- }
- | {
- event: 'source-changed';
- info: { source: string };
- }
- | {
- event: 'success';
- info: Record;
- }
- | {
- event: 'tags';
- info: {
- tags: string[];
- }; //tags currently in the field
- }
- | {
- event: 'upload-added';
- info: {
- file: {
- lastModified: string;
- lastModifiedDate: string;
- name: string;
- size: number;
- type: string;
- };
- publicId: 'public-id';
- };
- };
-export interface UploadWidget {
- open: (_?: any, { files }?: { files?: [URL] }) => void;
- close: ({ quiet }: { quiet: boolean }) => void;
- update: (options: CldUploadWidgetPropsOptions) => void;
- hide: () => void;
- show: () => void;
- minimize: () => void;
- destroy: ({ removeThumbnails }: { removeThumbnails: boolean }) => void;
- isShowing: () => boolean;
- isMinimized: () => boolean;
- isDestroyed: () => boolean;
-}
-
-export type CldUploadWidgetProps = {
- onError?: (error: UploadError, widget?: UploadWidget) => void;
- onUpload?: (result: ResultsEvents, widget?: UploadWidget) => void;
- options?: CldUploadWidgetPropsOptions;
- onOpen?: (widget?: UploadWidget) => void;
- onClose?: (widget?: UploadWidget) => void;
- uploadPreset?: string;
- signatureEndpoint?: string;
-}
-/* From the docs:
-* The callback method has the following signature function(error, result) where error is either null if successful
-* or an error message if there was a failure,
-*/
-type UploadError = {
- status: string;
- statusText: string;
-} | null
-export type ResultCallback = (error: UploadError, result: ResultsEvents) => void;
-export type CreateUploadWidgetProps = {
- cloudName: string;
- uploadPreset?: string; // @TODO this should be optional if signature is present
- apiKey?: string;
-} & CldUploadWidgetPropsOptions;
+export type CldUploadEventCallback = (results: CldUploadWidgetResults, widget: CldUploadEventCallbackWidget) => void;
+export type CldUploadEventCallbackNoOptions = (results: CldUploadWidgetResults, widget: CldUploadWidgetWidgetInstance) => void;
+export type CldUploadEventCallbackWidgetOnly = (widget: CldUploadWidgetWidgetInstance) => void;
+export type CldUploadEventCallbackError = (error: CldUploadWidgetError, widget: CldUploadWidgetWidgetInstance) => void;
+export type CldUploadEventCallbackWidget = {
+ widget: CldUploadWidgetWidgetInstance;
+} & CldUploadWidgetInstanceMethods;