-
- {{ group }}
+
+
+
+
+ {{ group }}
-
+
+
+
+
+
+
+ Load More
+
+
+ Load All
+
+
+
diff --git a/src/tools/emoji-picker/index.ts b/src/tools/emoji-picker/index.ts
index 3a28cf0f19..a82123da2b 100644
--- a/src/tools/emoji-picker/index.ts
+++ b/src/tools/emoji-picker/index.ts
@@ -9,5 +9,5 @@ export const tool = defineTool({
keywords: ['emoji', 'picker', 'unicode', 'copy', 'paste'],
component: () => import('./emoji-picker.vue'),
icon: MoodSmile,
- createdAt: new Date('2023-08-07'),
+ createdAt: new Date(),
});
diff --git a/src/tools/image-resizer/image-resizer.vue b/src/tools/image-resizer/image-resizer.vue
new file mode 100644
index 0000000000..3c88ff1be2
--- /dev/null
+++ b/src/tools/image-resizer/image-resizer.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
Original Image Dimensions: {{ originalImageWidth }}x{{ originalImageHeight }}px
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
Preview: {{ imageWidth }}x{{ imageHeight }}px
+
+
+
Download Options:
+
+
+ Download JPG
+
+
+ Download PNG
+
+
+ Download BMP
+
+
+ Download SVG
+
+
+ Download ICO
+
+
+
+
+
+
+
diff --git a/src/tools/image-resizer/index.ts b/src/tools/image-resizer/index.ts
new file mode 100644
index 0000000000..6a3e754a75
--- /dev/null
+++ b/src/tools/image-resizer/index.ts
@@ -0,0 +1,12 @@
+import { Resize } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Image resizer',
+ path: '/image-resizer',
+ description: '',
+ keywords: ['image', 'resizer', 'favicon', 'jpg', 'jpeg', 'png', 'bmp', 'ico', 'svg'],
+ component: () => import('./image-resizer.vue'),
+ icon: Resize,
+ createdAt: new Date(),
+});
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 388cfaf494..6ed6c4d28b 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,6 +1,10 @@
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
+import { tool as jsonStringConverter } from './json-string-converter';
+import { tool as aiPromptSplitter } from './ai-prompt-splitter';
+import { tool as imageResizer } from './image-resizer';
+import { tool as multiLinkDownloader } from './multi-link-downloader';
import { tool as emailNormalizer } from './email-normalizer';
import { tool as asciiTextDrawer } from './ascii-text-drawer';
@@ -106,6 +110,7 @@ export const toolsByCategory: ToolCategory[] = [
textToNatoAlphabet,
textToBinary,
textToUnicode,
+ jsonStringConverter,
yamlToJson,
yamlToToml,
jsonToYaml,
@@ -141,7 +146,13 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Images and videos',
- components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
+ components: [
+ qrCodeGenerator,
+ wifiQrCodeGenerator,
+ svgPlaceholderGenerator,
+ cameraRecorder,
+ imageResizer,
+ ],
},
{
name: 'Development',
@@ -184,11 +195,16 @@ export const toolsByCategory: ToolCategory[] = [
textDiff,
numeronymGenerator,
asciiTextDrawer,
+ aiPromptSplitter,
],
},
{
name: 'Data',
- components: [phoneParserAndFormatter, ibanValidatorAndParser],
+ components: [
+ phoneParserAndFormatter,
+ ibanValidatorAndParser,
+ multiLinkDownloader,
+ ],
},
];
diff --git a/src/tools/json-string-converter/index.ts b/src/tools/json-string-converter/index.ts
new file mode 100644
index 0000000000..ea95a88ee5
--- /dev/null
+++ b/src/tools/json-string-converter/index.ts
@@ -0,0 +1,12 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Json string converter',
+ path: '/json-string-converter',
+ description: '',
+ keywords: ['json', 'string', 'converter'],
+ component: () => import('./json-string-converter.vue'),
+ icon: Braces,
+ createdAt: new Date(),
+});
diff --git a/src/tools/json-string-converter/json-string-converter.vue b/src/tools/json-string-converter/json-string-converter.vue
new file mode 100644
index 0000000000..71da26038a
--- /dev/null
+++ b/src/tools/json-string-converter/json-string-converter.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/json-to-csv/json-to-csv.vue b/src/tools/json-to-csv/json-to-csv.vue
index e2f5ddb67f..0f02bce9cd 100644
--- a/src/tools/json-to-csv/json-to-csv.vue
+++ b/src/tools/json-to-csv/json-to-csv.vue
@@ -4,6 +4,8 @@ import { convertArrayToCsv } from './json-to-csv.service';
import type { UseValidationRule } from '@/composable/validation';
import { withDefaultOnError } from '@/utils/defaults';
+const defaultValue = '[\n {\n "Age": 18,\n "Country": "Germany",\n "Gender": "Male",\n "Purchased": "N",\n "Salary": 20000\n },\n {\n "Age": 19,\n "Country": "France",\n "Gender": "Female",\n "Purchased": "N",\n "Salary": 22000\n },\n {\n "Age": 20,\n "Country": "England",\n "Gender": "Female",\n "Purchased": "N",\n "Salary": 24000\n }\n]';
+
function transformer(value: string) {
return withDefaultOnError(() => {
if (value === '') {
@@ -24,6 +26,7 @@ const rules: UseValidationRule
[] = [
import('./multi-link-downloader.vue'),
+ icon: IconFileDownload,
+ createdAt: new Date(),
+});
diff --git a/src/tools/multi-link-downloader/multi-link-downloader.service.ts b/src/tools/multi-link-downloader/multi-link-downloader.service.ts
new file mode 100644
index 0000000000..d22aa612d5
--- /dev/null
+++ b/src/tools/multi-link-downloader/multi-link-downloader.service.ts
@@ -0,0 +1,108 @@
+import JSZip from 'jszip';
+
+export async function downloadLinks(links: string): Promise {
+ // Split links by newline and filter out empty ones
+ const linksArray: string[] = links.split('\n').filter(link => link.trim() !== '');
+
+ // Helper function to handle duplicate filenames
+ function getUniqueFileName(existingNames: Set, originalName: string): string {
+ let fileName = originalName;
+ let fileExtension = '';
+
+ // Split filename and extension (if any)
+ const lastDotIndex = originalName.lastIndexOf('.');
+ if (lastDotIndex !== -1) {
+ fileName = originalName.substring(0, lastDotIndex);
+ fileExtension = originalName.substring(lastDotIndex);
+ }
+
+ let counter = 1;
+ let uniqueName = originalName;
+
+ // Append a counter to the filename if it already exists in the map
+ while (existingNames.has(uniqueName)) {
+ uniqueName = `${fileName} (${counter})${fileExtension}`;
+ counter++;
+ }
+
+ existingNames.add(uniqueName);
+ return uniqueName;
+ }
+
+ if (linksArray.length === 1) {
+ // Single link: download directly
+ const linkUrl: string = linksArray[0];
+ try {
+ const response: Response = await fetch(linkUrl);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ${linkUrl}`);
+ }
+
+ // Get file as blob
+ const blob: Blob = await response.blob();
+
+ // Extract filename from URL
+ const fileName: string = linkUrl.split('/').pop() || 'downloaded_file';
+
+ // Trigger download
+ const a: HTMLAnchorElement = document.createElement('a');
+ const downloadUrl: string = window.URL.createObjectURL(blob);
+ a.href = downloadUrl;
+ a.download = fileName;
+ document.body.appendChild(a);
+ a.click();
+
+ // Clean up
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(downloadUrl);
+ }
+ catch (error) {
+ console.error('Error downloading the file:', error);
+ }
+ }
+ else if (linksArray.length > 1) {
+ // Multiple links: create a zip file
+ const zip = new JSZip();
+ const fileNamesSet = new Set(); // To track file names for duplicates
+
+ await Promise.all(
+ linksArray.map(async (linkUrl: string) => {
+ try {
+ const response: Response = await fetch(linkUrl);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ${linkUrl}`);
+ }
+ const blob: Blob = await response.blob();
+
+ // Extract filename from URL
+ let fileName: string = linkUrl.split('/').pop() || 'file';
+
+ // Get unique filename if duplicate exists
+ fileName = getUniqueFileName(fileNamesSet, fileName);
+
+ // Add file to the zip
+ zip.file(fileName, blob);
+ }
+ catch (error) {
+ console.error(`Error downloading file from ${linkUrl}:`, error);
+ }
+ }),
+ );
+
+ // Generate the zip file and trigger download
+ zip.generateAsync({ type: 'blob' }).then((zipBlob: Blob) => {
+ const downloadUrl: string = window.URL.createObjectURL(zipBlob);
+
+ // Trigger download of the zip file
+ const a: HTMLAnchorElement = document.createElement('a');
+ a.href = downloadUrl;
+ a.download = 'downloaded_files.zip';
+ document.body.appendChild(a);
+ a.click();
+
+ // Clean up
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(downloadUrl);
+ });
+ }
+}
diff --git a/src/tools/multi-link-downloader/multi-link-downloader.vue b/src/tools/multi-link-downloader/multi-link-downloader.vue
new file mode 100644
index 0000000000..9e1c726843
--- /dev/null
+++ b/src/tools/multi-link-downloader/multi-link-downloader.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ Start Download
+
+
+ Clear
+
+
+
+
+
+
diff --git a/src/ui/c-diff-editor/c-diff-editor.vue b/src/ui/c-diff-editor/c-diff-editor.vue
index 2aa29475ef..84ba883726 100644
--- a/src/ui/c-diff-editor/c-diff-editor.vue
+++ b/src/ui/c-diff-editor/c-diff-editor.vue
@@ -1,14 +1,14 @@