Skip to content

Commit c2cd90a

Browse files
authored
Feat/optional upload preset (#8)
* chore: Update configuration and documentation for optional upload presets - Modified CONTRIBUTING.md and README.md to clarify that the upload preset is optional and can be omitted for signed uploads. - Updated code to handle cases where upload presets are not provided, ensuring that the application defaults to signed uploads. - Enhanced user interface elements to indicate that upload presets are optional, improving user experience and reducing confusion. * feat: Add sorting functionality and enhance status bar indicators - Introduced a new command for setting sort order in the Cloudinary media library. - Updated the status bar to display folder mode (Dynamic/Fixed) and improved tooltips for better user guidance. - Removed the 'load more assets' command and replaced it with a loading indicator during asset prefetching. - Refactored asset fetching logic to support dynamic folder handling and improved pagination management. * refactor: Replace resource filter and sort order commands with view options - Removed the commands for setting resource filter and sort order in the Cloudinary media library. - Introduced a new command for viewing options, enhancing the user interface. - Updated command registration to reflect these changes and improve navigation within the media library.
1 parent 9bf56fc commit c2cd90a

14 files changed

+544
-255
lines changed

CONTRIBUTING.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ Before you begin, ensure you have the following installed:
4444

4545
```json
4646
{
47-
"test-cloud": {
48-
"apiKey": "your-test-api-key",
49-
"apiSecret": "your-test-api-secret",
50-
"uploadPreset": "your-test-upload-preset"
47+
"your-cloud-name": {
48+
"apiKey": "your-api-key",
49+
"apiSecret": "your-api-secret"
5150
}
5251
}
5352
```
53+
54+
> **Note:** The **cloud name is the key** (the property name). You can optionally add `"uploadPreset"` if you want a default preset.
5455
5556
4. **Build the extension**
5657
```bash

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,15 @@ Auto-created with placeholder content on first use:
4040

4141
```json
4242
{
43-
"your-cloud-name-1": {
44-
"apiKey": "<your-api-key>",
45-
"apiSecret": "<your-api-secret>",
46-
"uploadPreset": "<your-default-upload-preset>"
47-
},
48-
"your-cloud-name-2": {
49-
"apiKey": "<your-api-key>",
50-
"apiSecret": "<your-api-secret>",
51-
"uploadPreset": "<your-default-upload-preset>"
43+
"REPLACE_WITH_YOUR_CLOUD_NAME": {
44+
"apiKey": "REPLACE_WITH_YOUR_API_KEY",
45+
"apiSecret": "REPLACE_WITH_YOUR_API_SECRET"
5246
}
5347
}
5448
```
5549

50+
> **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.
51+
5652
### 2. **Workspace Config** (Optional override)
5753
You can also include a project-specific config:
5854
```
@@ -87,13 +83,13 @@ Once a valid configuration has been added, the active environment will be shown
8783
- **File Browser** – Click "Browse Files" to select files from your system
8884
- **Remote URL** – Paste a URL to upload from a remote source
8985
- **Folder Selection** – Choose the destination folder from a dropdown
90-
- **Upload Presets**Select from your configured upload presets (view preset settings with the "Settings" toggle)
86+
- **Upload Presets**Optionally select from your configured upload presets (signed uploads work without a preset)
9187
- **Custom Public ID** – Specify a custom public ID for single file uploads
9288
- **Tags** – Add comma-separated tags to your uploads
9389
- **Progress Tracking** – See real-time upload progress for each file
9490
- **Uploaded Assets** – View thumbnails of uploaded assets, click to preview, copy URL or public ID
9591

96-
**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets.
92+
**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets (optional).
9793

9894
![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)
9995

package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@
8282
"title": " Refresh"
8383
},
8484
{
85-
"command": "cloudinary.setResourceFilter",
86-
"title": " Filter",
85+
"command": "cloudinary.viewOptions",
86+
"title": "View Options",
87+
"icon": "$(filter)",
8788
"category": "Cloudinary"
8889
},
8990
{
@@ -104,6 +105,7 @@
104105
{
105106
"command": "cloudinary.openGlobalConfig",
106107
"title": "Config",
108+
"icon": "$(gear)",
107109
"category": "Cloudinary"
108110
},
109111
{
@@ -127,17 +129,17 @@
127129
{
128130
"command": "cloudinary.searchAssets",
129131
"when": "view == cloudinaryMediaLibrary",
130-
"group": "1_search@1"
132+
"group": "navigation@3"
131133
},
132134
{
133-
"command": "cloudinary.setResourceFilter",
135+
"command": "cloudinary.viewOptions",
134136
"when": "view == cloudinaryMediaLibrary",
135-
"group": "1_search@2"
137+
"group": "navigation@4"
136138
},
137139
{
138140
"command": "cloudinary.openGlobalConfig",
139141
"when": "view == cloudinaryMediaLibrary",
140-
"group": "2_config@1"
142+
"group": "navigation@5"
141143
}
142144
],
143145
"view/item/context": [

src/commands/loadMoreAssets.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/commands/registerCommands.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as vscode from "vscode";
22
import registerSearch from "./searchAssets";
3-
import registerFilter from "./setResourceFilter";
3+
import registerViewOptions from "./viewOptions";
44
import registerPreview from "./previewAsset";
5-
import registerLoadMore from "./loadMoreAssets";
65
import registerUpload from "./uploadWidget";
76
import registerClipboard from "./copyCommands";
87
import registerSwitchEnv from "./switchEnvironment";
@@ -34,9 +33,8 @@ function registerAllCommands(
3433

3534
registerSearch(context, provider);
3635
registerClearSearch(context, provider);
37-
registerFilter(context, provider);
36+
registerViewOptions(context, provider);
3837
registerPreview(context);
39-
registerLoadMore(context, provider);
4038
registerUpload(context, provider);
4139
registerClipboard(context);
4240
registerSwitchEnv(context, provider, statusBar);

src/commands/setResourceFilter.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/commands/switchEnvironment.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { generateUserAgent } from "../utils/userAgent";
88
interface CloudinaryEnvironment {
99
apiKey: string;
1010
apiSecret: string;
11-
uploadPreset: string;
11+
uploadPreset?: string; // Optional: Default upload preset
1212
}
1313

1414
/**
@@ -44,7 +44,7 @@ function registerSwitchEnv(
4444
provider.cloudName = selected;
4545
provider.apiKey = env.apiKey;
4646
provider.apiSecret = env.apiSecret;
47-
provider.uploadPreset = env.uploadPreset;
47+
provider.uploadPreset = env.uploadPreset || null;
4848

4949
const cacheKey = `cloudinary.dynamicFolders.${selected}`;
5050
const cachedFolderMode = context.globalState.get(cacheKey) as boolean | undefined;
@@ -68,7 +68,13 @@ function registerSwitchEnv(
6868
api_secret: env.apiSecret,
6969
});
7070

71-
statusBar.text = `$(cloud) ${selected}`;
71+
// Update status bar with folder mode indicator
72+
const folderMode = provider.dynamicFolders ? "Dynamic" : "Fixed";
73+
statusBar.text = `$(cloud) ${selected} $(folder) ${folderMode}`;
74+
statusBar.tooltip = provider.dynamicFolders
75+
? "Click to switch Cloudinary environment\n\nDynamic Folders: Assets can be organized independently of their public ID"
76+
: "Click to switch Cloudinary environment\n\nFixed Folders: Asset folder is determined by public ID path";
77+
7278
provider.refresh({
7379
folderPath: '',
7480
nextCursor: null,

src/commands/uploadWidget.ts

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,8 @@ function registerUpload(
4444
context.subscriptions.push(
4545
vscode.commands.registerCommand("cloudinary.openUploadWidget", async () => {
4646
try {
47+
// Fetch presets but don't require them - signed uploads work without presets
4748
await provider.fetchUploadPresets();
48-
const uploadPreset = provider.getCurrentUploadPreset();
49-
50-
if (!uploadPreset) {
51-
vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account.");
52-
return;
53-
}
54-
5549
openOrRevealUploadPanel("", provider, context);
5650
} catch (err: any) {
5751
vscode.window.showErrorMessage(`Failed to open upload widget: ${err.message}`);
@@ -65,14 +59,8 @@ function registerUpload(
6559
"cloudinary.uploadToFolder",
6660
async (folderItem: { label: string; data: { path?: string } }) => {
6761
try {
62+
// Fetch presets but don't require them - signed uploads work without presets
6863
await provider.fetchUploadPresets();
69-
const uploadPreset = provider.getCurrentUploadPreset();
70-
71-
if (!uploadPreset) {
72-
vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account.");
73-
return;
74-
}
75-
7664
const folderPath = folderItem.data.path || "";
7765
openOrRevealUploadPanel(folderPath, provider, context);
7866
} catch (err: any) {
@@ -234,12 +222,17 @@ function createUploadPanel(
234222
}
235223

236224
// Get upload options based on current preset, folder, and optional overrides
237-
const getUploadOptions = (presetName: string, folder: string, publicId?: string, tags?: string, fileName?: string) => {
225+
// Upload preset is optional - signed uploads work without one
226+
const getUploadOptions = (presetName: string | null | undefined, folder: string, publicId?: string, tags?: string, fileName?: string) => {
238227
const options: Record<string, any> = {
239-
upload_preset: presetName,
240228
resource_type: "auto",
241229
};
242230

231+
// Only add upload_preset if one is selected (not null, undefined, or empty string)
232+
if (presetName && presetName.trim()) {
233+
options.upload_preset = presetName;
234+
}
235+
243236
// Add folder configuration
244237
if (folder) {
245238
if (provider.dynamicFolders) {
@@ -276,7 +269,8 @@ function createUploadPanel(
276269
};
277270

278271
if (message.command === "uploadFile" && message.dataUri && message.fileId) {
279-
const presetName = message.preset || currentPreset;
272+
// Use nullish coalescing - empty string "" means "no preset" (signed upload)
273+
const presetName = message.preset !== undefined ? message.preset : currentPreset;
280274
const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath;
281275
const options = getUploadOptions(presetName, folder, message.publicId, message.tags, message.fileName);
282276

@@ -315,7 +309,8 @@ function createUploadPanel(
315309
}
316310

317311
if (message.command === "uploadUrl" && message.url) {
318-
const presetName = message.preset || currentPreset;
312+
// Use nullish coalescing - empty string "" means "no preset" (signed upload)
313+
const presetName = message.preset !== undefined ? message.preset : currentPreset;
319314
const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath;
320315
// Try to extract filename from URL for display_name
321316
let urlFileName: string | undefined;
@@ -1009,10 +1004,11 @@ function getWebviewContent(
10091004
<!-- Preset Selector -->
10101005
<div class="setting-group">
10111006
<div class="preset-header">
1012-
<div class="setting-label">Upload Preset</div>
1013-
<button class="preset-details-toggle" id="presetDetailsToggle">Settings</button>
1007+
<div class="setting-label">Upload Preset <span style="opacity: 0.7; font-weight: normal;">(optional)</span></div>
1008+
${provider.uploadPresets.length > 0 ? '<button class="preset-details-toggle" id="presetDetailsToggle">Settings</button>' : ''}
10141009
</div>
10151010
<select id="presetSelect">
1011+
<option value="">No preset (signed upload)</option>
10161012
${provider.uploadPresets
10171013
.map(
10181014
(preset) => `
@@ -1146,7 +1142,13 @@ function getWebviewContent(
11461142
* Update preset details display
11471143
*/
11481144
function updatePresetDetails() {
1149-
const preset = presets.find(p => p.name === presetSelect.value);
1145+
const selectedValue = presetSelect.value;
1146+
if (!selectedValue) {
1147+
// No preset selected - using signed upload
1148+
presetDetails.textContent = 'Using signed upload (no preset required)';
1149+
return;
1150+
}
1151+
const preset = presets.find(p => p.name === selectedValue);
11501152
if (preset) {
11511153
presetDetails.textContent = formatPresetSettings(preset.settings);
11521154
}
@@ -1158,11 +1160,13 @@ function getWebviewContent(
11581160
/**
11591161
* Toggle preset details visibility
11601162
*/
1161-
presetDetailsToggle.addEventListener('click', () => {
1162-
const isVisible = presetDetails.classList.toggle('visible');
1163-
presetDetailsToggle.classList.toggle('expanded', isVisible);
1164-
presetDetailsToggle.textContent = isVisible ? 'Hide' : 'Settings';
1165-
});
1163+
if (presetDetailsToggle) {
1164+
presetDetailsToggle.addEventListener('click', () => {
1165+
const isVisible = presetDetails.classList.toggle('visible');
1166+
presetDetailsToggle.classList.toggle('expanded', isVisible);
1167+
presetDetailsToggle.textContent = isVisible ? 'Hide' : 'Settings';
1168+
});
1169+
}
11661170
11671171
/**
11681172
* Update preset details when selection changes
@@ -1185,10 +1189,10 @@ function getWebviewContent(
11851189
}
11861190
11871191
/**
1188-
* Get current preset
1192+
* Get current preset (returns null if no preset selected)
11891193
*/
11901194
function getCurrentPreset() {
1191-
return presetSelect.value;
1195+
return presetSelect.value || null;
11921196
}
11931197
11941198
/**

0 commit comments

Comments
 (0)