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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Thumbs.db
*.swo
*~
.cursor/
.claude/
CLAUDE.md

# Temporary files
*.tmp
Expand Down
2 changes: 1 addition & 1 deletion src/commands/uploadWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ function createUploadPanel(
const presetsJson = JSON.stringify(provider.uploadPresets);
const initScript = `
initUploadWidget({
cloudName: "${escapeHtml(cloudName)}",
cloudName: ${JSON.stringify(cloudName)},
presets: ${presetsJson}
});
`;
Expand Down
126 changes: 82 additions & 44 deletions src/webview/client/upload-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,28 @@ function addToQueue(fileId: string, fileName: string): void {

const displayName = truncateString(fileName, 30, "middle");

item.innerHTML = `
<span class="queue-item__name" title="${fileName}">${displayName}</span>
<div class="queue-item__progress">
<div class="progress">
<div class="progress__bar" style="width: 0%"></div>
</div>
</div>
<span class="queue-item__status">Pending...</span>
`;
const nameEl = document.createElement("span");
nameEl.className = "queue-item__name";
nameEl.title = fileName;
nameEl.textContent = displayName;

const progressBar = document.createElement("div");
progressBar.className = "progress__bar";
progressBar.style.width = "0%";
const progress = document.createElement("div");
progress.className = "progress";
progress.appendChild(progressBar);
const progressWrapper = document.createElement("div");
progressWrapper.className = "queue-item__progress";
progressWrapper.appendChild(progress);

const statusEl = document.createElement("span");
statusEl.className = "queue-item__status";
statusEl.textContent = "Pending...";

item.appendChild(nameEl);
item.appendChild(progressWrapper);
item.appendChild(statusEl);

uploadQueue.appendChild(item);
}
Expand Down Expand Up @@ -378,55 +391,80 @@ function renderUploadedAsset(asset: AssetData): void {
const card = document.createElement("div");
card.className = "asset-card";

const fileIcon =
"<svg width='48' height='48' viewBox='0 0 24 24' fill='currentColor'><path d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20ZM9 13H15V15H9V13ZM9 17H15V19H9V17Z'/></svg>";
// Static SVG icon — no user data
const fileIconSvg = "<svg width='48' height='48' viewBox='0 0 24 24' fill='currentColor'><path d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20ZM9 13H15V15H9V13ZM9 17H15V19H9V17Z'/></svg>";

// Thumbnail wrapper
const thumbnailWrapper = document.createElement("div");
thumbnailWrapper.className = "asset-card__thumbnail";
thumbnailWrapper.setAttribute("data-asset-id", asset.public_id);

let mediaHtml: string;
if (thumbnailUrl) {
mediaHtml = `
<div class="asset-card__thumbnail" data-asset-id="${asset.public_id}">
<img class="asset-card__image" src="${thumbnailUrl}" alt="Thumbnail" />
<div class="asset-card__icon fallback" style="display:none;">${fileIcon}</div>
</div>
`;
const img = document.createElement("img");
img.className = "asset-card__image";
img.src = thumbnailUrl;
img.alt = "Thumbnail";
const fallbackEl = document.createElement("div");
fallbackEl.className = "asset-card__icon fallback";
fallbackEl.style.display = "none";
fallbackEl.innerHTML = fileIconSvg;
img.addEventListener("error", function () {
this.style.display = "none";
fallbackEl.style.display = "flex";
});
thumbnailWrapper.appendChild(img);
thumbnailWrapper.appendChild(fallbackEl);
} else {
mediaHtml = `
<div class="asset-card__thumbnail" data-asset-id="${asset.public_id}">
<div class="asset-card__icon">${fileIcon}</div>
</div>
`;
const iconEl = document.createElement("div");
iconEl.className = "asset-card__icon";
iconEl.innerHTML = fileIconSvg;
thumbnailWrapper.appendChild(iconEl);
}

// Folder
const folderEl = document.createElement("div");
folderEl.className = "asset-card__folder";
const folderCode = document.createElement("code");
folderCode.textContent = folderDisplay;
folderEl.appendChild(document.createTextNode("📂 "));
folderEl.appendChild(folderCode);

// Asset ID
const displayId = truncateString(asset.public_id, 25, "start");
const idEl = document.createElement("div");
idEl.className = "asset-card__id";
idEl.title = asset.public_id;
idEl.textContent = displayId;

// Action buttons
const actionsEl = document.createElement("div");
actionsEl.className = "asset-card__actions";

const copyUrlBtn = document.createElement("button");
copyUrlBtn.className = "btn btn--secondary btn--sm btn--copy";
copyUrlBtn.setAttribute("data-copy", asset.secure_url);
copyUrlBtn.textContent = "Copy URL";

card.innerHTML = `
${mediaHtml}
<div class="asset-card__folder">📂 <code>${folderDisplay}</code></div>
<div class="asset-card__id" title="${asset.public_id}">${displayId}</div>
<div class="asset-card__actions">
<button class="btn btn--secondary btn--sm btn--copy" data-copy="${asset.secure_url}">Copy URL</button>
<button class="btn btn--secondary btn--sm btn--copy" data-copy="${asset.public_id}">Copy ID</button>
</div>
`;
const copyIdBtn = document.createElement("button");
copyIdBtn.className = "btn btn--secondary btn--sm btn--copy";
copyIdBtn.setAttribute("data-copy", asset.public_id);
copyIdBtn.textContent = "Copy ID";

actionsEl.appendChild(copyUrlBtn);
actionsEl.appendChild(copyIdBtn);

card.appendChild(thumbnailWrapper);
card.appendChild(folderEl);
card.appendChild(idEl);
card.appendChild(actionsEl);

// Click thumbnail to preview
const thumbnailWrapper = card.querySelector(".asset-card__thumbnail");
if (thumbnailWrapper && vscode) {
if (vscode) {
thumbnailWrapper.addEventListener("click", () => {
vscode.postMessage({ command: "openAsset", asset: asset });
});
}

// Handle image load errors
const img = card.querySelector<HTMLImageElement>(".asset-card__image");
if (img) {
img.addEventListener("error", function () {
this.style.display = "none";
const fallback = this.parentElement?.querySelector<HTMLElement>(".fallback");
if (fallback) {fallback.style.display = "flex";}
});
}

// Initialize copy buttons
card.querySelectorAll<HTMLElement>(".btn--copy[data-copy]").forEach((btn) => {
btn.addEventListener("click", async (e) => {
Expand Down
62 changes: 0 additions & 62 deletions src/webview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export * from "./components";

// Utility scripts
export * from "./utils";
import { escapeHtml } from "./utils/helpers";

// Icons
export {
Expand Down Expand Up @@ -115,65 +114,4 @@ export function getCompleteScripts(): string {
].join("\n");
}

/**
* Creates a complete HTML document for a webview panel.
*
* @param options - Document configuration
* @returns Complete HTML string
*
* @example
* ```typescript
* panel.webview.html = createWebviewDocument({
* title: 'Upload to Cloudinary',
* body: createPanel({ content: '...' }),
* additionalStyles: '.custom { color: red; }',
* additionalScripts: 'console.log("Hello");'
* });
* ```
*/
export function createWebviewDocument(options: {
/** Document title */
title: string;
/** Body content (HTML string) */
body: string;
/** Additional CSS to include */
additionalStyles?: string;
/** Additional JavaScript to include */
additionalScripts?: string;
/** Whether to include all component scripts (default: true) */
includeScripts?: boolean;
}): string {
const {
title,
body,
additionalStyles = "",
additionalScripts = "",
includeScripts = true,
} = options;

const styles = getCompleteStyles();
const scripts = includeScripts ? getCompleteScripts() : "";

return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${escapeHtml(title)}</title>
<style>
${styles}
${additionalStyles}
</style>
</head>
<body>
${body}
<script>
${scripts}
${additionalScripts}
</script>
</body>
</html>
`;
}

Loading