From fadd503133e56f7e35cc3dae9784438870366a93 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Wed, 26 Mar 2025 01:32:09 +0000 Subject: [PATCH 1/6] feat: convert-hours-to-days --- .../convert-hours-to-days.service.test.ts | 40 ++++++ .../time/convert-hours-to-days/index.tsx | 136 ++++++++++++++++++ .../tools/time/convert-hours-to-days/meta.ts | 15 ++ .../time/convert-hours-to-days/service.ts | 33 +++++ src/pages/tools/time/index.ts | 3 +- 5 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/pages/tools/time/convert-hours-to-days/convert-hours-to-days.service.test.ts create mode 100644 src/pages/tools/time/convert-hours-to-days/index.tsx create mode 100644 src/pages/tools/time/convert-hours-to-days/meta.ts create mode 100644 src/pages/tools/time/convert-hours-to-days/service.ts diff --git a/src/pages/tools/time/convert-hours-to-days/convert-hours-to-days.service.test.ts b/src/pages/tools/time/convert-hours-to-days/convert-hours-to-days.service.test.ts new file mode 100644 index 0000000..d830723 --- /dev/null +++ b/src/pages/tools/time/convert-hours-to-days/convert-hours-to-days.service.test.ts @@ -0,0 +1,40 @@ +import { expect, describe, it } from 'vitest'; +import { convertHoursToDays } from './service'; + +describe('convertHoursToDays', () => { + it('should convert hours to days with default accuracy', () => { + const input = '48'; + const result = convertHoursToDays(input, '1', false); + expect(result).toBe('2'); + }); + + it('should convert hours to days with specified accuracy', () => { + const input = '50'; + const result = convertHoursToDays(input, '2', false); + expect(result).toBe('2.08'); + }); + + it('should append "days" postfix when daysFlag is true', () => { + const input = '72'; + const result = convertHoursToDays(input, '1', true); + expect(result).toBe('3 days'); + }); + + it('should handle multiple lines of input', () => { + const input = '24\n48\n72'; + const result = convertHoursToDays(input, '1', true); + expect(result).toBe('1 days\n2 days\n3 days'); + }); + + it('should handle invalid input gracefully', () => { + const input = 'abc'; + const result = convertHoursToDays(input, '1', false); + expect(result).toBe(''); + }); + + it('should handle empty input', () => { + const input = ''; + const result = convertHoursToDays(input, '1', false); + expect(result).toBe(''); + }); +}); diff --git a/src/pages/tools/time/convert-hours-to-days/index.tsx b/src/pages/tools/time/convert-hours-to-days/index.tsx new file mode 100644 index 0000000..1b6d3c1 --- /dev/null +++ b/src/pages/tools/time/convert-hours-to-days/index.tsx @@ -0,0 +1,136 @@ +import { Box } from '@mui/material'; +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import { convertHoursToDays } from './service'; + +const initialValues = { + daysFlag: false, + accuracy: '1' +}; +type InitialValuesType = typeof initialValues; +const exampleCards: CardExampleType[] = [ + { + title: 'Hours to Integer Days', + description: + 'In this example, we convert ten hour values to ten day values. Each input hour is divisible by 24 without a remainder, so all converted output values are full days. To better communicate the time units, we use the word "hours" in the input data and also add the word "days" to the output data.', + sampleText: `24 hours +48 hours +72 hours +96 hours +120 hours +144 hours +168 hours +192 hours +216 hours +240 hours`, + sampleResult: `1 day +2 days +3 days +4 days +5 days +6 days +7 days +8 days +9 days +10 days`, + sampleOptions: { daysFlag: true, accuracy: '2' } + }, + { + title: 'Decimal Days', + description: + 'In this example, we convert five decimal fraction day values to hours. Conversion of partial days is similar to the conversion of full days – they are all multiplied by 24. We turn off the option that appends the "hours" string after the converted values and get only the numerical hour values in the output.', + sampleText: `1 hr +100 hr +9999 hr +12345 hr +333333 hr`, + sampleResult: `0.0417 days +4.1667 days +416.625 days +514.375 days +13888.875 days`, + sampleOptions: { daysFlag: true, accuracy: '4' } + }, + { + title: 'Partial Hours', + description: + 'In the modern Gregorian calendar, a common year has 365 days and a leap year has 366 days. This makes the true average length of a year to be 365.242199 days. In this example, we load this number in the input field and convert it to the hours. It turns out that there 8765.812776 hours in an average year.', + sampleText: `0.5 +0.01 +0.99`, + sampleResult: `0.02083333 +0.00041667 +0.04125`, + sampleOptions: { daysFlag: false, accuracy: '8' } + } +]; + +export default function ConvertDaysToHours({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (optionsValues: typeof initialValues, input: string) => { + setResult( + convertHoursToDays(input, optionsValues.accuracy, optionsValues.daysFlag) + ); + }; + + const getGroups: GetGroupsType | null = ({ + values, + updateField + }) => [ + { + title: 'Day Value Accuracy', + component: ( + + updateField('accuracy', val)} + type={'text'} + /> + + ) + }, + { + title: 'Days Postfix', + component: ( + + updateField('daysFlag', val)} + checked={values.daysFlag} + title={'Append Days Postfix'} + description={'Display numeric day values with the postfix "days".'} + /> + + ) + } + ]; + + return ( + } + resultComponent={} + initialValues={initialValues} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + exampleCards={exampleCards} + /> + ); +} diff --git a/src/pages/tools/time/convert-hours-to-days/meta.ts b/src/pages/tools/time/convert-hours-to-days/meta.ts new file mode 100644 index 0000000..28ed2cc --- /dev/null +++ b/src/pages/tools/time/convert-hours-to-days/meta.ts @@ -0,0 +1,15 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + path: 'convert-hours-to-days', + name: 'Convert Hours to Days', + icon: 'mdi:hours-24', + description: + 'With this browser-based application, you can calculate how many days there are in the given number of hours. Given one or more hour values in the input, it converts them into days via the simple math formula: days = hours/24. It works with arbitrary large hour values and you can also customize the decimal day precision.', + shortDescription: 'Convert hours to days easily.', + keywords: ['convert', 'hours', 'days'], + longDescription: + "This is a quick online utility for converting hours to days. To figure out the number of days in the specified hours, the program divides them by 24. For example, if the input hours value is 48, then by doing 48/24, it finds that there are 2 days, or if the hours value is 120, then it's 120/24 = 5 days. If the hours value is not divisible by 24, then the number of days is displayed as a decimal number. For example, 36 hours is 36/24 = 1.5 days and 100 hours is approximately 4.167 days. You can specify the precision of the decimal fraction calculation in the options. You can also enable the option that adds the postfix 'days' to all the output values. Timeabulous!", + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/convert-hours-to-days/service.ts b/src/pages/tools/time/convert-hours-to-days/service.ts new file mode 100644 index 0000000..7448672 --- /dev/null +++ b/src/pages/tools/time/convert-hours-to-days/service.ts @@ -0,0 +1,33 @@ +import { containsOnlyDigits } from '@utils/string'; + +function compute(input: string, accuracy: number) { + if (!containsOnlyDigits(input)) { + return ''; + } + const hours = parseFloat(input); + const days = (hours / 24).toFixed(accuracy); + return parseFloat(days); +} + +export function convertHoursToDays( + input: string, + accuracy: string, + daysFlag: boolean +): string { + if (!containsOnlyDigits(accuracy)) { + throw new Error('Accuracy contains non digits.'); + } + + const result: string[] = []; + + const lines = input.split('\n'); + + lines.forEach((line) => { + const parts = line.split(' '); + const hours = parts[0]; // Extract the number before the space + const days = compute(hours, Number(accuracy)); + result.push(daysFlag ? `${days} days` : `${days}`); + }); + + return result.join('\n'); +} diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index 5e43459..f7e7f9f 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -1,3 +1,4 @@ import { tool as daysDoHours } from './convert-days-to-hours/meta'; +import { tool as hoursToDays } from './convert-hours-to-days/meta'; -export const timeTools = [daysDoHours]; +export const timeTools = [daysDoHours, hoursToDays]; From b762cc86dac37ab017e79d6675120ed4eeb09188 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Wed, 26 Mar 2025 03:48:45 +0000 Subject: [PATCH 2/6] refactor: file inputs --- .idea/workspace.xml | 19 ++- src/components/input/BaseFileInput.tsx | 147 ++++++++++++++++++ src/components/input/ToolImageInput.tsx | 142 +++++++++++++++++ src/components/input/ToolVideoInput.tsx | 121 ++++++++++++++ src/components/input/file-input-utils.ts | 22 +++ .../image/png/change-colors-in-png/index.tsx | 7 +- .../tools/image/png/change-opacity/index.tsx | 4 +- .../tools/image/png/compress-png/index.tsx | 4 +- .../image/png/convert-jgp-to-png/index.tsx | 4 +- .../image/png/create-transparent/index.tsx | 4 +- src/pages/tools/image/png/crop/index.tsx | 4 +- src/pages/tools/video/trim/index.tsx | 13 +- 12 files changed, 465 insertions(+), 26 deletions(-) create mode 100644 src/components/input/BaseFileInput.tsx create mode 100644 src/components/input/ToolImageInput.tsx create mode 100644 src/components/input/ToolVideoInput.tsx create mode 100644 src/components/input/file-input-utils.ts diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e15ea9d..571d6ae 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,11 +5,18 @@ + + + + - - - - + + + + + + + - - - - + + - - - - - - - + + + @@ -790,7 +784,6 @@ diff --git a/package-lock.json b/package-lock.json index 4d80209..95e7f37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", + "@imgly/background-removal": "^1.6.0", "@jimp/types": "^1.6.0", "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", @@ -1528,6 +1529,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@imgly/background-removal": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@imgly/background-removal/-/background-removal-1.6.0.tgz", + "integrity": "sha512-nmqOBDE9dQpDEJg73XrKNUoWugyyDHEVh+U1akjYdUW85ILh9UilvKu/kdv1MI822rKExwgLNuVLVulzAzgZJg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "lodash-es": "^4.17.21", + "ndarray": "~1.0.0", + "zod": "^3.23.8" + }, + "peerDependencies": { + "onnxruntime-web": "1.21.0-dev.20250206-d981b153d3" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2362,6 +2377,80 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -3022,7 +3111,6 @@ "version": "20.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", "integrity": "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -5591,6 +5679,13 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatbuffers": { + "version": "25.2.10", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz", + "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -6033,6 +6128,13 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC", + "peer": true + }, "node_modules/happy-dom": { "version": "12.10.3", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-12.10.3.tgz", @@ -6300,6 +6402,12 @@ "node": ">= 0.4" } }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "license": "MIT" + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -6392,6 +6500,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7389,6 +7503,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7652,6 +7773,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/ndarray": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", + "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "license": "MIT", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7912,6 +8043,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnxruntime-common": { + "version": "1.21.0-dev.20250206-d981b153d3", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0-dev.20250206-d981b153d3.tgz", + "integrity": "sha512-TwaE51xV9q2y8pM61q73rbywJnusw9ivTEHAJ39GVWNZqxCoDBpe/tQkh/w9S+o/g+zS7YeeL0I/2mEWd+dgyA==", + "license": "MIT", + "peer": true + }, + "node_modules/onnxruntime-web": { + "version": "1.21.0-dev.20250206-d981b153d3", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.21.0-dev.20250206-d981b153d3.tgz", + "integrity": "sha512-esDVQdRic6J44VBMFLumYvcGfioMh80ceLmzF1yheJyuLKq/Th8VT2aj42XWQst+2bcWnAhw4IKmRQaqzU8ugg==", + "license": "MIT", + "peer": true, + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.21.0-dev.20250206-d981b153d3", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8196,6 +8349,13 @@ "pathe": "^1.1.2" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT", + "peer": true + }, "node_modules/playwright": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", @@ -8511,6 +8671,31 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -10180,8 +10365,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicorn-magic": { "version": "0.1.0", diff --git a/package.json b/package.json index b69da3d..2bd110c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", + "@imgly/background-removal": "^1.6.0", "@jimp/types": "^1.6.0", "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", diff --git a/src/pages/tools/image/png/index.ts b/src/pages/tools/image/png/index.ts index c5a6a34..f248619 100644 --- a/src/pages/tools/image/png/index.ts +++ b/src/pages/tools/image/png/index.ts @@ -4,6 +4,7 @@ import { tool as convertJgpToPng } from './convert-jgp-to-png/meta'; import { tool as pngCreateTransparent } from './create-transparent/meta'; import { tool as changeColorsInPng } from './change-colors-in-png/meta'; import { tool as changeOpacity } from './change-opacity/meta'; +import { tool as removeBackground } from './remove-background/meta'; export const pngTools = [ pngCompressPng, @@ -11,5 +12,6 @@ export const pngTools = [ changeColorsInPng, convertJgpToPng, changeOpacity, - pngCrop + pngCrop, + removeBackground ]; diff --git a/src/pages/tools/image/png/remove-background/index.tsx b/src/pages/tools/image/png/remove-background/index.tsx new file mode 100644 index 0000000..7825092 --- /dev/null +++ b/src/pages/tools/image/png/remove-background/index.tsx @@ -0,0 +1,104 @@ +import { Box, CircularProgress, Typography } from '@mui/material'; +import React, { useState } from 'react'; +import * as Yup from 'yup'; +import ToolFileResult from '@components/result/ToolFileResult'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; +import ToolImageInput from '@components/input/ToolImageInput'; +import { removeBackground } from '@imgly/background-removal'; + +const initialValues = {}; + +const validationSchema = Yup.object({}); + +export default function RemoveBackgroundFromPng({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [result, setResult] = useState(null); + const [isProcessing, setIsProcessing] = useState(false); + + const compute = async (_optionsValues: typeof initialValues, input: any) => { + if (!input) return; + + setIsProcessing(true); + + try { + // Convert the input file to a Blob URL + const inputUrl = URL.createObjectURL(input); + + // Process the image with the background removal library + const blob = await removeBackground(inputUrl, { + progress: (progress) => { + console.log(`Background removal progress: ${progress}`); + } + }); + + // Create a new file from the blob + const newFile = new File( + [blob], + input.name.replace(/\.[^/.]+$/, '') + '-no-bg.png', + { + type: 'image/png' + } + ); + + setResult(newFile); + } catch (err) { + console.error('Error removing background:', err); + throw new Error( + 'Failed to remove background. Please try a different image or try again later.' + ); + } finally { + setIsProcessing(false); + } + }; + + return ( + + } + resultComponent={ + <> + {isProcessing ? ( + + + + Removing background... This may take a moment. + + + ) : ( + + )} + + } + toolInfo={{ + title: 'Remove Background from PNG', + description: + 'This tool uses AI to automatically remove the background from your images, creating a transparent PNG. Perfect for product photos, profile pictures, and design assets.' + }} + /> + ); +} diff --git a/src/pages/tools/image/png/remove-background/meta.ts b/src/pages/tools/image/png/remove-background/meta.ts new file mode 100644 index 0000000..1ae19e7 --- /dev/null +++ b/src/pages/tools/image/png/remove-background/meta.ts @@ -0,0 +1,13 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('png', { + name: 'Remove Background from PNG', + path: 'remove-background', + icon: 'mdi:image-remove', + description: + "World's simplest online tool to remove backgrounds from PNG images. Just upload your image and our AI-powered tool will automatically remove the background, giving you a transparent PNG. Perfect for product photos, profile pictures, and design assets.", + shortDescription: 'Automatically remove backgrounds from images', + keywords: ['remove', 'background', 'png', 'transparent', 'image', 'ai'], + component: lazy(() => import('./index')) +}); From c0297a187d11cc06156ae67f07168a5e82c3f72a Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Wed, 26 Mar 2025 04:14:19 +0000 Subject: [PATCH 4/6] refactor: background removal --- .idea/workspace.xml | 31 +++---- src/components/result/ToolFileResult.tsx | 86 ++++++++++++------- .../image/png/remove-background/index.tsx | 31 ++----- 3 files changed, 75 insertions(+), 73 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index c2b1f69..8e88558 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,13 +4,10 @@ - - - + - - - + + @@ -784,7 +781,6 @@ diff --git a/src/components/result/ToolFileResult.tsx b/src/components/result/ToolFileResult.tsx index d39afeb..140cce3 100644 --- a/src/components/result/ToolFileResult.tsx +++ b/src/components/result/ToolFileResult.tsx @@ -1,4 +1,4 @@ -import { Box } from '@mui/material'; +import { Box, CircularProgress, Typography } from '@mui/material'; import React, { useContext } from 'react'; import InputHeader from '../InputHeader'; import greyPattern from '@assets/grey-pattern.png'; @@ -9,11 +9,15 @@ import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext'; export default function ToolFileResult({ title = 'Result', value, - extension + extension, + loading, + loadingText }: { title?: string; value: File | null; extension: string; + loading?: boolean; + loadingText?: string; }) { const [preview, setPreview] = React.useState(null); const { showSnackBar } = useContext(CustomSnackBarContext); @@ -83,44 +87,62 @@ export default function ToolFileResult({ bgcolor: 'white' }} > - {preview && ( + {loading ? ( - {fileType === 'image' && ( - Result - )} - {fileType === 'video' && ( - + ) : ( + preview && ( + + {fileType === 'image' && ( + Result + )} + {fileType === 'video' && ( + + ) )} } resultComponent={ - <> - {isProcessing ? ( - - - - Removing background... This may take a moment. - - - ) : ( - - )} - + } toolInfo={{ title: 'Remove Background from PNG', From e6f54a3f2b863b219c9d4705baa09b1a06445189 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Wed, 26 Mar 2025 05:43:59 +0000 Subject: [PATCH 5/6] feat: split pdf --- .idea/workspace.xml | 106 ++++++----- package-lock.json | 37 ++++ package.json | 1 + src/components/ToolContent.tsx | 32 ++-- src/pages/tools/pdf/index.ts | 4 + src/pages/tools/pdf/split-pdf/index.tsx | 180 ++++++++++++++++++ src/pages/tools/pdf/split-pdf/meta.ts | 13 ++ src/pages/tools/pdf/split-pdf/service.test.ts | 43 +++++ src/pages/tools/pdf/split-pdf/service.ts | 66 +++++++ src/tools/defineTool.tsx | 3 +- src/tools/index.ts | 10 +- 11 files changed, 427 insertions(+), 68 deletions(-) create mode 100644 src/pages/tools/pdf/index.ts create mode 100644 src/pages/tools/pdf/split-pdf/index.tsx create mode 100644 src/pages/tools/pdf/split-pdf/meta.ts create mode 100644 src/pages/tools/pdf/split-pdf/service.test.ts create mode 100644 src/pages/tools/pdf/split-pdf/service.ts diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8e88558..279b0b4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,9 +5,17 @@ + + + + + - - + + + + + - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", + "Docker.Dockerfile build.executor": "Run", + "Docker.Dockerfile.executor": "Run", + "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", + "Playwright.JoinText Component.executor": "Run", + "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "Vitest.compute function (1).executor": "Run", + "Vitest.compute function.executor": "Run", + "Vitest.mergeText.executor": "Run", + "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", + "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", + "Vitest.removeDuplicateLines function.executor": "Run", + "Vitest.removeDuplicateLines function.newlines option.executor": "Run", + "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", + "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", + "Vitest.replaceText function.executor": "Run", + "git-widget-placeholder": "main", + "ignore.virus.scanning.warn.message": "true", + "kotlin-language-version-configured": "true", + "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/pages/tools/list/duplicate/index.tsx", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "npm.build.executor": "Run", + "npm.dev.executor": "Run", + "npm.lint.executor": "Run", + "npm.prebuild.executor": "Run", + "npm.script:create:tool.executor": "Run", + "npm.test.executor": "Run", + "npm.test:e2e.executor": "Run", + "npm.test:e2e:run.executor": "Run", + "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", + "project.structure.last.edited": "Problems", + "project.structure.proportion": "0.0", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "refactai_advanced_settings", + "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", + "vue.rearranger.settings.migration": "true" } -}]]> +} - - - - - - + + - - - - - + + + + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", - "Docker.Dockerfile build.executor": "Run", - "Docker.Dockerfile.executor": "Run", - "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", - "Playwright.JoinText Component.executor": "Run", - "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Vitest.compute function (1).executor": "Run", - "Vitest.compute function.executor": "Run", - "Vitest.mergeText.executor": "Run", - "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", - "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", - "Vitest.removeDuplicateLines function.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", - "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", - "Vitest.replaceText function.executor": "Run", - "git-widget-placeholder": "main", - "ignore.virus.scanning.warn.message": "true", - "kotlin-language-version-configured": "true", - "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/pages/tools/list/duplicate/index.tsx", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "npm.build.executor": "Run", - "npm.dev.executor": "Run", - "npm.lint.executor": "Run", - "npm.prebuild.executor": "Run", - "npm.script:create:tool.executor": "Run", - "npm.test.executor": "Run", - "npm.test:e2e.executor": "Run", - "npm.test:e2e:run.executor": "Run", - "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", - "project.structure.last.edited": "Problems", - "project.structure.proportion": "0.0", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "refactai_advanced_settings", - "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", - "vue.rearranger.settings.migration": "true" + +}]]> + - @@ -188,7 +184,7 @@ - + @@ -202,6 +198,19 @@ + + + + + + + + + + + + + @@ -232,16 +241,6 @@ - - - - -