mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-16 04:29:32 +02:00
feat: change gif speed
This commit is contained in:
150
.idea/workspace.xml
generated
150
.idea/workspace.xml
generated
@@ -4,19 +4,25 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: toolOptions">
|
||||
<change afterPath="$PROJECT_DIR$/playwright.config.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/string/join/string-join.e2e.spec.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/test-results/.last-run.json" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/test-results/pages-string-join-string-j-4fb47-th-specified-join-character-webkit-retry1/trace.zip" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: optimize imports">
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/gif/change-speed/change-speed.service.test.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/gif/change-speed/index.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/gif/change-speed/meta.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/gif/change-speed/service.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/gif/index.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/video/index.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/utils/gif.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/join/Info.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolInfo.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ToolLayout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolLayout.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/options/RadioWithTextField.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/RadioWithTextField.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/join/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/join/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/tools/Separator.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Separator.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -53,43 +59,43 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Playwright.JoinText Component.executor": "Run",
|
||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "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",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/HP/IdeaProjects/omni-tools/src/assets",
|
||||
"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.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",
|
||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\HP\\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": "settings.typescriptcompiler",
|
||||
"ts.external.directory.path": "C:\\Users\\HP\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Playwright.JoinText Component.executor": "Run",
|
||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "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",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/HP/IdeaProjects/omni-tools/src/assets",
|
||||
"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.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",
|
||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\HP\\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": "settings.typescriptcompiler",
|
||||
"ts.external.directory.path": "C:\\Users\\HP\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="ReactDesignerToolWindowState">
|
||||
<option name="myId2Visible">
|
||||
<map>
|
||||
@@ -114,7 +120,7 @@
|
||||
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\tools" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="npm.test:e2e">
|
||||
<component name="RunManager" selected="npm.dev">
|
||||
<configuration name="JoinText Component" type="JavaScriptTestRunnerPlaywright" temporary="true" nameIsGenerated="true">
|
||||
<node-interpreter value="project" />
|
||||
<playwright-package value="$PROJECT_DIR$/node_modules/@playwright/test" />
|
||||
@@ -150,11 +156,11 @@
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="test" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
|
||||
<configuration name="lint" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="test" />
|
||||
<script value="lint" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
@@ -172,11 +178,11 @@
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="npm.dev" />
|
||||
<item itemvalue="npm.lint" />
|
||||
<item itemvalue="npm.test:e2e" />
|
||||
<item itemvalue="Playwright.JoinText Component.should merge text pieces with specified join character" />
|
||||
<item itemvalue="npm.dev" />
|
||||
<item itemvalue="Playwright.JoinText Component" />
|
||||
<item itemvalue="npm.test" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
@@ -207,23 +213,9 @@
|
||||
<workItem from="1719339559458" duration="303000" />
|
||||
<workItem from="1719340295244" duration="772000" />
|
||||
<workItem from="1719363272227" duration="390000" />
|
||||
<workItem from="1719379971872" duration="8578000" />
|
||||
</task>
|
||||
<task id="LOCAL-00018" summary="feat: conventional commit">
|
||||
<option name="closed" value="true" />
|
||||
<created>1719007195103</created>
|
||||
<option name="number" value="00018" />
|
||||
<option name="presentableId" value="LOCAL-00018" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719007195103</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00019" summary="test: init">
|
||||
<option name="closed" value="true" />
|
||||
<created>1719023377131</created>
|
||||
<option name="number" value="00019" />
|
||||
<option name="presentableId" value="LOCAL-00019" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719023377131</updated>
|
||||
<workItem from="1719379971872" duration="8943000" />
|
||||
<workItem from="1719464673797" duration="38000" />
|
||||
<workItem from="1719475764139" duration="12528000" />
|
||||
</task>
|
||||
<task id="LOCAL-00020" summary="chore: remove prebuild">
|
||||
<option name="closed" value="true" />
|
||||
@@ -601,7 +593,23 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719384439535</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="67" />
|
||||
<task id="LOCAL-00067" summary="feat: playwright">
|
||||
<option name="closed" value="true" />
|
||||
<created>1719388760134</created>
|
||||
<option name="number" value="00067" />
|
||||
<option name="presentableId" value="LOCAL-00067" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719388760134</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00068" summary="refactor: optimize imports">
|
||||
<option name="closed" value="true" />
|
||||
<created>1719388927238</created>
|
||||
<option name="number" value="00068" />
|
||||
<option name="presentableId" value="LOCAL-00068" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719388927238</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="69" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -622,8 +630,6 @@
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||
<MESSAGE value="fix: build" />
|
||||
<MESSAGE value="chore: CODEOWNERS" />
|
||||
<MESSAGE value="ci: fix" />
|
||||
<MESSAGE value="fix: create-tool.mjs" />
|
||||
<MESSAGE value="feat: change colors in png init" />
|
||||
@@ -647,7 +653,9 @@
|
||||
<MESSAGE value="chore: loading screen" />
|
||||
<MESSAGE value="chore: shuffle tools search" />
|
||||
<MESSAGE value="refactor: toolOptions" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="refactor: toolOptions" />
|
||||
<MESSAGE value="feat: playwright" />
|
||||
<MESSAGE value="refactor: optimize imports" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="refactor: optimize imports" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
92
package-lock.json
generated
92
package-lock.json
generated
@@ -13,13 +13,20 @@
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@playwright/test": "^1.45.0",
|
||||
"@types/gif-encoder": "^0.7.4",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/morsee": "^1.0.2",
|
||||
"@types/omggif": "^1.0.5",
|
||||
"color": "^4.2.3",
|
||||
"formik": "^2.4.6",
|
||||
"gif-encoder": "^0.7.2",
|
||||
"gif-encoder-2": "^1.0.5",
|
||||
"gif-encoder-2-browser": "^1.0.5",
|
||||
"gifuct-js": "^2.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"morsee": "^1.0.9",
|
||||
"notistack": "^3.0.1",
|
||||
"omggif": "^1.0.10",
|
||||
"playwright": "^1.45.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -2511,6 +2518,14 @@
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/gif-encoder": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/gif-encoder/-/gif-encoder-0.7.4.tgz",
|
||||
"integrity": "sha512-tke9j2sFpRpX2C1JLAxZpTMAzVILlWkkhuSGCbxWuyBvXYtA+og7KpxL5ag+4dbDYJxZ2zVmBH0taxwihgz0ZQ==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
@@ -2540,11 +2555,15 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/omggif": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/omggif/-/omggif-1.0.5.tgz",
|
||||
"integrity": "sha512-gDQJflz1rOgEcUXkMAl80bDGN46f5mp8GbcM5dyvq+zsFV6YRBRtmNxlJJ5mjY77T7BRkRFzdIBVmK90QYhCxA=="
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
@@ -3779,6 +3798,11 @@
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
@@ -5116,6 +5140,35 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gif-encoder": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.7.2.tgz",
|
||||
"integrity": "sha512-rEe2DJCb8quqOElV5orqRjyk2KNjz+Hdy+eWYVNWn7s1/33QQ6boIJHkgOto5qU2NrGxI2coPDRqcEaGFZkQ1w==",
|
||||
"dependencies": {
|
||||
"readable-stream": "~1.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gif-encoder-2": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/gif-encoder-2/-/gif-encoder-2-1.0.5.tgz",
|
||||
"integrity": "sha512-fsRAKbZuUoZ7FYGjpFElmflTkKwsn/CzAmL/xDl4558aTAgysIDCUF6AXWO8dmai/ApfZACbPVAM+vPezJXlFg=="
|
||||
},
|
||||
"node_modules/gif-encoder-2-browser": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/gif-encoder-2-browser/-/gif-encoder-2-browser-1.0.5.tgz",
|
||||
"integrity": "sha512-YFIFc3yjgoBFnClWAYFq0FmSSdrk1Q4AFVp+2/CN7mRzh+fDHC6q/jUBzWY9SpGUP8fICzgW1Q2iZtHbdONabA=="
|
||||
},
|
||||
"node_modules/gifuct-js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz",
|
||||
"integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==",
|
||||
"dependencies": {
|
||||
"js-binary-schema-parser": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/git-raw-commits": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz",
|
||||
@@ -5495,8 +5548,7 @@
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "4.1.1",
|
||||
@@ -6006,6 +6058,11 @@
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-binary-schema-parser": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz",
|
||||
"integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -6686,6 +6743,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/omggif": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -7380,6 +7442,22 @@
|
||||
"pify": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -7973,6 +8051,11 @@
|
||||
"duplexer": "~0.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@@ -8649,8 +8732,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",
|
||||
|
@@ -31,13 +31,20 @@
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@playwright/test": "^1.45.0",
|
||||
"@types/gif-encoder": "^0.7.4",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/morsee": "^1.0.2",
|
||||
"@types/omggif": "^1.0.5",
|
||||
"color": "^4.2.3",
|
||||
"formik": "^2.4.6",
|
||||
"gif-encoder": "^0.7.2",
|
||||
"gif-encoder-2": "^1.0.5",
|
||||
"gif-encoder-2-browser": "^1.0.5",
|
||||
"gifuct-js": "^2.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"morsee": "^1.0.9",
|
||||
"notistack": "^3.0.1",
|
||||
"omggif": "^1.0.10",
|
||||
"playwright": "^1.45.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
@@ -14,7 +14,7 @@ const exampleTools: { label: string; url: string }[] = [
|
||||
url: '/png/create-transparent'
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '/string/to-morse' },
|
||||
{ label: 'Change GIF speed', url: '' },
|
||||
{ label: 'Change GIF speed', url: '/gif/change-speed' },
|
||||
{ label: 'Pick a random item', url: '' },
|
||||
{ label: 'Find and replace text', url: '' },
|
||||
{ label: 'Convert emoji to image', url: '' },
|
||||
|
@@ -2,7 +2,7 @@ import { Box } from '@mui/material';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import ToolHeader from './ToolHeader';
|
||||
import Separator from '@tools/Separator';
|
||||
import Separator from './Separator';
|
||||
import AllTools from './allTools/AllTools';
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
|
@@ -34,7 +34,10 @@ const RadioWithTextField = <T,>({
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={value}
|
||||
onChange={onTextChange}
|
||||
onChange={(val) => {
|
||||
if (typeof val === 'string') onTextChange(val);
|
||||
else onTextChange(val.target.value);
|
||||
}}
|
||||
description={description}
|
||||
/>
|
||||
</Box>
|
||||
|
@@ -1,18 +1,20 @@
|
||||
import { Box, TextField } from '@mui/material';
|
||||
import { Box, TextField, TextFieldProps } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
|
||||
type OwnProps = {
|
||||
description: string;
|
||||
value: string | number;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
};
|
||||
const TextFieldWithDesc = ({
|
||||
description,
|
||||
value,
|
||||
onChange,
|
||||
placeholder
|
||||
}: {
|
||||
description: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}) => {
|
||||
placeholder,
|
||||
...props
|
||||
}: TextFieldProps & OwnProps) => {
|
||||
return (
|
||||
<Box>
|
||||
<TextField
|
||||
@@ -20,6 +22,7 @@ const TextFieldWithDesc = ({
|
||||
sx={{ backgroundColor: 'white' }}
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
{...props}
|
||||
/>
|
||||
<Typography fontSize={12} mt={1}>
|
||||
{description}
|
||||
|
@@ -6,6 +6,28 @@ import { Formik, FormikProps, FormikValues, useFormikContext } from 'formik';
|
||||
import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
|
||||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
|
||||
const FormikListenerComponent = <T,>({
|
||||
initialValues,
|
||||
input,
|
||||
compute
|
||||
}: {
|
||||
initialValues: T;
|
||||
input: any;
|
||||
compute: (optionsValues: T, input: any) => void;
|
||||
}) => {
|
||||
const { values } = useFormikContext<typeof initialValues>();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (values && input) compute(values, input);
|
||||
} catch (exception: unknown) {
|
||||
if (exception instanceof Error) showSnackBar(exception.message, 'error');
|
||||
}
|
||||
}, [values, input]);
|
||||
|
||||
return null; // This component doesn't render anything
|
||||
};
|
||||
export default function ToolOptions<T extends FormikValues>({
|
||||
children,
|
||||
initialValues,
|
||||
@@ -24,21 +46,7 @@ export default function ToolOptions<T extends FormikValues>({
|
||||
formRef?: RefObject<FormikProps<T>>;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const FormikListenerComponent = () => {
|
||||
const { values } = useFormikContext<typeof initialValues>();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
compute(values, input);
|
||||
} catch (exception: unknown) {
|
||||
if (exception instanceof Error)
|
||||
showSnackBar(exception.message, 'error');
|
||||
}
|
||||
}, [values, showSnackBar]);
|
||||
|
||||
return null; // This component doesn't render anything
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -62,7 +70,11 @@ export default function ToolOptions<T extends FormikValues>({
|
||||
>
|
||||
{(formikProps) => (
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
<FormikListenerComponent />
|
||||
<FormikListenerComponent
|
||||
compute={compute}
|
||||
input={input}
|
||||
initialValues={initialValues}
|
||||
/>
|
||||
<ToolOptionGroups groups={getGroups(formikProps)} />
|
||||
{children}
|
||||
</Stack>
|
||||
|
@@ -10,7 +10,7 @@ import CheckboxWithDesc from '../../../components/options/CheckboxWithDesc';
|
||||
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
||||
|
||||
import ToolInfo from '../../../components/ToolInfo';
|
||||
import Separator from '../../../tools/Separator';
|
||||
import Separator from '../../../components/Separator';
|
||||
import Examples from '../../../components/examples/Examples';
|
||||
|
||||
const initialValues = {
|
||||
|
@@ -0,0 +1,6 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
// import { } from './service';
|
||||
//
|
||||
// describe('change-speed', () => {
|
||||
//
|
||||
// })
|
150
src/pages/video/gif/change-speed/index.tsx
Normal file
150
src/pages/video/gif/change-speed/index.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import ToolFileInput from '../../../../components/input/ToolFileInput';
|
||||
import ToolFileResult from '../../../../components/result/ToolFileResult';
|
||||
import ToolOptions from '../../../../components/options/ToolOptions';
|
||||
import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
|
||||
import ToolInputAndResult from '../../../../components/ToolInputAndResult';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { FrameOptions, GifReader, GifWriter } from 'omggif';
|
||||
import { gifBinaryToFile } from '../../../../utils/gif';
|
||||
|
||||
const initialValues = {
|
||||
newSpeed: 200
|
||||
};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function ChangeSpeed() {
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
|
||||
const compute = (optionsValues: typeof initialValues, input: File) => {
|
||||
const { newSpeed } = optionsValues;
|
||||
|
||||
const processImage = async (file: File, newSpeed: number) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
reader.onload = async () => {
|
||||
const arrayBuffer = reader.result;
|
||||
|
||||
if (arrayBuffer instanceof ArrayBuffer) {
|
||||
const intArray = new Uint8Array(arrayBuffer);
|
||||
|
||||
const reader = new GifReader(intArray as Buffer);
|
||||
const info = reader.frameInfo(0);
|
||||
const imageDataArr: ImageData[] = new Array(reader.numFrames())
|
||||
.fill(0)
|
||||
.map((_, k) => {
|
||||
const image = new ImageData(info.width, info.height);
|
||||
|
||||
reader.decodeAndBlitFrameRGBA(k, image.data as any);
|
||||
|
||||
return image;
|
||||
});
|
||||
const gif = new GifWriter(
|
||||
[],
|
||||
imageDataArr[0].width,
|
||||
imageDataArr[0].height,
|
||||
{ loop: 20 }
|
||||
);
|
||||
|
||||
// Decode the GIF
|
||||
imageDataArr.forEach((imageData) => {
|
||||
const palette = [];
|
||||
const pixels = new Uint8Array(imageData.width * imageData.height);
|
||||
|
||||
const { data } = imageData;
|
||||
for (let j = 0, k = 0, jl = data.length; j < jl; j += 4, k++) {
|
||||
const r = Math.floor(data[j] * 0.1) * 10;
|
||||
const g = Math.floor(data[j + 1] * 0.1) * 10;
|
||||
const b = Math.floor(data[j + 2] * 0.1) * 10;
|
||||
const color = (r << 16) | (g << 8) | (b << 0);
|
||||
|
||||
const index = palette.indexOf(color);
|
||||
|
||||
if (index === -1) {
|
||||
pixels[k] = palette.length;
|
||||
palette.push(color);
|
||||
} else {
|
||||
pixels[k] = index;
|
||||
}
|
||||
}
|
||||
|
||||
// Force palette to be power of 2
|
||||
|
||||
let powof2 = 1;
|
||||
while (powof2 < palette.length) powof2 <<= 1;
|
||||
palette.length = powof2;
|
||||
|
||||
const delay = newSpeed / 10; // Delay in hundredths of a sec (100 = 1s)
|
||||
const options: FrameOptions = {
|
||||
// @ts-ignore
|
||||
palette: new Uint32Array(palette),
|
||||
delay: delay
|
||||
};
|
||||
gif.addFrame(
|
||||
0,
|
||||
0,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
// @ts-ignore
|
||||
pixels,
|
||||
options
|
||||
);
|
||||
});
|
||||
const newFile = gifBinaryToFile(gif.getOutputBuffer(), file.name);
|
||||
|
||||
setResult(newFile);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
processImage(input, newSpeed);
|
||||
};
|
||||
return (
|
||||
<Box>
|
||||
<ToolInputAndResult
|
||||
input={
|
||||
<ToolFileInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['image/gif']}
|
||||
title={'Input GIF'}
|
||||
/>
|
||||
}
|
||||
result={
|
||||
<ToolFileResult
|
||||
title={'Output GIF with new speed'}
|
||||
value={result}
|
||||
extension={'gif'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToolOptions
|
||||
compute={compute}
|
||||
getGroups={({ values, setFieldValue }) => [
|
||||
{
|
||||
title: 'New GIF speed',
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.newSpeed}
|
||||
onChange={(val) => setFieldValue('newSpeed', val)}
|
||||
description={'Default new GIF speed.'}
|
||||
InputProps={{ endAdornment: <Typography>ms</Typography> }}
|
||||
type={'number'}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
]}
|
||||
initialValues={initialValues}
|
||||
input={input}
|
||||
validationSchema={validationSchema}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
14
src/pages/video/gif/change-speed/meta.ts
Normal file
14
src/pages/video/gif/change-speed/meta.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('gif', {
|
||||
name: 'Change speed',
|
||||
path: 'change-speed',
|
||||
// image,
|
||||
description:
|
||||
'This online utility lets you change the speed of a GIF animation. You can speed it up or slow it down. You can set the same constant delay between all frames or change the delays of individual frames. You can also play both the input and output GIFs at the same time and compare their speeds',
|
||||
shortDescription: '',
|
||||
keywords: ['change', 'speed'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
0
src/pages/video/gif/change-speed/service.ts
Normal file
0
src/pages/video/gif/change-speed/service.ts
Normal file
3
src/pages/video/gif/index.ts
Normal file
3
src/pages/video/gif/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { tool as gifChangeSpeed } from './change-speed/meta';
|
||||
|
||||
export const gifTools = [gifChangeSpeed];
|
3
src/pages/video/index.ts
Normal file
3
src/pages/video/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { gifTools } from './gif';
|
||||
|
||||
export const videoTools = [...gifTools];
|
@@ -3,11 +3,13 @@ import { imageTools } from '../pages/image';
|
||||
import { DefinedTool } from './defineTool';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
import { numberTools } from '../pages/number';
|
||||
import { videoTools } from '../pages/video';
|
||||
|
||||
export const tools: DefinedTool[] = [
|
||||
...imageTools,
|
||||
...stringTools,
|
||||
...numberTools
|
||||
...numberTools,
|
||||
...videoTools
|
||||
];
|
||||
const categoriesDescriptions: { type: string; value: string }[] = [
|
||||
{
|
||||
@@ -24,6 +26,11 @@ const categoriesDescriptions: { type: string; value: string }[] = [
|
||||
type: 'number',
|
||||
value:
|
||||
'Tools for working with numbers – generate number sequences, convert numbers to words and words to numbers, sort, round, factor numbers, and much more.'
|
||||
},
|
||||
{
|
||||
type: 'gif',
|
||||
value:
|
||||
'Tools for working with GIF animations – create transparent GIFs, extract GIF frames, add text to GIF, crop, rotate, reverse GIFs, and much more.'
|
||||
}
|
||||
];
|
||||
export const filterTools = (
|
||||
|
18
src/utils/gif.ts
Normal file
18
src/utils/gif.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GifBinary } from 'omggif';
|
||||
|
||||
export function gifBinaryToFile(
|
||||
gifBinary: GifBinary,
|
||||
fileName: string,
|
||||
mimeType: string = 'image/gif'
|
||||
): File {
|
||||
// Convert GifBinary to Uint8Array
|
||||
const uint8Array = new Uint8Array(gifBinary.length);
|
||||
for (let i = 0; i < gifBinary.length; i++) {
|
||||
uint8Array[i] = gifBinary[i];
|
||||
}
|
||||
|
||||
const blob = new Blob([uint8Array], { type: mimeType });
|
||||
|
||||
// Create File from Blob
|
||||
return new File([blob], fileName, { type: mimeType });
|
||||
}
|
Reference in New Issue
Block a user