diff --git a/components.d.ts b/components.d.ts index 3e65c3cc5..018f157e8 100644 --- a/components.d.ts +++ b/components.d.ts @@ -101,6 +101,7 @@ declare module '@vue/runtime-core' { IconMdiSearch: typeof import('~icons/mdi/search')['default'] IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] + ImageConverter: typeof import('./src/tools/image-converter/image-converter.vue')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] @@ -135,13 +136,16 @@ declare module '@vue/runtime-core' { NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] + NFormItem: typeof import('naive-ui')['NFormItem'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NSpace: typeof import('naive-ui')['NSpace'] + NSpin: typeof import('naive-ui')['NSpin'] NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] diff --git a/package.json b/package.json index 5c991cff6..08c78d6e2 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "image-in-browser": "^3.2.0", "js-base64": "^3.7.6", "json5": "^2.2.3", "jwt-decode": "^3.1.2", @@ -88,7 +89,9 @@ "plausible-tracker": "^0.3.8", "qrcode": "^1.5.1", "randexp": "^0.5.3", + "roboto-base64": "^0.1.2", "sql-formatter": "^13.0.0", + "svg2png-wasm": "^1.4.1", "ua-parser-js": "^1.0.35", "ulid": "^2.3.0", "unicode-emoji-json": "^0.4.0", @@ -99,6 +102,7 @@ "vue-router": "^4.1.6", "vue-shadow-dom": "^4.2.0", "vue-tsc": "^1.8.1", + "webp-converter-browser": "^1.0.4", "vuedraggable": "^4.1.0", "xml-formatter": "^3.3.2", "xml-js": "^1.6.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3798ae171..cdffb0be2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + image-in-browser: + specifier: ^3.2.0 + version: 3.2.0 js-base64: specifier: ^3.7.6 version: 3.7.7 @@ -161,9 +164,15 @@ dependencies: randexp: specifier: ^0.5.3 version: 0.5.3 + roboto-base64: + specifier: ^0.1.2 + version: 0.1.2 sql-formatter: specifier: ^13.0.0 version: 13.0.0 + svg2png-wasm: + specifier: ^1.4.1 + version: 1.4.1 ua-parser-js: specifier: ^1.0.35 version: 1.0.35 @@ -197,6 +206,9 @@ dependencies: vuedraggable: specifier: ^4.1.0 version: 4.1.0(vue@3.3.4) + webp-converter-browser: + specifier: ^1.0.4 + version: 1.0.4 xml-formatter: specifier: ^3.3.2 version: 3.3.2 @@ -2666,9 +2678,11 @@ packages: dependencies: '@tabler/icons': 3.20.0 vue: 3.3.4 + dev: false /@tabler/icons@3.20.0: resolution: {integrity: sha512-nXSeUzsCOxX/Of+kdUVQfxL9bG+ck8XCWNf9dGSpE+nhVexRwk/4HiDQDxFDysfT7vfgSut6GXnrZsU5M5dSlA==} + dev: false /@tiptap/core@2.1.12(@tiptap/pm@2.1.6): resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==} @@ -3412,7 +3426,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 11.0.3(vue@3.3.4) + '@vueuse/shared': 11.1.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -4054,8 +4068,8 @@ packages: - vue dev: false - /@vueuse/shared@11.0.3(vue@3.3.4): - resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} + /@vueuse/shared@11.1.0(vue@3.3.4): + resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==} dependencies: vue-demi: 0.14.10(vue@3.3.4) transitivePeerDependencies: @@ -6188,6 +6202,10 @@ packages: engines: {node: '>= 4'} dev: true + /image-in-browser@3.2.0: + resolution: {integrity: sha512-N2u2IEBbgBwYCZQ150f1G0RY+eDQ5AoT43BYEbg6nnjtiLqb5LZTj64t0R6AEicRNKOm80rGuleZcWOFNumR8g==} + dev: false + /image-size@0.5.5: resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} @@ -7997,6 +8015,10 @@ packages: glob: 7.2.3 dev: true + /roboto-base64@0.1.2: + resolution: {integrity: sha512-vXOGVIresupkks/WVNy4upNA4OorZx6XbHJt5ZQaat1tHBdJxui3oI101n9XV9Yg6HWIQKeAGnQbJJaUa8mfvA==} + dev: false + /rollup-plugin-terser@7.0.2(rollup@2.79.1): resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser @@ -8236,6 +8258,7 @@ packages: /sortablejs@1.14.0: resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + dev: false /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} @@ -8451,6 +8474,10 @@ packages: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} dev: true + /svg2png-wasm@1.4.1: + resolution: {integrity: sha512-ZFy1NtwZVAsslaTQoI+/QqX2sg0vjmgJ/jGAuLZZvYcRlndI54hLPiwLC9JzXlFBerfxN5JiS7kpEUG0mrXS3Q==} + dev: false + /svgo@3.0.2: resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} engines: {node: '>=14.0.0'} @@ -9369,6 +9396,7 @@ packages: dependencies: sortablejs: 1.14.0 vue: 3.3.4 + dev: false /vueuc@0.4.51(vue@3.3.4): resolution: {integrity: sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==} @@ -9416,6 +9444,12 @@ packages: engines: {node: '>=12'} dev: true + /webp-converter-browser@1.0.4: + resolution: {integrity: sha512-ZkcrrM4TyxBI7mPmE4uG6PU2MF5YXhKTtvub/ll/4ZjXM7THcms0C0tA10CwnsrLzRBC+ry/sUzwpW6vdPzA8g==} + dependencies: + '@babel/runtime': 7.23.2 + dev: false + /webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} diff --git a/public/svg2png_wasm_bg.wasm b/public/svg2png_wasm_bg.wasm new file mode 100644 index 000000000..1bef3f1f6 Binary files /dev/null and b/public/svg2png_wasm_bg.wasm differ diff --git a/src/composable/downloadBase64.ts b/src/composable/downloadBase64.ts index 3bc202269..773541e25 100644 --- a/src/composable/downloadBase64.ts +++ b/src/composable/downloadBase64.ts @@ -1,6 +1,7 @@ import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; -import type { Ref } from 'vue'; +import type { MaybeRef, Ref } from 'vue'; import _ from 'lodash'; +import { get } from '@vueuse/core'; export { getMimeTypeFromBase64, @@ -75,21 +76,11 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: } function useDownloadFileFromBase64( - { source, filename, extension, fileMimeType }: - { source: Ref; filename?: string; extension?: string; fileMimeType?: string }) { - return { - download() { - downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType }); - }, - }; -} - -function useDownloadFileFromBase64Refs( { source, filename, extension }: - { source: Ref; filename?: Ref; extension?: Ref }) { + { source: MaybeRef; filename?: MaybeRef; extension?: MaybeRef }) { return { download() { - downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); + downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) }); }, }; } @@ -116,3 +107,13 @@ function previewImageFromBase64(base64String: string): HTMLImageElement { return img; } + +function useDownloadFileFromBase64Refs( + { source, filename, extension }: + { source: Ref; filename?: Ref; extension?: Ref }) { + return { + download() { + downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); + }, + }; +} diff --git a/src/tools/image-converter/image-converter.vue b/src/tools/image-converter/image-converter.vue new file mode 100644 index 000000000..4e4a9fb7b --- /dev/null +++ b/src/tools/image-converter/image-converter.vue @@ -0,0 +1,170 @@ + + + diff --git a/src/tools/image-converter/index.ts b/src/tools/image-converter/index.ts new file mode 100644 index 000000000..40a7aa0aa --- /dev/null +++ b/src/tools/image-converter/index.ts @@ -0,0 +1,12 @@ +import { PictureInPicture } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Image Formats Converter', + path: '/image-converter', + description: 'Convert images from one format to another', + keywords: ['image', 'bmp', 'gif', 'ico', 'jpg', 'png', 'tga', 'pvr', 'tiff', 'pnm', 'pbm', 'pgm', 'ppm', 'psd', 'webp', 'converter'], + component: () => import('./image-converter.vue'), + icon: PictureInPicture, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/image-converter/roboto-base64.d.ts b/src/tools/image-converter/roboto-base64.d.ts new file mode 100644 index 000000000..248d11b31 --- /dev/null +++ b/src/tools/image-converter/roboto-base64.d.ts @@ -0,0 +1,3 @@ +declare module 'roboto-base64' { + export const normal: string; +} \ No newline at end of file diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf49..c3ebd29c6 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,7 @@ 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 emailNormalizer } from './email-normalizer'; +import { tool as imageConverter } from './image-converter'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -141,7 +142,13 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Images and videos', - components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], + components: [ + qrCodeGenerator, + wifiQrCodeGenerator, + svgPlaceholderGenerator, + cameraRecorder, + imageConverter, + ], }, { name: 'Development',