Skip to content

Commit 3e155b4

Browse files
committed
feat: improve core functionality and user interface
This commit includes several important changes: - pages/index.html: Enhanced popup interface for better user experience - pages/options.html: Updated configuration options and settings layout - scripts/background.js: Improved background processing for image handling - scripts/content_script.js: Fixed image detection and processing logic - scripts/utils.js: Optimized utility functions for better performance These changes collectively improve the Pixely Sort & Save extension's ability to detect, process, and save images from web pages.
1 parent 1476e43 commit 3e155b4

File tree

5 files changed

+432
-16
lines changed

5 files changed

+432
-16
lines changed

pages/index.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
}
4343

4444
.header img {
45-
width: 48px;
46-
height: 48px;
45+
width: 54px;
46+
height: 54px;
4747
margin-bottom: 0.5rem;
4848
}
4949

@@ -177,7 +177,7 @@
177177
<body>
178178
<main>
179179
<div class="header">
180-
<img src="../icons/icon.png" alt="Logo Pixely Sort & Save" />
180+
<img src="../icons/iconx256.png" alt="Logo Pixely Sort & Save" />
181181
<h1>Pixely Sort & Save</h1>
182182
</div>
183183

@@ -235,6 +235,10 @@ <h1>Pixely Sort & Save</h1>
235235
</div>
236236
</footer>
237237

238+
<script
239+
type="application/javascript"
240+
src="../lib/browser-polyfill.min.js"
241+
></script>
238242
<script src="../scripts/index.js"></script>
239243
</body>
240244
</html>

pages/options.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@
372372
}
373373

374374
.header img {
375-
width: 64px;
376-
height: 64px;
375+
width: 72px;
376+
height: 72px;
377377
margin-bottom: 0.5rem;
378378
}
379379

@@ -433,7 +433,7 @@
433433
<body>
434434
<main>
435435
<div class="header">
436-
<img src="../icons/icon.png" alt="Logo Pixely Sort & Save" />
436+
<img src="../icons/iconx256.png" alt="Logo Pixely Sort & Save" />
437437
<h1>Pixely Sort & Save</h1>
438438
</div>
439439

@@ -532,6 +532,10 @@ <h2>Configurar Subpastas</h2>
532532
</div>
533533
</footer>
534534

535+
<script
536+
type="application/javascript"
537+
src="../lib/browser-polyfill.min.js"
538+
></script>
535539
<script src="../scripts/options.js"></script>
536540
</body>
537541
</html>

scripts/background.js

Lines changed: 206 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,44 @@ const MESSAGE_ACTIONS = {
2424
SHOW_SUCCESS_TOAST: "showDownloadSuccessToast",
2525
SHOW_ERROR_TOAST: "showDownloadErrorToast",
2626
};
27+
let lastClickPosition = { x: null, y: null };
2728

2829
// ============================================================================
2930
// CONTEXT MENU MANAGEMENT
3031
// ============================================================================
3132
async function createLocalSaveContextMenus(suffixes) {
3233
await removeLocalSaveContextMenus();
3334

35+
const isFirefox = DetectBrowser.isFirefox();
36+
37+
const icons = {
38+
16: "../icons/folder.png",
39+
32: "../icons/folder.png",
40+
48: "../icons/folder.png",
41+
128: "../icons/folder.png",
42+
};
43+
44+
const menuCreateOptions = {
45+
documentUrlPatterns: ["https://*/*", "http://*/*"],
46+
contexts: ["frame", "image", "page"],
47+
};
48+
49+
if (isFirefox) {
50+
menuCreateOptions.icons = icons;
51+
}
52+
3453
browser.contextMenus.create({
3554
id: CONTEXT_MENU_IDS.LOCAL_SAVE_PARENT,
3655
title: "Salvar imagem (local)",
37-
contexts: ["image"],
56+
...menuCreateOptions,
3857
});
3958

4059
suffixes.forEach((suffix) => {
4160
browser.contextMenus.create({
4261
id: `${CONTEXT_MENU_IDS.LOCAL_SAVE_PREFIX}${suffix}`,
4362
parentId: CONTEXT_MENU_IDS.LOCAL_SAVE_PARENT,
4463
title: suffix,
45-
contexts: ["image"],
64+
...menuCreateOptions,
4665
});
4766
});
4867
}
@@ -52,6 +71,12 @@ function createDropboxSaveContextMenu() {
5271
id: CONTEXT_MENU_IDS.DROPBOX_SAVE,
5372
title: "Salvar no Dropbox",
5473
contexts: ["image"],
74+
icons: {
75+
16: "../icons/dropbox.png",
76+
32: "../icons/dropbox.png",
77+
48: "../icons/dropbox.png",
78+
128: "../icons/dropbox.png",
79+
},
5580
});
5681
}
5782

@@ -97,7 +122,108 @@ async function updateContextMenusBasedOnSettings(settings) {
97122
}
98123

99124
// ============================================================================
100-
// IMAGE DOWNLOAD
125+
// BLOB HANDLING
126+
// ============================================================================
127+
function isBlobUrl(url) {
128+
return url && url.startsWith("blob:");
129+
}
130+
131+
async function downloadBlobUrl(blobUrl, tabId) {
132+
try {
133+
// Enviar mensagem para o content script fazer o fetch
134+
const response = await browser.tabs.sendMessage(tabId, {
135+
action: "FETCH_BLOB_DATA",
136+
blobUrl: blobUrl,
137+
});
138+
139+
if (response.success) {
140+
return {
141+
arrayBuffer: response.arrayBuffer,
142+
mimeType: response.mimeType,
143+
extension: response.extension,
144+
};
145+
} else {
146+
throw new Error(response.error);
147+
}
148+
} catch (error) {
149+
console.error("Erro ao processar blob URL:", error);
150+
throw error;
151+
}
152+
}
153+
154+
async function CreateBlobUrl(arrayBuffer, mimeType) {
155+
let bufferToUse;
156+
if (Array.isArray(arrayBuffer)) {
157+
bufferToUse = new Uint8Array(arrayBuffer);
158+
} else {
159+
bufferToUse = arrayBuffer;
160+
}
161+
162+
const blob = new Blob([bufferToUse], { type: mimeType });
163+
const downloadUrl = URL.createObjectURL(blob);
164+
165+
console.log("Blob URL created:", downloadUrl);
166+
167+
return downloadUrl;
168+
}
169+
170+
async function resolveImagemInput(info, tab) {
171+
if (!info?.mediaType || info.mediaType !== "image") {
172+
const response = await browser.tabs.sendMessage(tab.id, {
173+
action: "download-image",
174+
position: lastClickPosition,
175+
});
176+
177+
console.log({ response });
178+
179+
if (response && response.url) {
180+
const { url: imageUrl, type } = response;
181+
if (type === "blob") {
182+
const blobData = await downloadBlobUrl(imageUrl, tab.id);
183+
const url = await CreateBlobUrl(
184+
blobData.arrayBuffer,
185+
blobData.mimeType
186+
);
187+
188+
return {
189+
url: url,
190+
fileExtension: blobData.extension,
191+
};
192+
} else {
193+
const url = imageUrl;
194+
195+
return {
196+
url: url,
197+
fileExtension: getFileExtension(url),
198+
};
199+
}
200+
}
201+
}
202+
203+
if (!info.srcUrl) {
204+
return null;
205+
}
206+
207+
const isBlob = isBlobUrl(info.srcUrl);
208+
209+
if (isBlob) {
210+
const blobData = await downloadBlobUrl(info.srcUrl, tab.id);
211+
const url = await CreateBlobUrl(blobData.arrayBuffer, blobData.mimeType);
212+
213+
return {
214+
url: url,
215+
fileExtension: blobData.extension,
216+
};
217+
}
218+
219+
return {
220+
url: info.srcUrl,
221+
fileExtension: getFileExtension(info.srcUrl),
222+
};
223+
}
224+
225+
// ============================================================================
226+
// IMAGE DOWNLOAD (UPDATED)
101227
// ============================================================================
102228
function buildDownloadFilename(suffix, fileExtension) {
103229
const randomName = generateRandomName(10);
@@ -125,6 +251,7 @@ async function sendSuccessNotification(tabId, message) {
125251
async function downloadImageToLocal(
126252
imageUrl,
127253
folderSuffix = "direct",
254+
fileExtension = null,
128255
tabId = null
129256
) {
130257
if (!imageUrl) {
@@ -133,19 +260,56 @@ async function downloadImageToLocal(
133260
}
134261

135262
try {
136-
const fileExtension = getFileExtension(imageUrl);
263+
let downloadUrl = imageUrl;
264+
265+
// Handle data URLs by converting to blob URLs
266+
if (downloadUrl.startsWith("data:")) {
267+
try {
268+
const response = await fetch(downloadUrl);
269+
const blob = await response.blob();
270+
downloadUrl = URL.createObjectURL(blob);
271+
} catch (fetchError) {
272+
console.error("Error converting data URL to blob URL:", fetchError);
273+
await browser.tabs.sendMessage(tabId, {
274+
action: MESSAGE_ACTIONS.SHOW_ERROR_TOAST,
275+
message: "Erro ao processar a imagem. Tente novamente.",
276+
});
277+
return;
278+
}
279+
}
280+
137281
const filename = buildDownloadFilename(folderSuffix, fileExtension);
138282

139283
await browser.downloads.download({
140-
url: imageUrl,
284+
url: downloadUrl,
141285
filename: filename,
142286
conflictAction: "uniquify",
143287
saveAs: false,
144288
});
145289

290+
// Clean up any blob URLs we created
291+
if (
292+
(isBlobUrl(downloadUrl) || downloadUrl.startsWith("blob:")) &&
293+
downloadUrl !== imageUrl
294+
) {
295+
setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000);
296+
}
297+
146298
await sendSuccessNotification(tabId, "Imagem salva com sucesso!");
147299
} catch (error) {
148300
console.error("Erro ao baixar imagem:", error);
301+
302+
// Send error notification to user
303+
if (tabId) {
304+
try {
305+
await browser.tabs.sendMessage(tabId, {
306+
action: MESSAGE_ACTIONS.SHOW_ERROR_TOAST,
307+
message: "Erro ao salvar imagem. Tente novamente.",
308+
});
309+
} catch (msgError) {
310+
console.error("Erro ao enviar mensagem de erro:", msgError);
311+
}
312+
}
149313
}
150314
}
151315

@@ -216,20 +380,37 @@ async function handleStorageChanges(changes) {
216380
// ============================================================================
217381
// EVENT HANDLERS
218382
// ============================================================================
219-
function handleContextMenuClick(info, tab) {
383+
384+
async function handleContextMenuClick(info, tab) {
220385
const menuItemId = info.menuItemId;
221386

222387
if (menuItemId.startsWith(CONTEXT_MENU_IDS.LOCAL_SAVE_PREFIX)) {
223388
const folderSuffix = menuItemId.replace(
224389
CONTEXT_MENU_IDS.LOCAL_SAVE_PREFIX,
225390
""
226391
);
227-
downloadImageToLocal(info.srcUrl, folderSuffix, tab?.id);
392+
393+
const resolvedImage = await resolveImagemInput(info, tab);
394+
if (!resolvedImage || !resolvedImage.url) {
395+
console.error("Failed to resolve image input.");
396+
return;
397+
}
398+
399+
console.log({ resolvedImage });
400+
401+
const { url, fileExtension } = resolvedImage;
402+
downloadImageToLocal(url, folderSuffix, fileExtension, tab?.id);
228403
return;
229404
}
230405

231406
if (menuItemId === CONTEXT_MENU_IDS.DROPBOX_SAVE) {
232-
saveImageToDropbox(info.srcUrl, tab?.id);
407+
const resolvedImage = await resolveImagemInput(info, tab);
408+
if (!resolvedImage || !resolvedImage.url) {
409+
console.error("Failed to resolve image input for Dropbox.");
410+
return;
411+
}
412+
413+
saveImageToDropbox(resolvedImage.url, tab?.id);
233414
return;
234415
}
235416
}
@@ -240,7 +421,16 @@ async function handleDoubleClickDownload(imageUrl, tabId) {
240421
);
241422

242423
if (doubleClickEnabled) {
243-
await downloadImageToLocal(imageUrl, "direct", tabId);
424+
const resolvedImage = await resolveImagemInput(
425+
{ srcUrl: imageUrl, mediaType: "image" },
426+
tabId
427+
);
428+
await downloadImageToLocal(
429+
resolvedImage.url,
430+
"direct",
431+
resolvedImage.fileExtension,
432+
tabId
433+
);
244434
} else {
245435
console.log("Download por duplo clique desativado nas configurações.");
246436
await sendSuccessNotification(
@@ -271,6 +461,13 @@ async function handleRuntimeMessage(message, sender, sendResponse) {
271461
});
272462
break;
273463

464+
case "context-menu-position":
465+
lastClickPosition = {
466+
x: message.x,
467+
y: message.y,
468+
};
469+
break;
470+
274471
default:
275472
console.warn(`Unknown action: ${action}`);
276473
}

0 commit comments

Comments
 (0)