diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ccad55c..682d8e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,13 +44,14 @@ Before you begin, ensure you have the following installed: ```json { - "test-cloud": { - "apiKey": "your-test-api-key", - "apiSecret": "your-test-api-secret", - "uploadPreset": "your-test-upload-preset" + "your-cloud-name": { + "apiKey": "your-api-key", + "apiSecret": "your-api-secret" } } ``` + + > **Note:** The **cloud name is the key** (the property name). You can optionally add `"uploadPreset"` if you want a default preset. 4. **Build the extension** ```bash diff --git a/README.md b/README.md index dc3abb4..488a0cb 100644 --- a/README.md +++ b/README.md @@ -40,19 +40,15 @@ Auto-created with placeholder content on first use: ```json { - "your-cloud-name-1": { - "apiKey": "", - "apiSecret": "", - "uploadPreset": "" - }, - "your-cloud-name-2": { - "apiKey": "", - "apiSecret": "", - "uploadPreset": "" + "REPLACE_WITH_YOUR_CLOUD_NAME": { + "apiKey": "REPLACE_WITH_YOUR_API_KEY", + "apiSecret": "REPLACE_WITH_YOUR_API_SECRET" } } ``` +> **Note:** The **cloud name is the key** (the property name in the JSON object). You can optionally add `"uploadPreset": "your-preset-name"` if you want to use a default upload preset. + ### 2. **Workspace Config** (Optional override) You can also include a project-specific config: ``` @@ -87,13 +83,13 @@ Once a valid configuration has been added, the active environment will be shown - **File Browser** – Click "Browse Files" to select files from your system - **Remote URL** – Paste a URL to upload from a remote source - **Folder Selection** – Choose the destination folder from a dropdown -- **Upload Presets** – Select from your configured upload presets (view preset settings with the "Settings" toggle) +- **Upload Presets** – Optionally select from your configured upload presets (signed uploads work without a preset) - **Custom Public ID** – Specify a custom public ID for single file uploads - **Tags** – Add comma-separated tags to your uploads - **Progress Tracking** – See real-time upload progress for each file - **Uploaded Assets** – View thumbnails of uploaded assets, click to preview, copy URL or public ID -**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets. +**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets (optional). ![Uploading assets](https://res.cloudinary.com/demo/video/upload/w_1200/f_auto:animated/q_auto/e_accelerate:100/e_loop/docs/vscode-extension-vid3) diff --git a/package.json b/package.json index 25641e8..f00b6a4 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,9 @@ "title": " Refresh" }, { - "command": "cloudinary.setResourceFilter", - "title": " Filter", + "command": "cloudinary.viewOptions", + "title": "View Options", + "icon": "$(filter)", "category": "Cloudinary" }, { @@ -104,6 +105,7 @@ { "command": "cloudinary.openGlobalConfig", "title": "Config", + "icon": "$(gear)", "category": "Cloudinary" }, { @@ -127,17 +129,17 @@ { "command": "cloudinary.searchAssets", "when": "view == cloudinaryMediaLibrary", - "group": "1_search@1" + "group": "navigation@3" }, { - "command": "cloudinary.setResourceFilter", + "command": "cloudinary.viewOptions", "when": "view == cloudinaryMediaLibrary", - "group": "1_search@2" + "group": "navigation@4" }, { "command": "cloudinary.openGlobalConfig", "when": "view == cloudinaryMediaLibrary", - "group": "2_config@1" + "group": "navigation@5" } ], "view/item/context": [ diff --git a/src/commands/loadMoreAssets.ts b/src/commands/loadMoreAssets.ts deleted file mode 100644 index c0c57fd..0000000 --- a/src/commands/loadMoreAssets.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as vscode from "vscode"; -import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider"; - -/** - * Registers the 'load more assets' command to handle pagination in the Cloudinary tree. - * @param context - VS Code extension context. - * @param provider - Instance of CloudinaryTreeDataProvider used to fetch and update asset nodes. - */ -function registerLoadMore( - context: vscode.ExtensionContext, - provider: CloudinaryTreeDataProvider -) { - context.subscriptions.push( - vscode.commands.registerCommand( - "cloudinary.loadMoreAssets", - async (folderPath: string, nextCursor: string) => { - if (!nextCursor) { - vscode.window.showErrorMessage("No more assets to load."); - return; - } - - const newAssets = await provider.fetchFoldersAndAssets( - folderPath, - nextCursor, - true - ); - - if (newAssets.length === 0) { - vscode.window.showErrorMessage("No additional assets found."); - return; - } - - provider.updateLoadMoreItem(folderPath, nextCursor); - - provider.refresh( - { - folderPath, - nextCursor - }, - true - ); - } - ) - ); -} - -export default registerLoadMore; \ No newline at end of file diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 0e8ec58..9a50e14 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -1,8 +1,7 @@ import * as vscode from "vscode"; import registerSearch from "./searchAssets"; -import registerFilter from "./setResourceFilter"; +import registerViewOptions from "./viewOptions"; import registerPreview from "./previewAsset"; -import registerLoadMore from "./loadMoreAssets"; import registerUpload from "./uploadWidget"; import registerClipboard from "./copyCommands"; import registerSwitchEnv from "./switchEnvironment"; @@ -34,9 +33,8 @@ function registerAllCommands( registerSearch(context, provider); registerClearSearch(context, provider); - registerFilter(context, provider); + registerViewOptions(context, provider); registerPreview(context); - registerLoadMore(context, provider); registerUpload(context, provider); registerClipboard(context); registerSwitchEnv(context, provider, statusBar); diff --git a/src/commands/setResourceFilter.ts b/src/commands/setResourceFilter.ts deleted file mode 100644 index a576005..0000000 --- a/src/commands/setResourceFilter.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as vscode from "vscode"; -import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider"; - -type ResourceType = "all" | "image" | "video" | "raw"; - -/** - * Registers a command to let users filter Cloudinary assets by resource type. - * @param context - VS Code extension context. - * @param provider - Cloudinary tree data provider instance. - */ -function registerFilter( - context: vscode.ExtensionContext, - provider: CloudinaryTreeDataProvider -) { - context.subscriptions.push( - vscode.commands.registerCommand( - "cloudinary.setResourceFilter", - async () => { - const options: ResourceType[] = ["all", "image", "video", "raw"]; - const selected = await vscode.window.showQuickPick(options, { - placeHolder: "Filter Cloudinary assets by type", - }); - - if (selected) { - provider.refresh({ resourceTypeFilter: selected as ResourceType }); - } - } - ) - ); -} - -export default registerFilter; diff --git a/src/commands/switchEnvironment.ts b/src/commands/switchEnvironment.ts index c27d331..743d2f6 100644 --- a/src/commands/switchEnvironment.ts +++ b/src/commands/switchEnvironment.ts @@ -8,7 +8,7 @@ import { generateUserAgent } from "../utils/userAgent"; interface CloudinaryEnvironment { apiKey: string; apiSecret: string; - uploadPreset: string; + uploadPreset?: string; // Optional: Default upload preset } /** @@ -44,7 +44,7 @@ function registerSwitchEnv( provider.cloudName = selected; provider.apiKey = env.apiKey; provider.apiSecret = env.apiSecret; - provider.uploadPreset = env.uploadPreset; + provider.uploadPreset = env.uploadPreset || null; const cacheKey = `cloudinary.dynamicFolders.${selected}`; const cachedFolderMode = context.globalState.get(cacheKey) as boolean | undefined; @@ -68,7 +68,13 @@ function registerSwitchEnv( api_secret: env.apiSecret, }); - statusBar.text = `$(cloud) ${selected}`; + // Update status bar with folder mode indicator + const folderMode = provider.dynamicFolders ? "Dynamic" : "Fixed"; + statusBar.text = `$(cloud) ${selected} $(folder) ${folderMode}`; + statusBar.tooltip = provider.dynamicFolders + ? "Click to switch Cloudinary environment\n\nDynamic Folders: Assets can be organized independently of their public ID" + : "Click to switch Cloudinary environment\n\nFixed Folders: Asset folder is determined by public ID path"; + provider.refresh({ folderPath: '', nextCursor: null, diff --git a/src/commands/uploadWidget.ts b/src/commands/uploadWidget.ts index 14de3e1..231ac08 100644 --- a/src/commands/uploadWidget.ts +++ b/src/commands/uploadWidget.ts @@ -44,14 +44,8 @@ function registerUpload( context.subscriptions.push( vscode.commands.registerCommand("cloudinary.openUploadWidget", async () => { try { + // Fetch presets but don't require them - signed uploads work without presets await provider.fetchUploadPresets(); - const uploadPreset = provider.getCurrentUploadPreset(); - - if (!uploadPreset) { - vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account."); - return; - } - openOrRevealUploadPanel("", provider, context); } catch (err: any) { vscode.window.showErrorMessage(`Failed to open upload widget: ${err.message}`); @@ -65,14 +59,8 @@ function registerUpload( "cloudinary.uploadToFolder", async (folderItem: { label: string; data: { path?: string } }) => { try { + // Fetch presets but don't require them - signed uploads work without presets await provider.fetchUploadPresets(); - const uploadPreset = provider.getCurrentUploadPreset(); - - if (!uploadPreset) { - vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account."); - return; - } - const folderPath = folderItem.data.path || ""; openOrRevealUploadPanel(folderPath, provider, context); } catch (err: any) { @@ -234,12 +222,17 @@ function createUploadPanel( } // Get upload options based on current preset, folder, and optional overrides - const getUploadOptions = (presetName: string, folder: string, publicId?: string, tags?: string, fileName?: string) => { + // Upload preset is optional - signed uploads work without one + const getUploadOptions = (presetName: string | null | undefined, folder: string, publicId?: string, tags?: string, fileName?: string) => { const options: Record = { - upload_preset: presetName, resource_type: "auto", }; + // Only add upload_preset if one is selected (not null, undefined, or empty string) + if (presetName && presetName.trim()) { + options.upload_preset = presetName; + } + // Add folder configuration if (folder) { if (provider.dynamicFolders) { @@ -276,7 +269,8 @@ function createUploadPanel( }; if (message.command === "uploadFile" && message.dataUri && message.fileId) { - const presetName = message.preset || currentPreset; + // Use nullish coalescing - empty string "" means "no preset" (signed upload) + const presetName = message.preset !== undefined ? message.preset : currentPreset; const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath; const options = getUploadOptions(presetName, folder, message.publicId, message.tags, message.fileName); @@ -315,7 +309,8 @@ function createUploadPanel( } if (message.command === "uploadUrl" && message.url) { - const presetName = message.preset || currentPreset; + // Use nullish coalescing - empty string "" means "no preset" (signed upload) + const presetName = message.preset !== undefined ? message.preset : currentPreset; const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath; // Try to extract filename from URL for display_name let urlFileName: string | undefined; @@ -1009,10 +1004,11 @@ function getWebviewContent(
-
Upload Preset
- +
Upload Preset (optional)
+ ${provider.uploadPresets.length > 0 ? '' : ''}