mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-11-15 17:44:02 +01:00
232
.idea/workspace.xml
generated
232
.idea/workspace.xml
generated
@@ -4,23 +4,11 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: svg change colors">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: path">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/index.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-opacity/index.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-opacity/meta.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-opacity/service.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/create-transparent.e2e.spec.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/create-transparent.e2e.spec.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/meta.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/test.png" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/test.png" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/crop/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/crop/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/crop/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/crop/meta.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/remove-background/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/remove-background/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/remove-background/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/remove-background/meta.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -37,7 +25,7 @@
|
|||||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="image-resize" />
|
<entry key="$PROJECT_DIR$" value="chesterkxng" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -134,6 +122,13 @@
|
|||||||
"number": 76
|
"number": 76
|
||||||
},
|
},
|
||||||
"lastSeen": 1743352150953
|
"lastSeen": 1743352150953
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": {
|
||||||
|
"id": "PR_kwDOMJIfts6Q0JBe",
|
||||||
|
"number": 82
|
||||||
|
},
|
||||||
|
"lastSeen": 1743470267269
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}</component>
|
}</component>
|
||||||
@@ -165,56 +160,56 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||||
"Docker.Dockerfile build.executor": "Run",
|
"Docker.Dockerfile build.executor": "Run",
|
||||||
"Docker.Dockerfile.executor": "Run",
|
"Docker.Dockerfile.executor": "Run",
|
||||||
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
||||||
"Playwright.JoinText Component.executor": "Run",
|
"Playwright.JoinText Component.executor": "Run",
|
||||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"Vitest.compute function (1).executor": "Run",
|
"Vitest.compute function (1).executor": "Run",
|
||||||
"Vitest.compute function.executor": "Run",
|
"Vitest.compute function.executor": "Run",
|
||||||
"Vitest.mergeText.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 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.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
|
||||||
"Vitest.parsePageRanges.executor": "Run",
|
"Vitest.parsePageRanges.executor": "Run",
|
||||||
"Vitest.removeDuplicateLines function.executor": "Run",
|
"Vitest.removeDuplicateLines function.executor": "Run",
|
||||||
"Vitest.removeDuplicateLines function.newlines option.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.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 (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||||
"Vitest.replaceText function.executor": "Run",
|
"Vitest.replaceText function.executor": "Run",
|
||||||
"Vitest.timeBetweenDates.executor": "Run",
|
"Vitest.timeBetweenDates.executor": "Run",
|
||||||
"git-widget-placeholder": "main",
|
"git-widget-placeholder": "compression",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"kotlin-language-version-configured": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types",
|
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"npm.build.executor": "Run",
|
"npm.build.executor": "Run",
|
||||||
"npm.dev.executor": "Run",
|
"npm.dev.executor": "Run",
|
||||||
"npm.lint.executor": "Run",
|
"npm.lint.executor": "Run",
|
||||||
"npm.prebuild.executor": "Run",
|
"npm.prebuild.executor": "Run",
|
||||||
"npm.script:create:tool.executor": "Run",
|
"npm.script:create:tool.executor": "Run",
|
||||||
"npm.test.executor": "Run",
|
"npm.test.executor": "Run",
|
||||||
"npm.test:e2e.executor": "Run",
|
"npm.test:e2e.executor": "Run",
|
||||||
"npm.test:e2e:run.executor": "Run",
|
"npm.test:e2e:run.executor": "Run",
|
||||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
||||||
"project.structure.last.edited": "Problems",
|
"project.structure.last.edited": "Problems",
|
||||||
"project.structure.proportion": "0.0",
|
"project.structure.proportion": "0.0",
|
||||||
"project.structure.side.proportion": "0.2",
|
"project.structure.side.proportion": "0.2",
|
||||||
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
||||||
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="ReactDesignerToolWindowState">
|
<component name="ReactDesignerToolWindowState">
|
||||||
<option name="myId2Visible">
|
<option name="myId2Visible">
|
||||||
<map>
|
<map>
|
||||||
@@ -226,14 +221,14 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.husky" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.husky" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" />
|
|
||||||
</key>
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\image\generic" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\lib\ghostscript" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
|
||||||
@@ -411,19 +406,8 @@
|
|||||||
<workItem from="1743047367993" duration="986000" />
|
<workItem from="1743047367993" duration="986000" />
|
||||||
<workItem from="1743103182313" duration="4264000" />
|
<workItem from="1743103182313" duration="4264000" />
|
||||||
<workItem from="1743348610793" duration="21855000" />
|
<workItem from="1743348610793" duration="21855000" />
|
||||||
<workItem from="1743556259185" duration="7150000" />
|
<workItem from="1743397561176" duration="25000" />
|
||||||
<workItem from="1743569964813" duration="352000" />
|
<workItem from="1743458576265" duration="13083000" />
|
||||||
<workItem from="1743570403937" duration="109000" />
|
|
||||||
<workItem from="1743607343305" duration="20000" />
|
|
||||||
<workItem from="1743619618671" duration="7027000" />
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00136" summary="chore: img">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740680778110</created>
|
|
||||||
<option name="number" value="00136" />
|
|
||||||
<option name="presentableId" value="LOCAL-00136" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740680778110</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00137" summary="docs: readme">
|
<task id="LOCAL-00137" summary="docs: readme">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -737,79 +721,87 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743355166426</updated>
|
<updated>1743355166426</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00176" summary="chore: tool description">
|
<task id="LOCAL-00176" summary="fix: gif speed">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743560690570</created>
|
<created>1743385388051</created>
|
||||||
<option name="number" value="00176" />
|
<option name="number" value="00176" />
|
||||||
<option name="presentableId" value="LOCAL-00176" />
|
<option name="presentableId" value="LOCAL-00176" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743560690571</updated>
|
<updated>1743385388051</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00177" summary="feat: image resize init">
|
<task id="LOCAL-00177" summary="fix: tsc">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743565606951</created>
|
<created>1743385467178</created>
|
||||||
<option name="number" value="00177" />
|
<option name="number" value="00177" />
|
||||||
<option name="presentableId" value="LOCAL-00177" />
|
<option name="presentableId" value="LOCAL-00177" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743565606951</updated>
|
<updated>1743385467178</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00178" summary="feat: svg resize">
|
<task id="LOCAL-00178" summary="fix: background color">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743566704552</created>
|
<created>1743385898871</created>
|
||||||
<option name="number" value="00178" />
|
<option name="number" value="00178" />
|
||||||
<option name="presentableId" value="LOCAL-00178" />
|
<option name="presentableId" value="LOCAL-00178" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743566704552</updated>
|
<updated>1743385898871</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00179" summary="feat: gif resize">
|
<task id="LOCAL-00179" summary="docs: github trendings">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743567906528</created>
|
<created>1743459110471</created>
|
||||||
<option name="number" value="00179" />
|
<option name="number" value="00179" />
|
||||||
<option name="presentableId" value="LOCAL-00179" />
|
<option name="presentableId" value="LOCAL-00179" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743567906528</updated>
|
<updated>1743459110471</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00180" summary="chore: add color">
|
<task id="LOCAL-00180" summary="docs: optimize">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743569026879</created>
|
<created>1743459205311</created>
|
||||||
<option name="number" value="00180" />
|
<option name="number" value="00180" />
|
||||||
<option name="presentableId" value="LOCAL-00180" />
|
<option name="presentableId" value="LOCAL-00180" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743569026879</updated>
|
<updated>1743459205311</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00181" summary="docs: readme">
|
<task id="LOCAL-00181" summary="fix: stars button width for 1k+ 😊">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743569711302</created>
|
<created>1743470832619</created>
|
||||||
<option name="number" value="00181" />
|
<option name="number" value="00181" />
|
||||||
<option name="presentableId" value="LOCAL-00181" />
|
<option name="presentableId" value="LOCAL-00181" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743569711302</updated>
|
<updated>1743470832619</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00182" summary="feat: compress image">
|
<task id="LOCAL-00182" summary="feat: compress pdf">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743621506634</created>
|
<created>1743644598841</created>
|
||||||
<option name="number" value="00182" />
|
<option name="number" value="00182" />
|
||||||
<option name="presentableId" value="LOCAL-00182" />
|
<option name="presentableId" value="LOCAL-00182" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743621506634</updated>
|
<updated>1743644598841</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00183" summary="chore: remove labels">
|
<task id="LOCAL-00183" summary="refactor: compress pdf">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743621602590</created>
|
<created>1743644703041</created>
|
||||||
<option name="number" value="00183" />
|
<option name="number" value="00183" />
|
||||||
<option name="presentableId" value="LOCAL-00183" />
|
<option name="presentableId" value="LOCAL-00183" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743621602590</updated>
|
<updated>1743644703042</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00184" summary="feat: svg change colors">
|
<task id="LOCAL-00184" summary="refactor: lib">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1743623617781</created>
|
<created>1743644942488</created>
|
||||||
<option name="number" value="00184" />
|
<option name="number" value="00184" />
|
||||||
<option name="presentableId" value="LOCAL-00184" />
|
<option name="presentableId" value="LOCAL-00184" />
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743623617781</updated>
|
<updated>1743644942488</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="185" />
|
<task id="LOCAL-00185" summary="fix: path">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743645074051</created>
|
||||||
|
<option name="number" value="00185" />
|
||||||
|
<option name="presentableId" value="LOCAL-00185" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743645074051</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="186" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -856,7 +848,6 @@
|
|||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||||
<MESSAGE value="refactor: sum" />
|
|
||||||
<MESSAGE value="fix: tools by category scroll" />
|
<MESSAGE value="fix: tools by category scroll" />
|
||||||
<MESSAGE value="fix: missing meta" />
|
<MESSAGE value="fix: missing meta" />
|
||||||
<MESSAGE value="feat: trim video" />
|
<MESSAGE value="feat: trim video" />
|
||||||
@@ -872,16 +863,17 @@
|
|||||||
<MESSAGE value="fix: typos" />
|
<MESSAGE value="fix: typos" />
|
||||||
<MESSAGE value="feat: compress video" />
|
<MESSAGE value="feat: compress video" />
|
||||||
<MESSAGE value="chore: compress video icon" />
|
<MESSAGE value="chore: compress video icon" />
|
||||||
<MESSAGE value="chore: tool description" />
|
<MESSAGE value="fix: gif speed" />
|
||||||
<MESSAGE value="feat: image resize init" />
|
<MESSAGE value="fix: tsc" />
|
||||||
<MESSAGE value="feat: svg resize" />
|
<MESSAGE value="fix: background color" />
|
||||||
<MESSAGE value="feat: gif resize" />
|
<MESSAGE value="docs: github trendings" />
|
||||||
<MESSAGE value="chore: add color" />
|
<MESSAGE value="docs: optimize" />
|
||||||
<MESSAGE value="docs: readme" />
|
<MESSAGE value="fix: stars button width for 1k+ " />
|
||||||
<MESSAGE value="feat: compress image" />
|
<MESSAGE value="feat: compress pdf" />
|
||||||
<MESSAGE value="chore: remove labels" />
|
<MESSAGE value="refactor: compress pdf" />
|
||||||
<MESSAGE value="feat: svg change colors" />
|
<MESSAGE value="refactor: lib" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat: svg change colors" />
|
<MESSAGE value="fix: path" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="fix: path" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"@ffmpeg/util": "^0.12.2",
|
"@ffmpeg/util": "^0.12.2",
|
||||||
"@imgly/background-removal": "^1.6.0",
|
"@imgly/background-removal": "^1.6.0",
|
||||||
"@jimp/types": "^1.6.0",
|
"@jimp/types": "^1.6.0",
|
||||||
|
"@jspawn/ghostscript-wasm": "^0.0.2",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@playwright/test": "^1.45.0",
|
"@playwright/test": "^1.45.0",
|
||||||
@@ -2038,6 +2039,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jspawn/ghostscript-wasm": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jspawn/ghostscript-wasm/-/ghostscript-wasm-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-IhGvfXNezc+V3jyJlmjz7oxrjWPqFPcz1gqRdo0Y7EkVyFuL1A+tCRnQXx/BHQZPRvBDA+Uf0EqkvXzfMzoDcw==",
|
||||||
|
"license": "AGPL-3.0"
|
||||||
|
},
|
||||||
"node_modules/@mui/base": {
|
"node_modules/@mui/base": {
|
||||||
"version": "5.0.0-beta.40",
|
"version": "5.0.0-beta.40",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"@ffmpeg/util": "^0.12.2",
|
"@ffmpeg/util": "^0.12.2",
|
||||||
"@imgly/background-removal": "^1.6.0",
|
"@imgly/background-removal": "^1.6.0",
|
||||||
"@jimp/types": "^1.6.0",
|
"@jimp/types": "^1.6.0",
|
||||||
|
"@jspawn/ghostscript-wasm": "^0.0.2",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@playwright/test": "^1.45.0",
|
"@playwright/test": "^1.45.0",
|
||||||
|
|||||||
39
public/gs.js
Normal file
39
public/gs.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// This is a placeholder file for the actual Ghostscript WASM implementation
|
||||||
|
// In a real implementation, this would be the compiled Ghostscript WASM module
|
||||||
|
|
||||||
|
// You would need to download the actual Ghostscript WASM files from:
|
||||||
|
// https://github.com/ochachacha/ps2pdf-wasm or compile it yourself
|
||||||
|
|
||||||
|
// This simulates the Module loading process that would occur with the real WASM file
|
||||||
|
(function () {
|
||||||
|
// Simulate WASM loading
|
||||||
|
console.log('Loading Ghostscript WASM module...');
|
||||||
|
|
||||||
|
// Expose a simulated Module to the window
|
||||||
|
window.Module = window.Module || {};
|
||||||
|
|
||||||
|
// Simulate filesystem
|
||||||
|
window.FS = {
|
||||||
|
writeFile: function (name, data) {
|
||||||
|
console.log(`[Simulated] Writing file: ${name}`);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
readFile: function (name, options) {
|
||||||
|
console.log(`[Simulated] Reading file: ${name}`);
|
||||||
|
// Return a sample Uint8Array that would represent a PDF
|
||||||
|
return new Uint8Array(10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mark module as initialized after a delay to simulate loading
|
||||||
|
setTimeout(function () {
|
||||||
|
window.Module.calledRun = true;
|
||||||
|
console.log('Ghostscript WASM module loaded');
|
||||||
|
|
||||||
|
// Add callMain method for direct calling
|
||||||
|
window.Module.callMain = function (args) {
|
||||||
|
console.log('[Simulated] Running Ghostscript with args:', args);
|
||||||
|
// In a real implementation, this would execute the WASM module with the given arguments
|
||||||
|
};
|
||||||
|
}, 1000);
|
||||||
|
})();
|
||||||
89
src/lib/ghostscript/background-worker.js
Normal file
89
src/lib/ghostscript/background-worker.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
function loadScript() {
|
||||||
|
import('./gs-worker.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
var Module;
|
||||||
|
|
||||||
|
function _GSPS2PDF(dataStruct, responseCallback) {
|
||||||
|
const compressionLevel = dataStruct.compressionLevel || 'medium';
|
||||||
|
|
||||||
|
// Set PDF settings based on compression level
|
||||||
|
let pdfSettings;
|
||||||
|
switch (compressionLevel) {
|
||||||
|
case 'low':
|
||||||
|
pdfSettings = '/printer'; // Higher quality, less compression
|
||||||
|
break;
|
||||||
|
case 'medium':
|
||||||
|
pdfSettings = '/ebook'; // Medium quality and compression
|
||||||
|
break;
|
||||||
|
case 'high':
|
||||||
|
pdfSettings = '/screen'; // Lower quality, higher compression
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pdfSettings = '/ebook'; // Default to medium
|
||||||
|
}
|
||||||
|
// first download the ps data
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', dataStruct.psDataURL);
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
xhr.onload = function () {
|
||||||
|
console.log('onload');
|
||||||
|
// release the URL
|
||||||
|
self.URL.revokeObjectURL(dataStruct.psDataURL);
|
||||||
|
//set up EMScripten environment
|
||||||
|
Module = {
|
||||||
|
preRun: [
|
||||||
|
function () {
|
||||||
|
self.Module.FS.writeFile('input.pdf', new Uint8Array(xhr.response));
|
||||||
|
}
|
||||||
|
],
|
||||||
|
postRun: [
|
||||||
|
function () {
|
||||||
|
var uarray = self.Module.FS.readFile('output.pdf', {
|
||||||
|
encoding: 'binary'
|
||||||
|
});
|
||||||
|
var blob = new Blob([uarray], { type: 'application/octet-stream' });
|
||||||
|
var pdfDataURL = self.URL.createObjectURL(blob);
|
||||||
|
responseCallback({ pdfDataURL: pdfDataURL, url: dataStruct.url });
|
||||||
|
}
|
||||||
|
],
|
||||||
|
arguments: [
|
||||||
|
'-sDEVICE=pdfwrite',
|
||||||
|
'-dCompatibilityLevel=1.4',
|
||||||
|
`-dPDFSETTINGS=${pdfSettings}`,
|
||||||
|
'-DNOPAUSE',
|
||||||
|
'-dQUIET',
|
||||||
|
'-dBATCH',
|
||||||
|
'-sOutputFile=output.pdf',
|
||||||
|
'input.pdf'
|
||||||
|
],
|
||||||
|
print: function (text) {},
|
||||||
|
printErr: function (text) {},
|
||||||
|
totalDependencies: 0,
|
||||||
|
noExitRuntime: 1
|
||||||
|
};
|
||||||
|
// Module.setStatus("Loading Ghostscript...");
|
||||||
|
if (!self.Module) {
|
||||||
|
self.Module = Module;
|
||||||
|
loadScript();
|
||||||
|
} else {
|
||||||
|
self.Module['calledRun'] = false;
|
||||||
|
self.Module['postRun'] = Module.postRun;
|
||||||
|
self.Module['preRun'] = Module.preRun;
|
||||||
|
self.Module.callMain();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('message', function ({ data: e }) {
|
||||||
|
console.log('message', e);
|
||||||
|
// e.data contains the message sent to the worker.
|
||||||
|
if (e.target !== 'wasm') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Message received from main script', e.data);
|
||||||
|
_GSPS2PDF(e.data, ({ pdfDataURL }) => self.postMessage(pdfDataURL));
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Worker ready');
|
||||||
5894
src/lib/ghostscript/gs-worker.js
Normal file
5894
src/lib/ghostscript/gs-worker.js
Normal file
File diff suppressed because it is too large
Load Diff
15
src/lib/ghostscript/worker-init.js
Normal file
15
src/lib/ghostscript/worker-init.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export async function compressWithGhostScript(dataStruct) {
|
||||||
|
const worker = new Worker(
|
||||||
|
new URL('./background-worker.js', import.meta.url),
|
||||||
|
{ type: 'module' }
|
||||||
|
);
|
||||||
|
worker.postMessage({ data: dataStruct, target: 'wasm' });
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const listener = (e) => {
|
||||||
|
resolve(e.data);
|
||||||
|
worker.removeEventListener('message', listener);
|
||||||
|
setTimeout(() => worker.terminate(), 0);
|
||||||
|
};
|
||||||
|
worker.addEventListener('message', listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
222
src/pages/tools/pdf/compress-pdf/index.tsx
Normal file
222
src/pages/tools/pdf/compress-pdf/index.tsx
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import ToolContent from '@components/ToolContent';
|
||||||
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
|
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||||
|
import ToolFileResult from '@components/result/ToolFileResult';
|
||||||
|
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import { CompressionLevel, InitialValuesType } from './types';
|
||||||
|
import { compressPdf } from './service';
|
||||||
|
import SimpleRadio from '@components/options/SimpleRadio';
|
||||||
|
import { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';
|
||||||
|
|
||||||
|
const initialValues: InitialValuesType = {
|
||||||
|
compressionLevel: 'medium'
|
||||||
|
};
|
||||||
|
|
||||||
|
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||||
|
{
|
||||||
|
title: 'Low Compression',
|
||||||
|
description: 'Slightly reduce file size with minimal quality loss',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'low'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Medium Compression',
|
||||||
|
description: 'Balance between file size and quality',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'medium'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'High Compression',
|
||||||
|
description: 'Maximum file size reduction with some quality loss',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'high'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CompressPdf({
|
||||||
|
title,
|
||||||
|
longDescription
|
||||||
|
}: ToolComponentProps) {
|
||||||
|
const [input, setInput] = useState<File | null>(null);
|
||||||
|
const [result, setResult] = useState<File | null>(null);
|
||||||
|
const [resultSize, setResultSize] = useState<string>('');
|
||||||
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
|
const [fileInfo, setFileInfo] = useState<{
|
||||||
|
size: string;
|
||||||
|
pages: number;
|
||||||
|
} | null>(null);
|
||||||
|
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||||
|
|
||||||
|
// Get the PDF info when a file is uploaded
|
||||||
|
useEffect(() => {
|
||||||
|
const getPdfInfo = async () => {
|
||||||
|
if (!input) {
|
||||||
|
setFileInfo(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await input.arrayBuffer();
|
||||||
|
const pdf = await PDFDocument.load(arrayBuffer);
|
||||||
|
const pages = pdf.getPageCount();
|
||||||
|
const size = formatFileSize(input.size);
|
||||||
|
|
||||||
|
setFileInfo({ size, pages });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting PDF info:', error);
|
||||||
|
setFileInfo(null);
|
||||||
|
showSnackBar(
|
||||||
|
'Error reading PDF file. Please make sure it is a valid PDF.',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getPdfInfo();
|
||||||
|
}, [input]);
|
||||||
|
|
||||||
|
const formatFileSize = (bytes: number): string => {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const compute = async (values: InitialValuesType, input: File | null) => {
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsProcessing(true);
|
||||||
|
const compressedPdf = await compressPdf(input, values);
|
||||||
|
setResult(compressedPdf);
|
||||||
|
|
||||||
|
// Log compression results
|
||||||
|
const compressionRatio = (compressedPdf.size / input.size) * 100;
|
||||||
|
console.log(`Compression Ratio: ${compressionRatio.toFixed(2)}%`);
|
||||||
|
setResultSize(formatFileSize(compressedPdf.size));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error compressing PDF:', error);
|
||||||
|
showSnackBar(
|
||||||
|
`Failed to compress PDF: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const compressionOptions: {
|
||||||
|
value: CompressionLevel;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
value: 'low',
|
||||||
|
label: 'Low Compression',
|
||||||
|
description: 'Slightly reduce file size with minimal quality loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'medium',
|
||||||
|
label: 'Medium Compression',
|
||||||
|
description: 'Balance between file size and quality'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'high',
|
||||||
|
label: 'High Compression',
|
||||||
|
description: 'Maximum file size reduction with some quality loss'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolContent
|
||||||
|
title={title}
|
||||||
|
input={input}
|
||||||
|
setInput={setInput}
|
||||||
|
initialValues={initialValues}
|
||||||
|
compute={compute}
|
||||||
|
inputComponent={
|
||||||
|
<ToolPdfInput
|
||||||
|
value={input}
|
||||||
|
onChange={setInput}
|
||||||
|
accept={['application/pdf']}
|
||||||
|
title={'Input PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
resultComponent={
|
||||||
|
<ToolFileResult
|
||||||
|
title={'Compressed PDF'}
|
||||||
|
value={result}
|
||||||
|
extension={'pdf'}
|
||||||
|
loading={isProcessing}
|
||||||
|
loadingText={'Compressing PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
getGroups={({ values, updateField }) => [
|
||||||
|
{
|
||||||
|
title: 'Compression Settings',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||||
|
Compression Level
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{compressionOptions.map((option) => (
|
||||||
|
<SimpleRadio
|
||||||
|
key={option.value}
|
||||||
|
title={option.label}
|
||||||
|
description={option.description}
|
||||||
|
checked={values.compressionLevel === option.value}
|
||||||
|
onClick={() => {
|
||||||
|
updateField('compressionLevel', option.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
{fileInfo && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
p: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2">
|
||||||
|
File size: <strong>{fileInfo.size}</strong>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Pages: <strong>{fileInfo.pages}</strong>
|
||||||
|
</Typography>
|
||||||
|
{resultSize && (
|
||||||
|
<Typography variant="body2">
|
||||||
|
Compressed file size: <strong>{resultSize}</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/pages/tools/pdf/compress-pdf/meta.ts
Normal file
28
src/pages/tools/pdf/compress-pdf/meta.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineTool } from '@tools/defineTool';
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export const tool = defineTool('pdf', {
|
||||||
|
name: 'Compress PDF',
|
||||||
|
path: 'compress-pdf',
|
||||||
|
icon: 'material-symbols:compress',
|
||||||
|
description:
|
||||||
|
'Reduce PDF file size while maintaining quality using Ghostscript',
|
||||||
|
shortDescription: 'Compress PDF files securely in your browser',
|
||||||
|
keywords: [
|
||||||
|
'pdf',
|
||||||
|
'compress',
|
||||||
|
'reduce',
|
||||||
|
'size',
|
||||||
|
'optimize',
|
||||||
|
'shrink',
|
||||||
|
'file size',
|
||||||
|
'ghostscript',
|
||||||
|
'secure',
|
||||||
|
'private',
|
||||||
|
'browser',
|
||||||
|
'webassembly'
|
||||||
|
],
|
||||||
|
longDescription:
|
||||||
|
'Compress PDF files securely in your browser using Ghostscript. Your files never leave your device, ensuring complete privacy while reducing file sizes for email sharing, uploading to websites, or saving storage space. Powered by WebAssembly technology.',
|
||||||
|
component: lazy(() => import('./index'))
|
||||||
|
});
|
||||||
107
src/pages/tools/pdf/compress-pdf/service.test.ts
Normal file
107
src/pages/tools/pdf/compress-pdf/service.test.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { compressPdf } from './service';
|
||||||
|
import { CompressionLevel } from './types';
|
||||||
|
|
||||||
|
// Mock the mupdf module
|
||||||
|
vi.mock('mupdf', () => {
|
||||||
|
return {
|
||||||
|
Document: {
|
||||||
|
openDocument: vi.fn(() => ({
|
||||||
|
countPages: vi.fn(() => 2),
|
||||||
|
loadPage: vi.fn(() => ({}))
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
PDFWriter: vi.fn(() => ({
|
||||||
|
addPage: vi.fn(),
|
||||||
|
asBuffer: vi.fn(() => Buffer.from('test'))
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock the pdf-lib module
|
||||||
|
vi.mock('pdf-lib', () => {
|
||||||
|
return {
|
||||||
|
PDFDocument: {
|
||||||
|
load: vi.fn(() => ({
|
||||||
|
getPageCount: vi.fn(() => 2)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compressPdf', () => {
|
||||||
|
it('should compress a PDF file with low compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with low compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'low' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compress a PDF file with medium compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with medium compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'medium' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compress a PDF file with high compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with high compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'high' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors during compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method to throw an error
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockRejectedValue(new Error('Test error'));
|
||||||
|
|
||||||
|
// Check that the function throws an error
|
||||||
|
await expect(
|
||||||
|
compressPdf(mockFile, { compressionLevel: 'medium' as CompressionLevel })
|
||||||
|
).rejects.toThrow('Failed to compress PDF: Test error');
|
||||||
|
});
|
||||||
|
});
|
||||||
44
src/pages/tools/pdf/compress-pdf/service.ts
Normal file
44
src/pages/tools/pdf/compress-pdf/service.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { InitialValuesType } from './types';
|
||||||
|
import { compressWithGhostScript } from '../../../../lib/ghostscript/worker-init';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compresses a PDF file using either Ghostscript WASM (preferred)
|
||||||
|
* or falls back to pdf-lib if WASM fails
|
||||||
|
*
|
||||||
|
* @param pdfFile - The PDF file to compress
|
||||||
|
* @param options - Compression options including compression level
|
||||||
|
* @returns A Promise that resolves to a compressed PDF File
|
||||||
|
*/
|
||||||
|
export async function compressPdf(
|
||||||
|
pdfFile: File,
|
||||||
|
options: InitialValuesType
|
||||||
|
): Promise<File> {
|
||||||
|
// Check if file is a PDF
|
||||||
|
if (pdfFile.type !== 'application/pdf') {
|
||||||
|
throw new Error('The provided file is not a PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataObject = {
|
||||||
|
psDataURL: URL.createObjectURL(pdfFile),
|
||||||
|
compressionLevel: options.compressionLevel
|
||||||
|
};
|
||||||
|
const compressedFileUrl: string = await compressWithGhostScript(dataObject);
|
||||||
|
return await loadPDFData(compressedFileUrl, pdfFile.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPDFData(url: string, filename: string): Promise<File> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
xhr.onload = function () {
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
const blob = new Blob([xhr.response], { type: 'application/pdf' });
|
||||||
|
const newFile = new File([blob], filename, {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
resolve(newFile);
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
5
src/pages/tools/pdf/compress-pdf/types.ts
Normal file
5
src/pages/tools/pdf/compress-pdf/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type CompressionLevel = 'low' | 'medium' | 'high';
|
||||||
|
|
||||||
|
export type InitialValuesType = {
|
||||||
|
compressionLevel: CompressionLevel;
|
||||||
|
};
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import { tool as pdfRotatePdf } from './rotate-pdf/meta';
|
import { tool as pdfRotatePdf } from './rotate-pdf/meta';
|
||||||
import { meta as splitPdfMeta } from './split-pdf/meta';
|
import { meta as splitPdfMeta } from './split-pdf/meta';
|
||||||
|
import { tool as compressPdfTool } from './compress-pdf/meta';
|
||||||
import { DefinedTool } from '@tools/defineTool';
|
import { DefinedTool } from '@tools/defineTool';
|
||||||
|
|
||||||
export const pdfTools: DefinedTool[] = [splitPdfMeta, pdfRotatePdf];
|
export const pdfTools: DefinedTool[] = [
|
||||||
|
splitPdfMeta,
|
||||||
|
pdfRotatePdf,
|
||||||
|
compressPdfTool
|
||||||
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user