diff --git a/raycast b/raycast deleted file mode 160000 index b32c652..0000000 --- a/raycast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b32c65230c3d55564c84bf0088288ac7f13d21b4 diff --git a/raycast/.eslintrc.json b/raycast/.eslintrc.json new file mode 100644 index 0000000..31608d8 --- /dev/null +++ b/raycast/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "root": true, + "env": { + "es2020": true, + "node": true + }, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"] +} diff --git a/raycast/.gitattributes b/raycast/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/raycast/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/raycast/.gitignore b/raycast/.gitignore new file mode 100644 index 0000000..0b7071f --- /dev/null +++ b/raycast/.gitignore @@ -0,0 +1,7 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# misc +.DS_Store \ No newline at end of file diff --git a/raycast/.prettierrc b/raycast/.prettierrc new file mode 100644 index 0000000..fc0f503 --- /dev/null +++ b/raycast/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 120, + "singleQuote": false +} diff --git a/raycast/CHANGELOG.md b/raycast/CHANGELOG.md new file mode 100644 index 0000000..efcaf50 --- /dev/null +++ b/raycast/CHANGELOG.md @@ -0,0 +1,60 @@ +# Image Modification Changelog + +## [Strip EXIF Data] - 2024-01-28 + +- Added "Strip EXIF Data" command +- Improved selection detection when Finder/Path Finder is not the frontmost application +- Fixed bug where converting from WebP to anything but PNG would change the file extension but not the file format + +## [Create Images, In-Clipboard Modification] - 2023-07-06 + +- Added settings to individually show/hide image formats from the list of conversion formats (#7023) +- Added settings for input source and output destination (e.g. clipboard, new file, replace original, etc.) (#6593) +- Made all commands work as expected regardless of input/output settings (i.e. you can rotate an image in the clipboard and immediately open it in Preview) (#7296) +- Added "Create Image" command to create image placeholders of various sizes, patterns, and colors +- Improved error handling for all commands, including copyable error messages +- Generally improved the code quality of the extension + +## [Optimize Images, SVG Conversion, More Filters] - 2023-04-03 + +- Add "Optimize Images" command +- Add ability to convert SVG to various image types +- Add ability to convert images to SVG using Potrace +- Add ability to convert PDF to various other image types +- Add ability to rotate and flip PDFs +- Add 13 new filters: + - Circular Screen + - Circular Wrap + - CMYK Halftone + - Dither + - Document Enhancement + - Dot Screen + - Hatched Screen + - Kaleidoscope + - Line Screen + - Maximum Component + - Minimum Components + - Posterize + - Sharpen Luminance +- Fix WebP operations failing due to insufficient permission + +## [WebP Support] - 2023-03-29 + +- Add WebP conversion support +- Add support for running SIPS commands on WebP (via temp file) +- Add Path Finder support (As preference toggle) + +## [Filters] - 2023-03-22 + +- Add "Apply Image Filter" command + +## [Padding, Bug Fixes] - 2023-03-15 + +- Added "Pad Images" command. +- Fixed compatibility with HEIC images and other formats. (#5238) + +## [Localization Fix] - 2023-03-07 + +- Updated the way the list of supported file types are handled. + +## [Initial Version] - 2023-02-23 diff --git a/raycast/DEVLOG.md b/raycast/DEVLOG.md new file mode 100644 index 0000000..8afafda --- /dev/null +++ b/raycast/DEVLOG.md @@ -0,0 +1,41 @@ +# Image Modification DevLog - A more detailed changelog + +## 2024-01-28 + +- Added "Strip EXIF Data" command +- Added ability to install ExifTool (for stripping EXIF data) +- Added checksum verification for ExifTool download +- Fixed bug which caused an error when padding a webp image but still padded the image correctly + +## 2024-01-27 + +- Added logic for handling arm vs. x86 architectures for libwebp operations +- Updated libwebp binaries +- Improved selection detection when Finder/Path Finder is not the frontmost application +- Fixed bug where converting from WebP to anything but PNG would change the file extension but not the file format + +## 2023-07-06 + +- Added 'Rotation Unit' setting for the Rotate Images command, supporting degrees and radians +- Added mathjs to support complex expressions in the Rotate Images command +- Added 'Default Pad Color' setting for the Pad Images command + +## 2023-07-05 + +- Added support for running all commands on the contents of the clipboard +- Added 'Create New Image' command to create a new image with selectable dimensions and patterns +- Filters can now be applied to every page of a PDF + +## 2023-06-18 + +- Added settings to show/hide image formats from the list of conversion formats + - Each format can be shown/hidden individually + - Added an action to open the Convert Images command preferences when all formats are hidden, alongside a brief message +- Added setting for where to store image results, supporting the following options: + - Replace Original + - Save In Containing Folder + - Copy To Clipboard + - Open In Preview + - Save to Downloads + - Save to Desktop +- Image operations will now ensure the destination file path is unique, to avoid overwriting existing files, unless the "Replace Original" result handling option is selected diff --git a/raycast/README.md b/raycast/README.md new file mode 100644 index 0000000..8a61b1d --- /dev/null +++ b/raycast/README.md @@ -0,0 +1,40 @@ +# Image Modification + +Apply filters and transformations to various image formats, and convert between them. Create new images by specifying their dimensions, colors, and patterns. Operate on selected files or on images in the clipboard. + +## Features + +- Convert between many different image formats, including WebP and SVG + - WebP conversion supported by [cwebp and dwebp](https://developers.google.com/speed/webp/docs/precompiled) + - SVG conversion supported by [Potrace](https://potrace.sourceforge.net) +- Rotate, flip, scale, resize, and pad images by applying SIPS commands +- Apply filters and distortions such as Bokeh Blur, Noir, X-Ray, and more +- Rotate, flip, and apply filters to the pages of PDFs +- Optimize images using JPEG compression, [svgo](https://github.com/svg/svgo), and other strategies +- Perform realtime image manipulation on images in the clipboard + +## Commands + +- Apply Image Filter + - Apply various filters to the provided images using Apple's CIFilters +- Convert Images + - Convert selected images from their current format to another. +- Create New Image + - Create a new image by selecting the dimensions, color, and pattern. +- Flip Images Horizontally + - Flip selected images horizontally. +- Flip Images Vertically + - Flip selected images vertically. +- Optimize Images [Amount] + - Decrease image file size by reducing complexity. +- Pad Images [Amount] [Color] + - Pad images by the specified number of pixels using the provided color (defaults to white). +- Resize Images [Width] [Height] + - Resize images proportionally by specifying either width or height. + - Resize images precisely by specifying both parameters. +- Rotate Images [Degrees] + - Rotate images clockwise by the specified amount. +- Scale Images [Scale Factor] + - Scale images proportionally by the specified factor. + +Filter thumbnail image credit: diff --git a/raycast/assets/command-icon.png b/raycast/assets/command-icon.png new file mode 100644 index 0000000..fadba60 Binary files /dev/null and b/raycast/assets/command-icon.png differ diff --git a/raycast/assets/potrace/COPYING b/raycast/assets/potrace/COPYING new file mode 100644 index 0000000..4c1337f --- /dev/null +++ b/raycast/assets/potrace/COPYING @@ -0,0 +1,341 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/raycast/assets/potrace/potrace b/raycast/assets/potrace/potrace new file mode 100755 index 0000000..88d4b54 Binary files /dev/null and b/raycast/assets/potrace/potrace differ diff --git a/raycast/assets/thumbnails/100x100.webp b/raycast/assets/thumbnails/100x100.webp new file mode 100644 index 0000000..2d1e06e Binary files /dev/null and b/raycast/assets/thumbnails/100x100.webp differ diff --git a/raycast/assets/thumbnails/100x1024.webp b/raycast/assets/thumbnails/100x1024.webp new file mode 100644 index 0000000..c23e304 Binary files /dev/null and b/raycast/assets/thumbnails/100x1024.webp differ diff --git a/raycast/assets/thumbnails/100x128.webp b/raycast/assets/thumbnails/100x128.webp new file mode 100644 index 0000000..fc4f0bd Binary files /dev/null and b/raycast/assets/thumbnails/100x128.webp differ diff --git a/raycast/assets/thumbnails/100x256.webp b/raycast/assets/thumbnails/100x256.webp new file mode 100644 index 0000000..461544c Binary files /dev/null and b/raycast/assets/thumbnails/100x256.webp differ diff --git a/raycast/assets/thumbnails/100x32.webp b/raycast/assets/thumbnails/100x32.webp new file mode 100644 index 0000000..75551ac Binary files /dev/null and b/raycast/assets/thumbnails/100x32.webp differ diff --git a/raycast/assets/thumbnails/100x50.webp b/raycast/assets/thumbnails/100x50.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/100x50.webp differ diff --git a/raycast/assets/thumbnails/100x512.webp b/raycast/assets/thumbnails/100x512.webp new file mode 100644 index 0000000..f2c61c5 Binary files /dev/null and b/raycast/assets/thumbnails/100x512.webp differ diff --git a/raycast/assets/thumbnails/100x64.webp b/raycast/assets/thumbnails/100x64.webp new file mode 100644 index 0000000..0ed181d Binary files /dev/null and b/raycast/assets/thumbnails/100x64.webp differ diff --git a/raycast/assets/thumbnails/1024x100.webp b/raycast/assets/thumbnails/1024x100.webp new file mode 100644 index 0000000..0b90417 Binary files /dev/null and b/raycast/assets/thumbnails/1024x100.webp differ diff --git a/raycast/assets/thumbnails/1024x1024.webp b/raycast/assets/thumbnails/1024x1024.webp new file mode 100644 index 0000000..2151eb4 Binary files /dev/null and b/raycast/assets/thumbnails/1024x1024.webp differ diff --git a/raycast/assets/thumbnails/1024x128.webp b/raycast/assets/thumbnails/1024x128.webp new file mode 100644 index 0000000..1f75ea2 Binary files /dev/null and b/raycast/assets/thumbnails/1024x128.webp differ diff --git a/raycast/assets/thumbnails/1024x256.webp b/raycast/assets/thumbnails/1024x256.webp new file mode 100644 index 0000000..34dcf5f Binary files /dev/null and b/raycast/assets/thumbnails/1024x256.webp differ diff --git a/raycast/assets/thumbnails/1024x32.webp b/raycast/assets/thumbnails/1024x32.webp new file mode 100644 index 0000000..954ab0f Binary files /dev/null and b/raycast/assets/thumbnails/1024x32.webp differ diff --git a/raycast/assets/thumbnails/1024x50.webp b/raycast/assets/thumbnails/1024x50.webp new file mode 100644 index 0000000..a9d6280 Binary files /dev/null and b/raycast/assets/thumbnails/1024x50.webp differ diff --git a/raycast/assets/thumbnails/1024x512.webp b/raycast/assets/thumbnails/1024x512.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/1024x512.webp differ diff --git a/raycast/assets/thumbnails/1024x64.webp b/raycast/assets/thumbnails/1024x64.webp new file mode 100644 index 0000000..4585b8c Binary files /dev/null and b/raycast/assets/thumbnails/1024x64.webp differ diff --git a/raycast/assets/thumbnails/128x100.webp b/raycast/assets/thumbnails/128x100.webp new file mode 100644 index 0000000..129814b Binary files /dev/null and b/raycast/assets/thumbnails/128x100.webp differ diff --git a/raycast/assets/thumbnails/128x1024.webp b/raycast/assets/thumbnails/128x1024.webp new file mode 100644 index 0000000..3842a1b Binary files /dev/null and b/raycast/assets/thumbnails/128x1024.webp differ diff --git a/raycast/assets/thumbnails/128x128.webp b/raycast/assets/thumbnails/128x128.webp new file mode 100644 index 0000000..f3193c7 Binary files /dev/null and b/raycast/assets/thumbnails/128x128.webp differ diff --git a/raycast/assets/thumbnails/128x256.webp b/raycast/assets/thumbnails/128x256.webp new file mode 100644 index 0000000..e7bbc65 Binary files /dev/null and b/raycast/assets/thumbnails/128x256.webp differ diff --git a/raycast/assets/thumbnails/128x32.webp b/raycast/assets/thumbnails/128x32.webp new file mode 100644 index 0000000..34dcf5f Binary files /dev/null and b/raycast/assets/thumbnails/128x32.webp differ diff --git a/raycast/assets/thumbnails/128x50.webp b/raycast/assets/thumbnails/128x50.webp new file mode 100644 index 0000000..517a155 Binary files /dev/null and b/raycast/assets/thumbnails/128x50.webp differ diff --git a/raycast/assets/thumbnails/128x512.webp b/raycast/assets/thumbnails/128x512.webp new file mode 100644 index 0000000..d6067c1 Binary files /dev/null and b/raycast/assets/thumbnails/128x512.webp differ diff --git a/raycast/assets/thumbnails/128x64.webp b/raycast/assets/thumbnails/128x64.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/128x64.webp differ diff --git a/raycast/assets/thumbnails/256x100.webp b/raycast/assets/thumbnails/256x100.webp new file mode 100644 index 0000000..517a155 Binary files /dev/null and b/raycast/assets/thumbnails/256x100.webp differ diff --git a/raycast/assets/thumbnails/256x1024.webp b/raycast/assets/thumbnails/256x1024.webp new file mode 100644 index 0000000..d6067c1 Binary files /dev/null and b/raycast/assets/thumbnails/256x1024.webp differ diff --git a/raycast/assets/thumbnails/256x128.webp b/raycast/assets/thumbnails/256x128.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/256x128.webp differ diff --git a/raycast/assets/thumbnails/256x256.webp b/raycast/assets/thumbnails/256x256.webp new file mode 100644 index 0000000..aaad1ae Binary files /dev/null and b/raycast/assets/thumbnails/256x256.webp differ diff --git a/raycast/assets/thumbnails/256x32.webp b/raycast/assets/thumbnails/256x32.webp new file mode 100644 index 0000000..1f75ea2 Binary files /dev/null and b/raycast/assets/thumbnails/256x32.webp differ diff --git a/raycast/assets/thumbnails/256x50.webp b/raycast/assets/thumbnails/256x50.webp new file mode 100644 index 0000000..23e107c Binary files /dev/null and b/raycast/assets/thumbnails/256x50.webp differ diff --git a/raycast/assets/thumbnails/256x512.webp b/raycast/assets/thumbnails/256x512.webp new file mode 100644 index 0000000..e7bbc65 Binary files /dev/null and b/raycast/assets/thumbnails/256x512.webp differ diff --git a/raycast/assets/thumbnails/256x64.webp b/raycast/assets/thumbnails/256x64.webp new file mode 100644 index 0000000..34dcf5f Binary files /dev/null and b/raycast/assets/thumbnails/256x64.webp differ diff --git a/raycast/assets/thumbnails/32x100.webp b/raycast/assets/thumbnails/32x100.webp new file mode 100644 index 0000000..a49784f Binary files /dev/null and b/raycast/assets/thumbnails/32x100.webp differ diff --git a/raycast/assets/thumbnails/32x1024.webp b/raycast/assets/thumbnails/32x1024.webp new file mode 100644 index 0000000..4b9a2c8 Binary files /dev/null and b/raycast/assets/thumbnails/32x1024.webp differ diff --git a/raycast/assets/thumbnails/32x128.webp b/raycast/assets/thumbnails/32x128.webp new file mode 100644 index 0000000..97cc8c5 Binary files /dev/null and b/raycast/assets/thumbnails/32x128.webp differ diff --git a/raycast/assets/thumbnails/32x256.webp b/raycast/assets/thumbnails/32x256.webp new file mode 100644 index 0000000..f290206 Binary files /dev/null and b/raycast/assets/thumbnails/32x256.webp differ diff --git a/raycast/assets/thumbnails/32x32.webp b/raycast/assets/thumbnails/32x32.webp new file mode 100644 index 0000000..21ec5f8 Binary files /dev/null and b/raycast/assets/thumbnails/32x32.webp differ diff --git a/raycast/assets/thumbnails/32x50.webp b/raycast/assets/thumbnails/32x50.webp new file mode 100644 index 0000000..b78f418 Binary files /dev/null and b/raycast/assets/thumbnails/32x50.webp differ diff --git a/raycast/assets/thumbnails/32x512.webp b/raycast/assets/thumbnails/32x512.webp new file mode 100644 index 0000000..4a5d350 Binary files /dev/null and b/raycast/assets/thumbnails/32x512.webp differ diff --git a/raycast/assets/thumbnails/32x64.webp b/raycast/assets/thumbnails/32x64.webp new file mode 100644 index 0000000..9255208 Binary files /dev/null and b/raycast/assets/thumbnails/32x64.webp differ diff --git a/raycast/assets/thumbnails/50x100.webp b/raycast/assets/thumbnails/50x100.webp new file mode 100644 index 0000000..3389e3b Binary files /dev/null and b/raycast/assets/thumbnails/50x100.webp differ diff --git a/raycast/assets/thumbnails/50x1024.webp b/raycast/assets/thumbnails/50x1024.webp new file mode 100644 index 0000000..d254136 Binary files /dev/null and b/raycast/assets/thumbnails/50x1024.webp differ diff --git a/raycast/assets/thumbnails/50x128.webp b/raycast/assets/thumbnails/50x128.webp new file mode 100644 index 0000000..b507d95 Binary files /dev/null and b/raycast/assets/thumbnails/50x128.webp differ diff --git a/raycast/assets/thumbnails/50x256.webp b/raycast/assets/thumbnails/50x256.webp new file mode 100644 index 0000000..757ecae Binary files /dev/null and b/raycast/assets/thumbnails/50x256.webp differ diff --git a/raycast/assets/thumbnails/50x32.webp b/raycast/assets/thumbnails/50x32.webp new file mode 100644 index 0000000..b180a99 Binary files /dev/null and b/raycast/assets/thumbnails/50x32.webp differ diff --git a/raycast/assets/thumbnails/50x50.webp b/raycast/assets/thumbnails/50x50.webp new file mode 100644 index 0000000..6a4973c Binary files /dev/null and b/raycast/assets/thumbnails/50x50.webp differ diff --git a/raycast/assets/thumbnails/50x512.webp b/raycast/assets/thumbnails/50x512.webp new file mode 100644 index 0000000..be548c7 Binary files /dev/null and b/raycast/assets/thumbnails/50x512.webp differ diff --git a/raycast/assets/thumbnails/50x64.webp b/raycast/assets/thumbnails/50x64.webp new file mode 100644 index 0000000..e59d8a9 Binary files /dev/null and b/raycast/assets/thumbnails/50x64.webp differ diff --git a/raycast/assets/thumbnails/512x100.webp b/raycast/assets/thumbnails/512x100.webp new file mode 100644 index 0000000..23e107c Binary files /dev/null and b/raycast/assets/thumbnails/512x100.webp differ diff --git a/raycast/assets/thumbnails/512x1024.webp b/raycast/assets/thumbnails/512x1024.webp new file mode 100644 index 0000000..e7bbc65 Binary files /dev/null and b/raycast/assets/thumbnails/512x1024.webp differ diff --git a/raycast/assets/thumbnails/512x128.webp b/raycast/assets/thumbnails/512x128.webp new file mode 100644 index 0000000..34dcf5f Binary files /dev/null and b/raycast/assets/thumbnails/512x128.webp differ diff --git a/raycast/assets/thumbnails/512x256.webp b/raycast/assets/thumbnails/512x256.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/512x256.webp differ diff --git a/raycast/assets/thumbnails/512x32.webp b/raycast/assets/thumbnails/512x32.webp new file mode 100644 index 0000000..4585b8c Binary files /dev/null and b/raycast/assets/thumbnails/512x32.webp differ diff --git a/raycast/assets/thumbnails/512x50.webp b/raycast/assets/thumbnails/512x50.webp new file mode 100644 index 0000000..0b90417 Binary files /dev/null and b/raycast/assets/thumbnails/512x50.webp differ diff --git a/raycast/assets/thumbnails/512x512.webp b/raycast/assets/thumbnails/512x512.webp new file mode 100644 index 0000000..5bd5ca1 Binary files /dev/null and b/raycast/assets/thumbnails/512x512.webp differ diff --git a/raycast/assets/thumbnails/512x64.webp b/raycast/assets/thumbnails/512x64.webp new file mode 100644 index 0000000..1f75ea2 Binary files /dev/null and b/raycast/assets/thumbnails/512x64.webp differ diff --git a/raycast/assets/thumbnails/64x100.webp b/raycast/assets/thumbnails/64x100.webp new file mode 100644 index 0000000..c363f10 Binary files /dev/null and b/raycast/assets/thumbnails/64x100.webp differ diff --git a/raycast/assets/thumbnails/64x1024.webp b/raycast/assets/thumbnails/64x1024.webp new file mode 100644 index 0000000..88f9bd2 Binary files /dev/null and b/raycast/assets/thumbnails/64x1024.webp differ diff --git a/raycast/assets/thumbnails/64x128.webp b/raycast/assets/thumbnails/64x128.webp new file mode 100644 index 0000000..e7bbc65 Binary files /dev/null and b/raycast/assets/thumbnails/64x128.webp differ diff --git a/raycast/assets/thumbnails/64x256.webp b/raycast/assets/thumbnails/64x256.webp new file mode 100644 index 0000000..d6067c1 Binary files /dev/null and b/raycast/assets/thumbnails/64x256.webp differ diff --git a/raycast/assets/thumbnails/64x32.webp b/raycast/assets/thumbnails/64x32.webp new file mode 100644 index 0000000..0cb478e Binary files /dev/null and b/raycast/assets/thumbnails/64x32.webp differ diff --git a/raycast/assets/thumbnails/64x50.webp b/raycast/assets/thumbnails/64x50.webp new file mode 100644 index 0000000..129814b Binary files /dev/null and b/raycast/assets/thumbnails/64x50.webp differ diff --git a/raycast/assets/thumbnails/64x512.webp b/raycast/assets/thumbnails/64x512.webp new file mode 100644 index 0000000..3842a1b Binary files /dev/null and b/raycast/assets/thumbnails/64x512.webp differ diff --git a/raycast/assets/thumbnails/64x64.webp b/raycast/assets/thumbnails/64x64.webp new file mode 100644 index 0000000..3a53823 Binary files /dev/null and b/raycast/assets/thumbnails/64x64.webp differ diff --git a/raycast/assets/thumbnails/_original.webp b/raycast/assets/thumbnails/_original.webp new file mode 100644 index 0000000..822e2ce Binary files /dev/null and b/raycast/assets/thumbnails/_original.webp differ diff --git a/raycast/assets/thumbnails/bloom.webp b/raycast/assets/thumbnails/bloom.webp new file mode 100644 index 0000000..3e2559f Binary files /dev/null and b/raycast/assets/thumbnails/bloom.webp differ diff --git a/raycast/assets/thumbnails/bokeh_blur.webp b/raycast/assets/thumbnails/bokeh_blur.webp new file mode 100644 index 0000000..4179226 Binary files /dev/null and b/raycast/assets/thumbnails/bokeh_blur.webp differ diff --git a/raycast/assets/thumbnails/box_blur.webp b/raycast/assets/thumbnails/box_blur.webp new file mode 100644 index 0000000..764c3b2 Binary files /dev/null and b/raycast/assets/thumbnails/box_blur.webp differ diff --git a/raycast/assets/thumbnails/checkerboard.webp b/raycast/assets/thumbnails/checkerboard.webp new file mode 100644 index 0000000..a8e8df5 Binary files /dev/null and b/raycast/assets/thumbnails/checkerboard.webp differ diff --git a/raycast/assets/thumbnails/chrome.webp b/raycast/assets/thumbnails/chrome.webp new file mode 100644 index 0000000..59a0209 Binary files /dev/null and b/raycast/assets/thumbnails/chrome.webp differ diff --git a/raycast/assets/thumbnails/circular_screen.webp b/raycast/assets/thumbnails/circular_screen.webp new file mode 100644 index 0000000..fc571d9 Binary files /dev/null and b/raycast/assets/thumbnails/circular_screen.webp differ diff --git a/raycast/assets/thumbnails/circular_wrap.webp b/raycast/assets/thumbnails/circular_wrap.webp new file mode 100644 index 0000000..89f9a39 Binary files /dev/null and b/raycast/assets/thumbnails/circular_wrap.webp differ diff --git a/raycast/assets/thumbnails/cmyk_halftone.webp b/raycast/assets/thumbnails/cmyk_halftone.webp new file mode 100644 index 0000000..7b04a95 Binary files /dev/null and b/raycast/assets/thumbnails/cmyk_halftone.webp differ diff --git a/raycast/assets/thumbnails/comic.webp b/raycast/assets/thumbnails/comic.webp new file mode 100644 index 0000000..0174715 Binary files /dev/null and b/raycast/assets/thumbnails/comic.webp differ diff --git a/raycast/assets/thumbnails/constant_color.webp b/raycast/assets/thumbnails/constant_color.webp new file mode 100644 index 0000000..bfaae29 Binary files /dev/null and b/raycast/assets/thumbnails/constant_color.webp differ diff --git a/raycast/assets/thumbnails/crystallize.webp b/raycast/assets/thumbnails/crystallize.webp new file mode 100644 index 0000000..32ba4c2 Binary files /dev/null and b/raycast/assets/thumbnails/crystallize.webp differ diff --git a/raycast/assets/thumbnails/depth_of_field.webp b/raycast/assets/thumbnails/depth_of_field.webp new file mode 100644 index 0000000..9e07ac3 Binary files /dev/null and b/raycast/assets/thumbnails/depth_of_field.webp differ diff --git a/raycast/assets/thumbnails/disc_blur.webp b/raycast/assets/thumbnails/disc_blur.webp new file mode 100644 index 0000000..9e57e8b Binary files /dev/null and b/raycast/assets/thumbnails/disc_blur.webp differ diff --git a/raycast/assets/thumbnails/dither.webp b/raycast/assets/thumbnails/dither.webp new file mode 100644 index 0000000..aacf436 Binary files /dev/null and b/raycast/assets/thumbnails/dither.webp differ diff --git a/raycast/assets/thumbnails/document_enhancement.webp b/raycast/assets/thumbnails/document_enhancement.webp new file mode 100644 index 0000000..0b23855 Binary files /dev/null and b/raycast/assets/thumbnails/document_enhancement.webp differ diff --git a/raycast/assets/thumbnails/dot_screen.webp b/raycast/assets/thumbnails/dot_screen.webp new file mode 100644 index 0000000..9c7a5a1 Binary files /dev/null and b/raycast/assets/thumbnails/dot_screen.webp differ diff --git a/raycast/assets/thumbnails/edge_work.webp b/raycast/assets/thumbnails/edge_work.webp new file mode 100644 index 0000000..18c9531 Binary files /dev/null and b/raycast/assets/thumbnails/edge_work.webp differ diff --git a/raycast/assets/thumbnails/edges.webp b/raycast/assets/thumbnails/edges.webp new file mode 100644 index 0000000..62a9e81 Binary files /dev/null and b/raycast/assets/thumbnails/edges.webp differ diff --git a/raycast/assets/thumbnails/fade.webp b/raycast/assets/thumbnails/fade.webp new file mode 100644 index 0000000..c1e12f1 Binary files /dev/null and b/raycast/assets/thumbnails/fade.webp differ diff --git a/raycast/assets/thumbnails/gaussian_blur.webp b/raycast/assets/thumbnails/gaussian_blur.webp new file mode 100644 index 0000000..51da45e Binary files /dev/null and b/raycast/assets/thumbnails/gaussian_blur.webp differ diff --git a/raycast/assets/thumbnails/gloom.webp b/raycast/assets/thumbnails/gloom.webp new file mode 100644 index 0000000..dfb1166 Binary files /dev/null and b/raycast/assets/thumbnails/gloom.webp differ diff --git a/raycast/assets/thumbnails/hatched_screen.webp b/raycast/assets/thumbnails/hatched_screen.webp new file mode 100644 index 0000000..8b7f0c4 Binary files /dev/null and b/raycast/assets/thumbnails/hatched_screen.webp differ diff --git a/raycast/assets/thumbnails/hexagonal_pixellate.webp b/raycast/assets/thumbnails/hexagonal_pixellate.webp new file mode 100644 index 0000000..d64baf0 Binary files /dev/null and b/raycast/assets/thumbnails/hexagonal_pixellate.webp differ diff --git a/raycast/assets/thumbnails/instant.webp b/raycast/assets/thumbnails/instant.webp new file mode 100644 index 0000000..421a219 Binary files /dev/null and b/raycast/assets/thumbnails/instant.webp differ diff --git a/raycast/assets/thumbnails/invert.webp b/raycast/assets/thumbnails/invert.webp new file mode 100644 index 0000000..2cec081 Binary files /dev/null and b/raycast/assets/thumbnails/invert.webp differ diff --git a/raycast/assets/thumbnails/kaleidoscope.webp b/raycast/assets/thumbnails/kaleidoscope.webp new file mode 100644 index 0000000..d3ca9bf Binary files /dev/null and b/raycast/assets/thumbnails/kaleidoscope.webp differ diff --git a/raycast/assets/thumbnails/lenticular_halo.webp b/raycast/assets/thumbnails/lenticular_halo.webp new file mode 100644 index 0000000..f8dfa79 Binary files /dev/null and b/raycast/assets/thumbnails/lenticular_halo.webp differ diff --git a/raycast/assets/thumbnails/line_overlay.webp b/raycast/assets/thumbnails/line_overlay.webp new file mode 100644 index 0000000..3ba2d67 Binary files /dev/null and b/raycast/assets/thumbnails/line_overlay.webp differ diff --git a/raycast/assets/thumbnails/line_screen.webp b/raycast/assets/thumbnails/line_screen.webp new file mode 100644 index 0000000..c9e412b Binary files /dev/null and b/raycast/assets/thumbnails/line_screen.webp differ diff --git a/raycast/assets/thumbnails/linear_gradient.webp b/raycast/assets/thumbnails/linear_gradient.webp new file mode 100644 index 0000000..a0cd6ca Binary files /dev/null and b/raycast/assets/thumbnails/linear_gradient.webp differ diff --git a/raycast/assets/thumbnails/maximum_component.webp b/raycast/assets/thumbnails/maximum_component.webp new file mode 100644 index 0000000..70d0c42 Binary files /dev/null and b/raycast/assets/thumbnails/maximum_component.webp differ diff --git a/raycast/assets/thumbnails/median.webp b/raycast/assets/thumbnails/median.webp new file mode 100644 index 0000000..c2525d0 Binary files /dev/null and b/raycast/assets/thumbnails/median.webp differ diff --git a/raycast/assets/thumbnails/minimum_component.webp b/raycast/assets/thumbnails/minimum_component.webp new file mode 100644 index 0000000..7f6fe6c Binary files /dev/null and b/raycast/assets/thumbnails/minimum_component.webp differ diff --git a/raycast/assets/thumbnails/mono.webp b/raycast/assets/thumbnails/mono.webp new file mode 100644 index 0000000..519abcf Binary files /dev/null and b/raycast/assets/thumbnails/mono.webp differ diff --git a/raycast/assets/thumbnails/motion_blur.webp b/raycast/assets/thumbnails/motion_blur.webp new file mode 100644 index 0000000..42f6882 Binary files /dev/null and b/raycast/assets/thumbnails/motion_blur.webp differ diff --git a/raycast/assets/thumbnails/noir.webp b/raycast/assets/thumbnails/noir.webp new file mode 100644 index 0000000..b38f043 Binary files /dev/null and b/raycast/assets/thumbnails/noir.webp differ diff --git a/raycast/assets/thumbnails/noise_reduction.webp b/raycast/assets/thumbnails/noise_reduction.webp new file mode 100644 index 0000000..8773b71 Binary files /dev/null and b/raycast/assets/thumbnails/noise_reduction.webp differ diff --git a/raycast/assets/thumbnails/pixellate.webp b/raycast/assets/thumbnails/pixellate.webp new file mode 100644 index 0000000..d7e0b07 Binary files /dev/null and b/raycast/assets/thumbnails/pixellate.webp differ diff --git a/raycast/assets/thumbnails/pointillize.webp b/raycast/assets/thumbnails/pointillize.webp new file mode 100644 index 0000000..b218572 Binary files /dev/null and b/raycast/assets/thumbnails/pointillize.webp differ diff --git a/raycast/assets/thumbnails/posterize.webp b/raycast/assets/thumbnails/posterize.webp new file mode 100644 index 0000000..e838fab Binary files /dev/null and b/raycast/assets/thumbnails/posterize.webp differ diff --git a/raycast/assets/thumbnails/process.webp b/raycast/assets/thumbnails/process.webp new file mode 100644 index 0000000..6e2dad7 Binary files /dev/null and b/raycast/assets/thumbnails/process.webp differ diff --git a/raycast/assets/thumbnails/radial_gradient.webp b/raycast/assets/thumbnails/radial_gradient.webp new file mode 100644 index 0000000..c465702 Binary files /dev/null and b/raycast/assets/thumbnails/radial_gradient.webp differ diff --git a/raycast/assets/thumbnails/random.webp b/raycast/assets/thumbnails/random.webp new file mode 100644 index 0000000..272a888 Binary files /dev/null and b/raycast/assets/thumbnails/random.webp differ diff --git a/raycast/assets/thumbnails/sepia.webp b/raycast/assets/thumbnails/sepia.webp new file mode 100644 index 0000000..b436513 Binary files /dev/null and b/raycast/assets/thumbnails/sepia.webp differ diff --git a/raycast/assets/thumbnails/sharpen_luminance.webp b/raycast/assets/thumbnails/sharpen_luminance.webp new file mode 100644 index 0000000..5e93fec Binary files /dev/null and b/raycast/assets/thumbnails/sharpen_luminance.webp differ diff --git a/raycast/assets/thumbnails/star_shine.webp b/raycast/assets/thumbnails/star_shine.webp new file mode 100644 index 0000000..2ba42c7 Binary files /dev/null and b/raycast/assets/thumbnails/star_shine.webp differ diff --git a/raycast/assets/thumbnails/stripes.webp b/raycast/assets/thumbnails/stripes.webp new file mode 100644 index 0000000..17585c5 Binary files /dev/null and b/raycast/assets/thumbnails/stripes.webp differ diff --git a/raycast/assets/thumbnails/sunbeams.webp b/raycast/assets/thumbnails/sunbeams.webp new file mode 100644 index 0000000..3e43210 Binary files /dev/null and b/raycast/assets/thumbnails/sunbeams.webp differ diff --git a/raycast/assets/thumbnails/thermal.webp b/raycast/assets/thumbnails/thermal.webp new file mode 100644 index 0000000..0f079f1 Binary files /dev/null and b/raycast/assets/thumbnails/thermal.webp differ diff --git a/raycast/assets/thumbnails/tonal.webp b/raycast/assets/thumbnails/tonal.webp new file mode 100644 index 0000000..8770766 Binary files /dev/null and b/raycast/assets/thumbnails/tonal.webp differ diff --git a/raycast/assets/thumbnails/transfer.webp b/raycast/assets/thumbnails/transfer.webp new file mode 100644 index 0000000..1e4e3f3 Binary files /dev/null and b/raycast/assets/thumbnails/transfer.webp differ diff --git a/raycast/assets/thumbnails/vignette.webp b/raycast/assets/thumbnails/vignette.webp new file mode 100644 index 0000000..bd7127d Binary files /dev/null and b/raycast/assets/thumbnails/vignette.webp differ diff --git a/raycast/assets/thumbnails/x-ray.webp b/raycast/assets/thumbnails/x-ray.webp new file mode 100644 index 0000000..a54bbdb Binary files /dev/null and b/raycast/assets/thumbnails/x-ray.webp differ diff --git a/raycast/assets/thumbnails/zoom_blur.webp b/raycast/assets/thumbnails/zoom_blur.webp new file mode 100644 index 0000000..c0a4152 Binary files /dev/null and b/raycast/assets/thumbnails/zoom_blur.webp differ diff --git a/raycast/assets/webp/README.md b/raycast/assets/webp/README.md new file mode 100644 index 0000000..7a06c7f --- /dev/null +++ b/raycast/assets/webp/README.md @@ -0,0 +1,65 @@ +# WebP Codec + +``` + __ __ ____ ____ ____ + / \\/ \/ _ \/ _ )/ _ \ + \ / __/ _ \ __/ + \__\__/\____/\_____/__/ ____ ___ + / _/ / \ \ / _ \/ _/ + / \_/ / / \ \ __/ \__ + \____/____/\_____/_____/____/v1.3.0 +``` + +WebP codec is a library to encode and decode images in WebP format. This package +contains the library that can be used in other programs to add WebP support, as +well as the command line tools 'cwebp' and 'dwebp' to compress and decompress +images respectively. + +See https://developers.google.com/speed/webp for details on the image format. + +The latest source tree is available at +https://chromium.googlesource.com/webm/libwebp + +It is released under the same license as the WebM project. See +https://www.webmproject.org/license/software/ or the "COPYING" file for details. +An additional intellectual property rights grant can be found in the file +PATENTS. + +## Files + +* bin/cwebp : encoding tool +* bin/dwebp : decoding tool +* bin/gif2webp : gif conversion tool +* bin/img2webp : animation creation tool +* bin/vwebp : webp visualization tool +* bin/webpinfo : webp analysis tool +* bin/webpmux : webp muxing tool +* bin/anim\_diff : webp file comparison tool +* bin/anim\_dump : tool for dumping animation frames +* bin/get\_disto : tool for calculating file distortion +* bin/webp\_quality : webp quality estimation tool +* doc/ : manual in HTML and text formats +* lib/ : static libraries +* include/webp : headers + +## Encoding and Decoding Tools + +The bin/ directory contains tools to encode and decode images and animations, +view information about WebP images, and more. See the +[tools documentation](doc/tools.md). + +## APIs + +See the [APIs documentation](doc/api.md). + +## Bugs + +Please report all bugs to the issue tracker: https://bugs.chromium.org/p/webp + +Patches welcome! See [how to contribute](CONTRIBUTING.md). + +## Discuss + +Email: webp-discuss@webmproject.org + +Web: https://groups.google.com/a/webmproject.org/group/webp-discuss diff --git a/raycast/assets/webp/arm/cwebp b/raycast/assets/webp/arm/cwebp new file mode 100755 index 0000000..e2555e4 Binary files /dev/null and b/raycast/assets/webp/arm/cwebp differ diff --git a/raycast/assets/webp/arm/dwebp b/raycast/assets/webp/arm/dwebp new file mode 100755 index 0000000..ecb4abf Binary files /dev/null and b/raycast/assets/webp/arm/dwebp differ diff --git a/raycast/assets/webp/x86/cwebp b/raycast/assets/webp/x86/cwebp new file mode 100755 index 0000000..9608eeb Binary files /dev/null and b/raycast/assets/webp/x86/cwebp differ diff --git a/raycast/assets/webp/x86/dwebp b/raycast/assets/webp/x86/dwebp new file mode 100755 index 0000000..608604a Binary files /dev/null and b/raycast/assets/webp/x86/dwebp differ diff --git a/raycast/metadata/sips-1.png b/raycast/metadata/sips-1.png new file mode 100644 index 0000000..fd5df52 Binary files /dev/null and b/raycast/metadata/sips-1.png differ diff --git a/raycast/metadata/sips-2.png b/raycast/metadata/sips-2.png new file mode 100644 index 0000000..af72caa Binary files /dev/null and b/raycast/metadata/sips-2.png differ diff --git a/raycast/metadata/sips-3.png b/raycast/metadata/sips-3.png new file mode 100644 index 0000000..8addd12 Binary files /dev/null and b/raycast/metadata/sips-3.png differ diff --git a/raycast/metadata/sips-4.png b/raycast/metadata/sips-4.png new file mode 100644 index 0000000..71cf409 Binary files /dev/null and b/raycast/metadata/sips-4.png differ diff --git a/raycast/package-lock.json b/raycast/package-lock.json new file mode 100644 index 0000000..36c57b7 --- /dev/null +++ b/raycast/package-lock.json @@ -0,0 +1,4347 @@ +{ + "name": "sips", + "version": "1.6.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "sips", + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@raycast/api": "^1.48.8", + "@raycast/utils": "^1.4.18", + "mathjs": "^11.8.2", + "run-applescript": "^6.1.0", + "svgo": "^3.0.2", + "tar": "^6.2.0" + }, + "devDependencies": { + "@types/node": "18.8.3", + "@types/react": "18.0.9", + "@types/tar": "^6.1.11", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "prettier": "^2.5.1", + "typescript": "^4.4.3" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@raycast/api": { + "version": "1.48.8", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.48.8.tgz", + "integrity": "sha512-Lfw1Fw+EiLv2Xo8EvDkZ6o6Z1eRx/sZ4SsHKYmZybNkRkxc2RHyJQ+4wDv9uraJH6KeAgnCiSjmbSZzMPBZV7Q==", + "hasInstallScript": true, + "dependencies": { + "@types/node": "18.8.3", + "@types/react": "18.0.9", + "react": "18.1.0", + "react-reconciler": "0.28.0" + }, + "bin": { + "ray": "bin/ray" + }, + "peerDependencies": { + "@types/node": "18.8.3", + "@types/react": "18.0.9" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@raycast/utils": { + "version": "1.4.18", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-1.4.18.tgz", + "integrity": "sha512-Zk+Y3hYuVTYkWapxrFRxKUUnD6rI0yCDLlkzoMeNvFSZpWANjdC7wXrSnlbYOuhY6IX7oOEYM09DIvaQL8coCw==", + "dependencies": { + "content-type": "^1.0.5", + "cross-fetch": "^3.1.5", + "dequal": "^2.0.3", + "media-typer": "^1.1.0", + "object-hash": "^3.0.0", + "signal-exit": "^3.0.7" + }, + "peerDependencies": { + "@raycast/api": ">=1.42.0" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.8.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz", + "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-ThA1WD8aDdVU4VLuyq5NEqriwXErF5gEIJeyT6gHBWU7JtSmW2a5qjNv3/vR82O20mW+1vhmeZJfBQPT3HCugg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "minipass": "^4.0.0" + } + }, + "node_modules/@types/tar/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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 + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mathjs": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.8.2.tgz", + "integrity": "sha512-ZePu0oDbM0vuFExikIMY/9syjo/jbgNbX6ti+iMdaALDuxciMCsXIslGDBEn7QCpCWYBiVCYmc0lsmk5bwHBdQ==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.1.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.28.0.tgz", + "integrity": "sha512-sGIHDOpgVjRYgsi8NgosDnbkDvvkYFFSF900ZUhUw0+lSBEA5n76TcKFaVkfYMIuYm+7W6mT8Q673DLBfuTxcQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.22.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.1.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-6.1.0.tgz", + "integrity": "sha512-4sbn5dfGD41izkksKNcYuhIIQduzNp0+kGM+GqhXcHr9TFIMffkBAcPFCiwP0K7T2h85JWSdTJTMbm6R5mG3SQ==", + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-function": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", + "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@raycast/api": { + "version": "1.48.8", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.48.8.tgz", + "integrity": "sha512-Lfw1Fw+EiLv2Xo8EvDkZ6o6Z1eRx/sZ4SsHKYmZybNkRkxc2RHyJQ+4wDv9uraJH6KeAgnCiSjmbSZzMPBZV7Q==", + "requires": { + "@types/node": "18.8.3", + "@types/react": "18.0.9", + "react": "18.1.0", + "react-reconciler": "0.28.0" + } + }, + "@raycast/utils": { + "version": "1.4.18", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-1.4.18.tgz", + "integrity": "sha512-Zk+Y3hYuVTYkWapxrFRxKUUnD6rI0yCDLlkzoMeNvFSZpWANjdC7wXrSnlbYOuhY6IX7oOEYM09DIvaQL8coCw==", + "requires": { + "content-type": "^1.0.5", + "cross-fetch": "^3.1.5", + "dequal": "^2.0.3", + "media-typer": "^1.1.0", + "object-hash": "^3.0.0", + "signal-exit": "^3.0.7" + } + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/node": { + "version": "18.8.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz", + "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==" + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-ThA1WD8aDdVU4VLuyq5NEqriwXErF5gEIJeyT6gHBWU7JtSmW2a5qjNv3/vR82O20mW+1vhmeZJfBQPT3HCugg==", + "dev": true, + "requires": { + "@types/node": "*", + "minipass": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true + } + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "requires": { + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + } + } + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mathjs": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.8.2.tgz", + "integrity": "sha512-ZePu0oDbM0vuFExikIMY/9syjo/jbgNbX6ti+iMdaALDuxciMCsXIslGDBEn7QCpCWYBiVCYmc0lsmk5bwHBdQ==", + "requires": { + "@babel/runtime": "^7.22.5", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.1.0" + } + }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-reconciler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.28.0.tgz", + "integrity": "sha512-sGIHDOpgVjRYgsi8NgosDnbkDvvkYFFSF900ZUhUw0+lSBEA5n76TcKFaVkfYMIuYm+7W6mT8Q673DLBfuTxcQ==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.22.0" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-applescript": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-6.1.0.tgz", + "integrity": "sha512-4sbn5dfGD41izkksKNcYuhIIQduzNp0+kGM+GqhXcHr9TFIMffkBAcPFCiwP0K7T2h85JWSdTJTMbm6R5mG3SQ==", + "requires": { + "execa": "^5.1.1" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "scheduler": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + } + }, + "table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typed-function": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", + "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==" + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/raycast/package.json b/raycast/package.json new file mode 100644 index 0000000..01052a4 --- /dev/null +++ b/raycast/package.json @@ -0,0 +1,381 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "thingtime-commander", + "title": "Thingtime Commander", + "description": "Next generation command runner for software", + "keywords": [ + "Lopu", + "Thingtime", + "Commander" + ], + "version": "1.7.0", + "icon": "command-icon.png", + "author": "nikolajfrey", + "contributors": [ + "arronhunt" + ], + "categories": [ + "Media", + "Productivity", + "Design Tools" + ], + "license": "MIT", + "preferences": [ + + { + "title": "Image Input", + "name": "inputMethod", + "description": "Where to obtain the image to modify.", + "type": "dropdown", + "data": [ + { + "title": "Selection In Finder", + "value": "Finder" + }, + { + "title": "Selection In Path Finder", + "value": "Path Finder" + }, + { + "title": "Clipboard", + "value": "Clipboard" + } + ], + "default": "Finder", + "required": false + }, + { + "title": "Image Output", + "name": "imageResultHandling", + "description": "How to handle the result of the image modification, i.e. where to save the modified image, or whether to copy it to the clipboard.", + "type": "dropdown", + "data": [ + { + "title": "Replace Original", + "value": "replaceOriginal" + }, + { + "title": "Save In Containing Folder", + "value": "saveInContainingFolder" + }, + { + "title": "Copy to Clipboard", + "value": "copyToClipboard" + }, + { + "title": "Open in Preview", + "value": "openInPreview" + }, + { + "title": "Save To Downloads", + "value": "saveToDownloads" + }, + { + "title": "Save to Desktop", + "value": "saveToDesktop" + } + ], + "default": "replaceOriginal", + "required": false + }, + { + "title": "cwebp Lossless", + "name": "cwebpLossless", + "description": "Whether to use lossless conversion", + "label": "Lossless", + "type": "checkbox", + "default": true, + "required": false + } + + ], + "commands": [ + { + "name": "commander", + "title": "Commander", + "description": "Open Thingtime Commander", + "mode": "view", + "fallbackText": "test2", + "keywords": [ + "commander", + "thingtime", + "ai" + ], + "preferences": [ + { + "name": "name", + "required": false, + "type": "textfield", + "key": "name", + "title": "Name of Command to run", + "description": "The name of the command/function to run", + "defaultValue": "Menu" + } + ] + }, + { + "name": "commanderOpenNewFinderWindow", + "title": "Open New Finder Window", + "description": "Open new finder window", + "mode": "no-view", + "fallbackText": "test2", + "keywords": [ + "commander", + "thingtime", + "ai" + ], + "arguments": [ + { + "placeholder": "Path", + "name": "path", + "type": "text", + "description": "The path to open the new finder window at", + "required": false + } + ], + "preferences": [ + { + "name": "name", + "required": false, + "type": "textfield", + "key": "name", + "title": "Name of Command to run", + "description": "The name of the command/function to run", + "defaultValue": "openNewFinderWindow" + } + ] + }, + { + "name": "commanderMp3ToMp4", + "title": "mp3 to mp4", + "description": "Converts an mp4 into an mp3 using ffmpeg at 320Kbps", + "mode": "view", + "fallbackText": "test2", + "keywords": [ + "commander", + "thingtime", + "ai" + ], + "preferences": [ + { + "name": "name", + "required": false, + "type": "textfield", + "key": "name", + "title": "Name of Command to run", + "description": "The name of the command/function to run", + "defaultValue": "mp4ToMp3" + } + ] + }, + + { + "name": "convert", + "title": "Convert Images", + "description": "Convert the selected images to the specified format", + "mode": "view", + "preferences": [ + { + "title": "Enabled Formats", + "label": "ASTC", + "name": "showASTC", + "description": "Whether to show ASTC as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "BMP", + "name": "showBMP", + "description": "Whether to show BMP as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "DDS", + "name": "showDDS", + "description": "Whether to show DDS as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "EXR", + "name": "showEXR", + "description": "Whether to show EXR as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "GIF", + "name": "showGIF", + "description": "Whether to show GIF as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "HEIC", + "name": "showHEIC", + "description": "Whether to show HEIC as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "HEICS", + "name": "showHEICS", + "description": "Whether to show HEICS as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "ICNS", + "name": "showICNS", + "description": "Whether to show ICNS as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "ICO", + "name": "showICO", + "description": "Whether to show ICO as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "JPEG", + "name": "showJPEG", + "description": "Whether to show JPEG as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "JP2", + "name": "showJP2", + "description": "Whether to show JP2 as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "KTX", + "name": "showKTX", + "description": "Whether to show KTX as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "PBM", + "name": "showPBM", + "description": "Whether to show PBM as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "PDF", + "name": "showPDF", + "description": "Whether to show PDF as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "PNG", + "name": "showPNG", + "description": "Whether to show PNG as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "PSD", + "name": "showPSD", + "description": "Whether to show PSD as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "PVR", + "name": "showPVR", + "description": "Whether to show PVR as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "TGA", + "name": "showTGA", + "description": "Whether to show TGA as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "TIFF", + "name": "showTIFF", + "description": "Whether to show TIFF as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "WEBP", + "name": "showWEBP", + "description": "Whether to show WEBP as a conversion option", + "type": "checkbox", + "default": true, + "required": false + }, + { + "label": "SVG", + "name": "showSVG", + "description": "Whether to show SVG as a conversion option", + "type": "checkbox", + "default": true, + "required": false + } + ], + "keywords": [ + "change", + "transform" + ] + } + + ], + "dependencies": { + "@raycast/api": "^1.48.8", + "@raycast/utils": "^1.4.18", + "mathjs": "^11.8.2", + "run-applescript": "^6.1.0", + "svgo": "^3.0.2", + "tar": "^6.2.0" + }, + "devDependencies": { + "@types/node": "18.8.3", + "@types/react": "18.0.9", + "@types/tar": "^6.1.11", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "prettier": "^2.5.1", + "typescript": "^4.4.3" + }, + "scripts": { + "build": "ray build -e dist", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "publish": "ray publish" + } +} diff --git a/raycast/src/apply-filter.tsx b/raycast/src/apply-filter.tsx new file mode 100644 index 0000000..fa67811 --- /dev/null +++ b/raycast/src/apply-filter.tsx @@ -0,0 +1,76 @@ +/** + * @file apply-filter.tsx + * + * @summary Raycast command to apply filters on selected images. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:52:33 + * Last modified : 2023-07-06 15:47:50 + */ + +import { Action, ActionPanel, Grid } from "@raycast/api"; + +import applyFilter from "./operations/filterOperation"; +import { filters, getFilterThumbnail } from "./utilities/filters"; +import { Filter } from "./utilities/types"; +import { cleanup, getSelectedImages } from "./utilities/utils"; +import { useState } from "react"; +import runOperation from "./operations/runOperation"; + +export default function Command() { + const [selectedFilter, setSelectedFilter] = useState(); + const [preview, setPreview] = useState(""); + + const gridItems = filters.map((filter) => { + const isSelected = selectedFilter?.name === filter.name; + const itemContent = { source: isSelected ? (preview == "" ? filter.thumbnail : preview) : filter.thumbnail }; + return ( + + { + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => applyFilter(selectedImages, filter), + selectedImages, + inProgressMessage: "Filtering in progress...", + successMessage: "Applied filter to", + failureMessage: "Failed to apply filter to", + }); + }} + /> + + } + /> + ); + }); + + return ( + { + const filter = filters.find((filter) => filter.name === id); + if (filter && filter.name !== selectedFilter?.name) { + setPreview(""); + setSelectedFilter(filter); + const selection = await getSelectedImages(); + if (selection.length > 0 && selection[0].trim() !== "") { + const preview = getFilterThumbnail(filter, selection[0]); + setPreview(preview); + } + await cleanup(); + } + }} + > + {gridItems} + + ); +} diff --git a/raycast/src/commander.tsx b/raycast/src/commander.tsx new file mode 100644 index 0000000..fa1dcd4 --- /dev/null +++ b/raycast/src/commander.tsx @@ -0,0 +1,91 @@ +/** + * @file convert.tsx + * + * @summary Raycast command to convert selected images between various formats. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:53:25 + * Last modified : 2023-07-06 15:47:53 + */ + +import { Action, ActionPanel, getPreferenceValues, showToast, Toast, Icon, List, openCommandPreferences } from "@raycast/api"; + +import any from "./operations/anyOperation"; +import { getSelectedFiles, getSelectedImages } from "./utilities/utils"; +import { ConvertPreferences, ExtensionPreferences } from "./utilities/preferences"; +import runOperation from "./operations/runOperation"; +import { openNewFinderWindow } from "./commands/openNewFinderWindow"; +import { execSync } from "child_process"; + +/** + * All supported image formats for conversion. + */ + +const formats = [ + "MP3", + "MP4", +] + +export default async function Command(props: any) { + + const preferences = getPreferenceValues() + + if (preferences.name === 'openNewFinderWindow') { + + openNewFinderWindow(props) + + // show toast of done + await showToast({ title: "Done", style: Toast.Style.Success }); + + // use echo "tell application \"System Events\" to key code 53" | osascript to close raycast + execSync('osascript -e \'tell application "System Events" to key code 53\'') + + return + + } + console.log('nik process.argv', process.argv) + console.log('nik process.cwd()', process.cwd()) + return ( + + + await openCommandPreferences()} + shortcut={{ modifiers: ["cmd", "shift"], key: "," }} + /> + + } + /> + {formats.map((format) => { + return ( + + { + const selectedFiles = await getSelectedFiles(); + await runOperation({ + operation: () => any(selectedFiles, format), + selectedImages: selectedFiles, + inProgressMessage: "Conversion in progress...", + successMessage: "Converted", + failureMessage: "Failed to convert", + }); + }} + /> + + } + /> + ); + })} + + ); +} diff --git a/raycast/src/commanderMp3ToMp4.tsx b/raycast/src/commanderMp3ToMp4.tsx new file mode 120000 index 0000000..85dbfb1 --- /dev/null +++ b/raycast/src/commanderMp3ToMp4.tsx @@ -0,0 +1 @@ +commander.tsx \ No newline at end of file diff --git a/raycast/src/commanderOpenNewFinderWindow.tsx b/raycast/src/commanderOpenNewFinderWindow.tsx new file mode 120000 index 0000000..85dbfb1 --- /dev/null +++ b/raycast/src/commanderOpenNewFinderWindow.tsx @@ -0,0 +1 @@ +commander.tsx \ No newline at end of file diff --git a/raycast/src/commands/openNewFinderWindow.tsx b/raycast/src/commands/openNewFinderWindow.tsx new file mode 100644 index 0000000..44c1d92 --- /dev/null +++ b/raycast/src/commands/openNewFinderWindow.tsx @@ -0,0 +1,17 @@ +import { exec, execSync } from "child_process"; + + +export const openNewFinderWindow = async (props:any) => { + + + // open a new finder window in the current mac desktop + // execSync(`open -a Finder ${process.cwd()}`); + + // move newly opened finder window to currently focused mac desktop + // execSync(`osascript -e 'tell application "Finder" to set the bounds of the first window to {0, 0, 1440, 900}'`); + + execSync(`osascript -e 'tell application "Finder" to make new Finder window'`); + + return true; + +} \ No newline at end of file diff --git a/raycast/src/components/ImageGeneratorActionPanel.tsx b/raycast/src/components/ImageGeneratorActionPanel.tsx new file mode 100644 index 0000000..9048abb --- /dev/null +++ b/raycast/src/components/ImageGeneratorActionPanel.tsx @@ -0,0 +1,90 @@ +/** + * @file components/ImageGeneratorActionPanel.tsx + * + * @summary Action panel for image generator grid items. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 11:45:04 + * Last modified : 2023-07-06 16:47:05 + */ + +import os from "os"; +import path from "path"; + +import { Action, ActionPanel, Icon, showToast, Toast } from "@raycast/api"; + +import { Generator } from "../utilities/types"; +import { cleanup, getDestinationPaths, moveImageResultsToFinalDestination, showErrorToast } from "../utilities/utils"; + +/** + * Action panel for image generators displayed in the main grid. + * + * @param props.generator The generator to use. + * @param props.width The width of the generated image. + * @param props.height The height of the generated image. + * @param props.options The options to pass to the generator. + * @param props.filename The filename to use for the generated image. + * @param props.objectType The type of object being generated. + * @param props.setColors The function to update the colors and trigger a re-render. + * @returns The action panel for the generator. + */ +export default function ImageGeneratorActionPanel(props: { + generator: Generator; + width: number; + height: number; + preview: string; + options: { [key: string]: unknown }; + objectType: string; + regeneratePreviews: () => void; +}) { + const { generator, width, height, preview, options, objectType, regeneratePreviews } = props; + return ( + + { + const destinations = getDestinationPaths( + [path.join(os.tmpdir(), `${objectType.replaceAll(" ", "_").toLowerCase()}.png`)], + true + ); + const toast = await showToast({ title: `Creating ${objectType}...`, style: Toast.Style.Animated }); + try { + await generator.applyMethod(destinations[0], generator.CIFilterName, width, height, options); + await moveImageResultsToFinalDestination(destinations); + toast.title = `Created ${objectType}`; + toast.style = Toast.Style.Success; + } catch (error) { + await showErrorToast(`Failed To Create ${objectType}`, error as Error, toast); + } finally { + cleanup(); + } + }} + /> + + + + ` }} + /> + ` }} + /> + + + + ); +} diff --git a/raycast/src/components/ImagePatternGrid.tsx b/raycast/src/components/ImagePatternGrid.tsx new file mode 100644 index 0000000..1112715 --- /dev/null +++ b/raycast/src/components/ImagePatternGrid.tsx @@ -0,0 +1,375 @@ +/** + * @file components/ImagePatternGrid.tsx + * + * @summary Grid view for selecting an image pattern to generate. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 11:44:03 + * Last modified : 2023-07-06 14:52:12 + */ + +import { useEffect, useState } from "react"; + +import { Grid } from "@raycast/api"; + +import { + generatePreview, + generators, + getCheckerboardOptions, + getLenticularHaloOptions, + getLinearGradientOptions, + getRadialGradientOptions, + getSolidColorOptions, + getStarShineOptions, + getStripeOptions, + getSunbeamsOptions, +} from "../utilities/generators"; +import { GeneratorOptions } from "../utilities/types"; +import ImageGeneratorActionPanel from "./ImageGeneratorActionPanel"; + +/** + * A grid of image patterns used to generate full-size renders. + * + * @param props.width The width of the image to generate. + * @param props.height The height of the image to generate. + * @returns A grid component. + */ +export default function ImagePatternGrid(props: { width: number; height: number }) { + const { width, height } = props; + + const [loading, setLoading] = useState(true); + + const [checkerboardItems, setCheckerboardItems] = useState<[string, GeneratorOptions][]>(Array(5).fill(["", {}])); + const [stripeItems, setStripeItems] = useState<[string, GeneratorOptions][]>(Array(5).fill(["", {}])); + const [solidColorItems, setSolidColorItems] = useState<[string, string, GeneratorOptions][]>( + Array(10).fill(["", "", {}]) + ); + const [linearGradientItems, setLinearGradientItems] = useState<[string, GeneratorOptions][]>(Array(5).fill(["", {}])); + const [radialGradientItems, setRadialGradientItems] = useState<[string, GeneratorOptions][]>(Array(5).fill(["", {}])); + const [randomItem, setRandomItem] = useState(""); + const [starShineItem, setStarShineItem] = useState<[string, GeneratorOptions]>(["", {}]); + const [lenticularHaloItem, setLenticularHaloItem] = useState<[string, GeneratorOptions]>(["", {}]); + const [sunbeamsItem, setSunbeamsItem] = useState<[string, GeneratorOptions]>(["", {}]); + + /** + * Regenerates the colors used in the image patterns. + * + * @returns An object containing the new color values. + */ + const regenerateColors = () => { + const redValues = Array(10) + .fill(0) + .map(() => Math.floor(Math.random() * 256)); + const greenValues = Array(10) + .fill(0) + .map(() => Math.floor(Math.random() * 256)); + const blueValues = Array(10) + .fill(0) + .map(() => Math.floor(Math.random() * 256)); + const alphaValues = Array(10) + .fill(0) + .map(() => Math.floor(Math.random() * 256)); + return { redValues, greenValues, blueValues, alphaValues }; + }; + + /** + * Regenerates the pattern preview images. + */ + const regeneratePreviews = async () => { + setLoading(true); + const { redValues, greenValues, blueValues, alphaValues } = regenerateColors(); + Promise.all([ + Promise.all( + getCheckerboardOptions(redValues, greenValues, blueValues, alphaValues).map(async (options, index) => { + const content = await generatePreview(generators.Checkerboard.CIFilterName, options); + setCheckerboardItems((oldItems) => { + const newItems = [...oldItems]; + newItems[index] = [content, options]; + return newItems; + }); + }) + ), + + Promise.all( + getStripeOptions(redValues, greenValues, blueValues, alphaValues).map(async (options, index) => { + const content = await generatePreview(generators.Stripes.CIFilterName, options); + setStripeItems((oldItems) => { + const newItems = [...oldItems]; + newItems[index] = [content, options]; + return newItems; + }); + }) + ), + + Promise.all( + getSolidColorOptions(redValues, greenValues, blueValues).map(async (options, index) => { + const content = await generatePreview(generators.ConstantColor.CIFilterName, options); + setSolidColorItems((oldItems) => { + const newItems = [...oldItems]; + newItems[index] = [ + content, + `#${redValues[index].toString(16).padEnd(2, "0")}${greenValues[index] + .toString(16) + .padEnd(2, "0")}${blueValues[index].toString(16).padEnd(2, "0")}`, + options, + ]; + return newItems; + }); + }) + ), + + Promise.all( + getLinearGradientOptions(redValues, greenValues, blueValues, alphaValues).map(async (options, index) => { + const content = await generatePreview(generators.LinearGradient.CIFilterName, options); + setLinearGradientItems((oldItems) => { + const newItems = [...oldItems]; + newItems[index] = [content, options]; + return newItems; + }); + }) + ), + + Promise.all( + getRadialGradientOptions(redValues, greenValues, blueValues, alphaValues).map(async (options, index) => { + const content = await generatePreview(generators.RadialGradient.CIFilterName, options); + setRadialGradientItems((oldItems) => { + const newItems = [...oldItems]; + newItems[index] = [content, options]; + return newItems; + }); + }) + ), + + Promise.resolve( + (async () => { + const content = await generatePreview(generators.Random.CIFilterName, {}); + setRandomItem(content); + })() + ), + + Promise.resolve( + (async () => { + const options = getStarShineOptions(redValues, greenValues, blueValues, alphaValues); + const content = await generatePreview(generators.StarShine.CIFilterName, options); + setStarShineItem([content, options]); + })() + ), + + Promise.resolve( + (async () => { + const options = getLenticularHaloOptions(redValues, greenValues, blueValues, alphaValues); + const content = await generatePreview(generators.LenticularHalo.CIFilterName, options); + setLenticularHaloItem([content, options]); + })() + ), + + Promise.resolve( + (async () => { + const options = getSunbeamsOptions(redValues, greenValues, blueValues, alphaValues); + const content = await generatePreview(generators.Sunbeams.CIFilterName, options); + setSunbeamsItem([content, options]); + })() + ), + ]).then(() => setLoading(false)); + }; + + useEffect(() => { + regeneratePreviews(); + }, []); + + const checkerboardPreviews = checkerboardItems.map(([preview, options], index: number) => ( + + } + /> + )); + + const stripePreviews = stripeItems.map(([preview, options], index: number) => ( + + } + /> + )); + + const solidColorPreviews = solidColorItems.map(([preview, title, options], index: number) => { + return ( + + } + /> + ); + }); + + const linearGradientPreviews = linearGradientItems.map(([preview, options], index: number) => ( + + } + /> + )); + + const radialGradientPreviews = radialGradientItems.map(([preview, options], index: number) => ( + + } + /> + )); + + const randomPreview = ( + + } + /> + ); + + const starShinePreview = ( + + } + /> + ); + + const lenticularHaloPreview = ( + + } + /> + ); + + const sunbeamsPreview = ( + + } + /> + ); + + return ( + + + {checkerboardPreviews} + {stripePreviews} + + + {solidColorPreviews} + + + {linearGradientPreviews} + {radialGradientPreviews} + + + + {lenticularHaloPreview} + {randomPreview} + {starShinePreview} + {sunbeamsPreview} + + + ); +} diff --git a/raycast/src/components/SizeSelectionActionPanel.tsx b/raycast/src/components/SizeSelectionActionPanel.tsx new file mode 100644 index 0000000..36d09ec --- /dev/null +++ b/raycast/src/components/SizeSelectionActionPanel.tsx @@ -0,0 +1,88 @@ +/** + * @file components/SizeSelectionActionPanel.tsx + * + * @summary Action panel for image size selection grid items. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 16:46:33 + * Last modified : 2023-07-06 16:46:49 + */ + +import os from "os"; +import path from "path"; + +import { Action, ActionPanel, Clipboard, Icon, showHUD, showToast, Toast } from "@raycast/api"; + +import { generatePlaceholder } from "../utilities/generators"; +import { cleanup, getDestinationPaths, moveImageResultsToFinalDestination, showErrorToast } from "../utilities/utils"; +import ImagePatternGrid from "./ImagePatternGrid"; + +/** + * Action panel for the image size selection grid. + * + * @param props.width The width of the generated image. + * @param props.height The height of the generated image. + * @returns An action panel component. + */ +export default function SizeSelectionActionPanel(props: { width: number; height: number }) { + const { width, height } = props; + return ( + + } + /> + { + const destinations = getDestinationPaths([path.join(os.tmpdir(), `${width}x${height}.png`)], true); + const toast = await showToast({ title: "Creating Placeholder...", style: Toast.Style.Animated }); + try { + await generatePlaceholder(width, height, destinations[0]); + await moveImageResultsToFinalDestination(destinations); + toast.title = `Created Placeholder`; + toast.style = Toast.Style.Success; + } catch (error) { + await showErrorToast(`Failed To Create Placeholder`, error as Error, toast); + } finally { + cleanup(); + } + }} + /> + + { + const dataURL = await generatePlaceholder(width, height); + await Clipboard.paste({ html: `` }); + await showHUD("Pasted Placeholder Image"); + }} + /> + { + const dataURL = await generatePlaceholder(width, height); + await Clipboard.copy({ html: `` }); + await showHUD("Copied Placeholder Image"); + }} + /> + { + const dataURL = await generatePlaceholder(width, height); + await Clipboard.copy(dataURL); + await showHUD("Copied Placeholder Data URL"); + }} + /> + + + ); +} diff --git a/raycast/src/convert.tsx b/raycast/src/convert.tsx new file mode 100644 index 0000000..ce97f59 --- /dev/null +++ b/raycast/src/convert.tsx @@ -0,0 +1,92 @@ +/** + * @file convert.tsx + * + * @summary Raycast command to convert selected images between various formats. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:53:25 + * Last modified : 2023-07-06 15:47:53 + */ + +import { Action, ActionPanel, getPreferenceValues, Icon, List, openCommandPreferences } from "@raycast/api"; + +import convert from "./operations/convertOperation"; +import { getSelectedImages } from "./utilities/utils"; +import { ConvertPreferences, ExtensionPreferences } from "./utilities/preferences"; +import runOperation from "./operations/runOperation"; + +/** + * All supported image formats for conversion. + */ +const FORMATS = [ + "ASTC", + "BMP", + "DDS", + "EXR", + "GIF", + "HEIC", + "HEICS", + "ICNS", + "ICO", + "JPEG", + "JP2", + "KTX", + "PBM", + "PDF", + "PNG", + "PSD", + "PVR", + "TGA", + "TIFF", + "WEBP", + "SVG", +]; + +export default function Command() { + const preferences = getPreferenceValues(); + const enabledFormats = FORMATS.filter((format) => preferences[`show${format}`]); + + return ( + + + await openCommandPreferences()} + shortcut={{ modifiers: ["cmd", "shift"], key: "," }} + /> + + } + /> + {enabledFormats.map((format) => { + return ( + + { + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => convert(selectedImages, format), + selectedImages, + inProgressMessage: "Conversion in progress...", + successMessage: "Converted", + failureMessage: "Failed to convert", + }); + }} + /> + + } + /> + ); + })} + + ); +} diff --git a/raycast/src/create-image.tsx b/raycast/src/create-image.tsx new file mode 100644 index 0000000..4eb4f64 --- /dev/null +++ b/raycast/src/create-image.tsx @@ -0,0 +1,90 @@ +/** + * @file create-image.tsx + * + * @summary Raycast command to create images with various patterns and dimensions. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:53:50 + * Last modified : 2023-07-06 16:48:08 + */ + +import { Color, Grid } from "@raycast/api"; + +import { standardDimensions } from "./utilities/generators"; +import SizeSelectionActionPanel from "./components/SizeSelectionActionPanel"; + +export default function Command() { + const squareOptions = standardDimensions.map((width) => + standardDimensions + .filter((height) => width == height) + .map((height) => { + return ( + } + /> + ); + }) + ); + + const wideOptions = standardDimensions.map((width) => + standardDimensions + .filter((height) => width / height > 4 / 3 && width / height < 15 / 3) + .map((height) => { + return ( + } + /> + ); + }) + ); + + const tallOptions = standardDimensions.map((width) => + standardDimensions + .filter((height) => height / width > 4 / 3 && height / width < 15 / 3) + .map((height) => { + return ( + } + /> + ); + }) + ); + + const extremeOptions = standardDimensions.map((width) => + standardDimensions + .filter((height) => height / width > 15 / 3 || width / height > 15 / 3) + .map((height) => { + return ( + } + /> + ); + }) + ); + + return ( + + {squareOptions} + {wideOptions} + {tallOptions} + {extremeOptions} + + ); +} diff --git a/raycast/src/flip-horizontally.ts b/raycast/src/flip-horizontally.ts new file mode 100644 index 0000000..51c0b78 --- /dev/null +++ b/raycast/src/flip-horizontally.ts @@ -0,0 +1,25 @@ +/** + * @file flip-horizontally.ts + * + * @summary Raycast command to flip selected images horizontally. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:54:33 + * Last modified : 2023-07-18 18:48:24 + */ + +import flip from "./operations/flipOperation"; +import { Direction } from "./utilities/enums"; +import { getSelectedImages } from "./utilities/utils"; +import runOperation from "./operations/runOperation"; + +export default async function Command() { + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => flip(selectedImages, Direction.HORIZONTAL), + selectedImages, + inProgressMessage: "Flipping in progress...", + successMessage: "Flipped", + failureMessage: "Failed to flip", + }); +} diff --git a/raycast/src/flip-vertically.ts b/raycast/src/flip-vertically.ts new file mode 100644 index 0000000..b4bbf0f --- /dev/null +++ b/raycast/src/flip-vertically.ts @@ -0,0 +1,25 @@ +/** + * @file flip-vertically.ts + * + * @summary Raycast command to flip selected images vertically. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:55:04 + * Last modified : 2023-07-18 18:48:29 + */ + +import flip from "./operations/flipOperation"; +import { Direction } from "./utilities/enums"; +import { getSelectedImages } from "./utilities/utils"; +import runOperation from "./operations/runOperation"; + +export default async function Command() { + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => flip(selectedImages, Direction.HORIZONTAL), + selectedImages, + inProgressMessage: "Flipping in progress...", + successMessage: "Flipped", + failureMessage: "Failed to flip", + }); +} diff --git a/raycast/src/operations/anyOperation.ts b/raycast/src/operations/anyOperation.ts new file mode 100644 index 0000000..f3494a0 --- /dev/null +++ b/raycast/src/operations/anyOperation.ts @@ -0,0 +1,114 @@ +/** + * @file operations/convertOperation.ts + * + * @summary Image conversion operation with support for basic image formats, SVGs, WebP, and PDFs. + * @author Stephen Kaplan + * + * Created at : 2023-07-dd 00:19:37 + * Last modified : 2024-01-27 13:31:19 + */ + +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import path from "path"; + +import { environment, getPreferenceValues } from "@raycast/api"; + +import { convertPDF, convertSVG, moveImageResultsToFinalDestination } from "../utilities/utils"; +import { ExtensionPreferences } from "../utilities/preferences"; +import { ImageResultHandling } from "../utilities/enums"; + +/** + * Converts images to the specified format, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to convert. + * @param desiredType The desired format to convert the images to. + * @returns A promise that resolves when the operation is complete. + */ +export default async function any(sourcePaths: string[], desiredType: string) { + const preferences = getPreferenceValues(); + + const resultPaths = []; + for (const item of sourcePaths) { + const pathComponents = item.split("."); + let newPath = pathComponents.slice(0, -1).join("") + "." + desiredType.toLowerCase(); + + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, `.${desiredType.toLowerCase()}`) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + console.log('nik desiredType', desiredType) + + if (desiredType === "MP3") { + // const platform = os.arch() === "arm64" ? "/arm" : "/x86"; + // execSync(`chmod +x ${environment.assetsPath}/webp${platform}/cwebp`); + // execSync(`${environment.assetsPath}/webp${platform}/cwebp ${preferences?.cwebpLossless ? '-lossless' : ''} "${item}" -o "${newPath}"`); + // use ${item} as input.mp4 and ${newPath} as output.mp3 + execSync(`ffmpeg -i "${item}" -vn -ar 44100 -ac 2 -ab 320k -f mp3 "${newPath}"`); + // resultPaths.push(newPath); + + } else if (desiredType === "WEBP") { + // Input Format -> WebP + // detect platform is arm or x86 + const platform = os.arch() === "arm64" ? "/arm" : "/x86"; + execSync(`chmod +x ${environment.assetsPath}/webp${platform}/cwebp`); + execSync(`${environment.assetsPath}/webp${platform}/cwebp ${preferences?.cwebpLossless ? '-lossless' : ''} "${item}" -o "${newPath}"`); + } else if (pathComponents.at(-1)?.toLowerCase() == "svg") { + // SVG -> NSBitmapImageRep -> Desired Format + convertSVG(desiredType, item, newPath); + } else if (desiredType == "SVG") { + const bmpPath = `${environment.supportPath}/tmp.bmp`; + execSync(`chmod +x ${environment.assetsPath}/potrace/potrace`); + if (pathComponents.at(-1)?.toLowerCase() == "webp") { + // WebP -> PNG -> BMP -> SVG + const pngPath = `${environment.supportPath}/tmp.png`; + execSync(`chmod +x ${environment.assetsPath}/webp/dwebp`); + execSync(`${environment.assetsPath}/webp/dwebp "${item}" -o "${pngPath}"`); + execSync( + `sips --setProperty format "bmp" "${pngPath}" --out "${bmpPath}" && ${environment.assetsPath}/potrace/potrace -s --tight -o "${newPath}" "${bmpPath}"; rm "${bmpPath}"; rm "${pngPath}"` + ); + } else { + // Input Format -> BMP -> SVG + execSync( + `sips --setProperty format "bmp" "${item}" --out "${bmpPath}" && ${environment.assetsPath}/potrace/potrace -s --tight -o "${newPath}" "${bmpPath}"; rm "${bmpPath}"` + ); + } + } else if (pathComponents.at(-1)?.toLowerCase() == "webp") { + // WebP -> PNG -> Desired Format + execSync(`chmod +x ${environment.assetsPath}/webp/dwebp`); + execSync(`${environment.assetsPath}/webp/dwebp "${item}" -o "${newPath}"`); + execSync(`sips --setProperty format ${desiredType.toLowerCase()} "${newPath}"`); + } else if (pathComponents.at(-1)?.toLowerCase() == "pdf") { + // PDF -> Desired Format + const itemName = path.basename(item); + const folderName = `${itemName?.substring(0, itemName.lastIndexOf("."))} ${desiredType}`; + const folderPath = path.join(newPath.split("/").slice(0, -1).join("/"), folderName); + execSync(`mkdir -p "${folderPath}"`); + convertPDF(desiredType, item, folderPath); + } else { + // General Input Format -> Desired Format + execSync(`sips --setProperty format ${desiredType.toLowerCase()} "${item}" --out "${newPath}"`); + } + + resultPaths.push(newPath); + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/convertOperation.ts b/raycast/src/operations/convertOperation.ts new file mode 100644 index 0000000..1881659 --- /dev/null +++ b/raycast/src/operations/convertOperation.ts @@ -0,0 +1,104 @@ +/** + * @file operations/convertOperation.ts + * + * @summary Image conversion operation with support for basic image formats, SVGs, WebP, and PDFs. + * @author Stephen Kaplan + * + * Created at : 2023-07-dd 00:19:37 + * Last modified : 2024-01-27 13:31:19 + */ + +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import path from "path"; + +import { environment, getPreferenceValues } from "@raycast/api"; + +import { convertPDF, convertSVG, moveImageResultsToFinalDestination } from "../utilities/utils"; +import { ExtensionPreferences } from "../utilities/preferences"; +import { ImageResultHandling } from "../utilities/enums"; + +/** + * Converts images to the specified format, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to convert. + * @param desiredType The desired format to convert the images to. + * @returns A promise that resolves when the operation is complete. + */ +export default async function convert(sourcePaths: string[], desiredType: string) { + const preferences = getPreferenceValues(); + + const resultPaths = []; + for (const item of sourcePaths) { + const pathComponents = item.split("."); + let newPath = pathComponents.slice(0, -1).join("") + "." + desiredType.toLowerCase(); + + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, `.${desiredType.toLowerCase()}`) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + if (desiredType === "WEBP") { + // Input Format -> WebP + // detect platform is arm or x86 + const platform = os.arch() === "arm64" ? "/arm" : "/x86"; + execSync(`chmod +x ${environment.assetsPath}/webp${platform}/cwebp`); + execSync(`${environment.assetsPath}/webp${platform}/cwebp ${preferences?.cwebpLossless ? '-lossless' : ''} "${item}" -o "${newPath}"`); + } else if (pathComponents.at(-1)?.toLowerCase() == "svg") { + // SVG -> NSBitmapImageRep -> Desired Format + convertSVG(desiredType, item, newPath); + } else if (desiredType == "SVG") { + const bmpPath = `${environment.supportPath}/tmp.bmp`; + execSync(`chmod +x ${environment.assetsPath}/potrace/potrace`); + if (pathComponents.at(-1)?.toLowerCase() == "webp") { + // WebP -> PNG -> BMP -> SVG + const pngPath = `${environment.supportPath}/tmp.png`; + execSync(`chmod +x ${environment.assetsPath}/webp/dwebp`); + execSync(`${environment.assetsPath}/webp/dwebp "${item}" -o "${pngPath}"`); + execSync( + `sips --setProperty format "bmp" "${pngPath}" --out "${bmpPath}" && ${environment.assetsPath}/potrace/potrace -s --tight -o "${newPath}" "${bmpPath}"; rm "${bmpPath}"; rm "${pngPath}"` + ); + } else { + // Input Format -> BMP -> SVG + execSync( + `sips --setProperty format "bmp" "${item}" --out "${bmpPath}" && ${environment.assetsPath}/potrace/potrace -s --tight -o "${newPath}" "${bmpPath}"; rm "${bmpPath}"` + ); + } + } else if (pathComponents.at(-1)?.toLowerCase() == "webp") { + // WebP -> PNG -> Desired Format + execSync(`chmod +x ${environment.assetsPath}/webp/dwebp`); + execSync(`${environment.assetsPath}/webp/dwebp "${item}" -o "${newPath}"`); + execSync(`sips --setProperty format ${desiredType.toLowerCase()} "${newPath}"`); + } else if (pathComponents.at(-1)?.toLowerCase() == "pdf") { + // PDF -> Desired Format + const itemName = path.basename(item); + const folderName = `${itemName?.substring(0, itemName.lastIndexOf("."))} ${desiredType}`; + const folderPath = path.join(newPath.split("/").slice(0, -1).join("/"), folderName); + execSync(`mkdir -p "${folderPath}"`); + convertPDF(desiredType, item, folderPath); + } else { + // General Input Format -> Desired Format + execSync(`sips --setProperty format ${desiredType.toLowerCase()} "${item}" --out "${newPath}"`); + } + + resultPaths.push(newPath); + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/filterOperation.ts b/raycast/src/operations/filterOperation.ts new file mode 100644 index 0000000..fa4809d --- /dev/null +++ b/raycast/src/operations/filterOperation.ts @@ -0,0 +1,69 @@ +/** + * @file operations/filterOperation.ts + * + * @summary Image filter operation with support for basic image formats, SVGs, WebP, and PDFs. + * @author Stephen Kaplan + * + * Created at : 2023-07-dd 00:32:49 + * Last modified : 2023-07-dd 00:32:49 + */ + +import fs from "fs"; +import os from "os"; +import path from "path"; + +import { getPreferenceValues } from "@raycast/api"; + +import { Filter } from "../utilities/types"; +import { moveImageResultsToFinalDestination } from "../utilities/utils"; +import { ExtensionPreferences } from "../utilities/preferences"; +import { ImageResultHandling } from "../utilities/enums"; + +/** + * Applies the specified filter to images, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to convert. + * @param filter The filter to apply to the images. + * @returns A promise that resolves when the operation is complete. + */ +export default async function applyFilter(sourcePaths: string[], filter: Filter) { + const preferences = getPreferenceValues(); + + const resultPaths = []; + for (const imageFilePath of sourcePaths) { + let newPath = path.join( + path.dirname(imageFilePath), + path.basename(imageFilePath, path.extname(imageFilePath)) + (imageFilePath.endsWith(".pdf") ? ".pdf" : ".png") + ); + + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + // Ensure that the file name is unique, unless the user wants to replace the original + if ( + preferences.imageResultHandling != ImageResultHandling.ReplaceOriginal && + os.tmpdir() != path.dirname(newPath) + ) { + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` ${iter}` + path.extname(newPath) + ); + iter++; + } + } + await filter.applyMethod(imageFilePath, newPath, filter.CIFilterName); + resultPaths.push(newPath); + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/flipOperation.ts b/raycast/src/operations/flipOperation.ts new file mode 100644 index 0000000..8ebd053 --- /dev/null +++ b/raycast/src/operations/flipOperation.ts @@ -0,0 +1,69 @@ +/** + * @file operations/flipOperation.ts + * + * @summary Image flipping operation with support for basic image formats, SVGs, WebP, and PDFs. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:16:12 + * Last modified : 2023-07-06 15:48:56 + */ + +import { execSync } from "child_process"; +import path from "path"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + flipPDF, + getDestinationPaths, + moveImageResultsToFinalDestination, +} from "../utilities/utils"; +import { Direction } from "../utilities/enums"; + +/** + * Flips images vertically or horizontally, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to flip. + * @param direction The direction in which to flip the images. + * @returns A promise that resolves when the operation is complete. + */ +export default async function flip(sourcePaths: string[], direction: Direction) { + const pathStrings = '"' + sourcePaths.join('" "') + '"'; + const newPaths = getDestinationPaths(sourcePaths); + const directionString = direction == Direction.HORIZONTAL ? "horizontal" : "vertical"; + + if ( + pathStrings.toLowerCase().includes("webp") || + pathStrings.toLowerCase().includes("svg") || + pathStrings.toLowerCase().includes("pdf") + ) { + // Special types present -- Handle each image individually + const resultPaths = []; + for (const imgPath of sourcePaths) { + if (imgPath.toLowerCase().endsWith("webp")) { + // Convert to PNG, flip and restore to WebP + resultPaths.push(await execSIPSCommandOnWebP(`sips --flip ${directionString}`, imgPath)); + } else if (imgPath.toLowerCase().endsWith("svg")) { + // Convert to PNG, flip, and restore to SVG + resultPaths.push(await execSIPSCommandOnSVG(`sips --flip ${directionString}`, imgPath)); + } else if (imgPath.toLowerCase().endsWith("pdf")) { + // Flip each page of PDF + resultPaths.push(flipPDF(imgPath, direction)); + } else { + // Image is not a special format, so just flip it using SIPS + const newPath = newPaths[sourcePaths.indexOf(imgPath)]; + resultPaths.push(newPath); + execSync(`sips --flip ${directionString} -o "${newPath}" "${imgPath}"`); + } + } + await moveImageResultsToFinalDestination(resultPaths); + } else { + // No special types -- Flip all images at once + const outputLocation = newPaths.length == 1 ? newPaths[0] : path.join(path.dirname(newPaths[0]), "flipped"); + + if (newPaths.length > 1) execSync(`mkdir -p "${outputLocation}"`); + + execSync(`sips --flip ${directionString} -o "${outputLocation}" ${pathStrings}`); + await moveImageResultsToFinalDestination(newPaths); + } +} diff --git a/raycast/src/operations/optimizeOperation.ts b/raycast/src/operations/optimizeOperation.ts new file mode 100644 index 0000000..b738db6 --- /dev/null +++ b/raycast/src/operations/optimizeOperation.ts @@ -0,0 +1,166 @@ +/** + * @file operations/optimizeOperation.ts + * + * @summary Image optimization operation with support for basic image formats, SVGs, and WebP. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:49:16 + * Last modified : 2023-07-06 14:51:57 + */ + +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import path from "path"; +import { runAppleScriptSync } from "run-applescript"; +import { optimize as svgoOptimize } from "svgo"; + +import { environment, getPreferenceValues } from "@raycast/api"; + +import { getDestinationPaths, moveImageResultsToFinalDestination } from "../utilities/utils"; +import { ExtensionPreferences } from "../utilities/preferences"; +import { ImageResultHandling } from "../utilities/enums"; + +/** + * Optimizes a JPEG image by applying lossy compression. + * + * @param jpegPath The path of the JPEG image to optimize. + * @param newPath The path to save the optimized JPEG image to. + * @param amount The amount of compression to apply to the JPEG image. + */ +const optimizeJPEG = (jpegPath: string, newPath: string, amount: number) => { + runAppleScriptSync(`use framework "Foundation" + + -- Load JPEG image from file + set jpegData to current application's NSData's alloc()'s initWithContentsOfFile:"${jpegPath}" + + -- Create bitmap image representation from image + set bitmapRep to current application's NSBitmapImageRep's imageRepWithData:jpegData + + -- Compress bitmap representation + set compressionFactor to ${1.0 - amount / 100.0} + set compressedData to bitmapRep's representationUsingType:(current application's NSBitmapImageFileTypeJPEG) |properties|:{NSImageCompressionFactor:compressionFactor} + + -- Save compressed data to file + set compressedFilePath to "${newPath}" + compressedData's writeToFile:compressedFilePath atomically:false`); +}; + +/** + * Optimizes a WebP image by converting it to a JPEG, optimizing the JPEG, and then converting it back to a WebP. + * + * @param webpPath The path of the WebP image to optimize. + * @param amount The amount of compression to apply to the JPEG image. + * @returns The path of the optimized WebP image. + */ +const optimizeWEBP = (webpPath: string, amount: number) => { + const preferences = getPreferenceValues(); + const jpegPath = `${environment.supportPath}/tmp.jpeg`; + + let newPath = webpPath.substring(0, webpPath.toLowerCase().lastIndexOf(".webp")) + " (Optimized).webp"; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + execSync(`chmod +x ${environment.assetsPath}/webp/cwebp`); + execSync(`chmod +x ${environment.assetsPath}/webp/dwebp`); + + execSync(`${environment.assetsPath}/webp/dwebp "${webpPath}" -o "${jpegPath}"`); + optimizeJPEG(jpegPath, newPath, amount); + execSync(`${environment.assetsPath}/webp/cwebp "${jpegPath}" -o "${newPath}"; rm "${jpegPath}"`); + return newPath; +}; + +/** + * Optimize SVG images using SVGO. + * + * @param svgPath The path of the SVG image to optimize. + * @returns The path of the optimized SVG image. + */ +const optimizeSVG = (svgPath: string) => { + const preferences = getPreferenceValues(); + + let newPath = svgPath.substring(0, svgPath.toLowerCase().lastIndexOf(".svg")) + " (Optimized).svg"; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + const data = fs.readFileSync(svgPath); + const result = svgoOptimize(data.toString(), { + path: newPath, + multipass: true, + }); + fs.writeFileSync(newPath, result.data); + return newPath; +}; + +/** + * Optimize images using format-specific strategies, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to optimize. + * @param amount The level of optimization to apply, from 0 to 100. + * @returns A promise that resolves when optimization is complete. + */ +export default async function optimize(sourcePaths: string[], amount: number) { + const newPaths = getDestinationPaths(sourcePaths); + + const resultPaths = []; + for (const imgPath of sourcePaths) { + if (imgPath.toLowerCase().endsWith("webp")) { + // Convert to JPEG, optimize, and restore to WebP + resultPaths.push(optimizeWEBP(imgPath, amount)); + } else if (imgPath.toLowerCase().endsWith("svg")) { + // Optimize SVG using SVGO + resultPaths.push(optimizeSVG(imgPath)); + } else if (imgPath.toLowerCase().endsWith("jpg") || imgPath.toLowerCase().endsWith("jpeg")) { + // Optimize JPEG images using NSBitmapImageRep compression + let newPath = newPaths[sourcePaths.indexOf(imgPath)]; + newPath = path.join(path.dirname(newPath), path.basename(newPath, path.extname(newPath)) + " (Optimized).jpeg"); + resultPaths.push(newPath); + optimizeJPEG(imgPath, newPath, amount); + } else { + // Optimize any other SIPS-compatible image type + const jpegPath = `${environment.supportPath}/tmp.jpeg`; + let newPath = newPaths[sourcePaths.indexOf(imgPath)]; + resultPaths.push(newPath); + newPath = path.join(path.dirname(newPath), path.basename(newPath, path.extname(newPath)) + " (Optimized).jpeg"); + resultPaths.push(newPath); + + optimizeJPEG(imgPath, jpegPath, amount); + execSync(`sips --setProperty format jpeg "${jpegPath}" --out "${newPath}"; rm "${jpegPath}"`); + } + } + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/padOperation.ts b/raycast/src/operations/padOperation.ts new file mode 100644 index 0000000..ad927cd --- /dev/null +++ b/raycast/src/operations/padOperation.ts @@ -0,0 +1,69 @@ +/** + * @file operations/padOperation.ts + * + * @summary Image padding operation with support for basic image formats, SVGs, and WebP. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:35:48 + * Last modified : 2023-07-06 14:51:59 + */ + +import { execSync } from "child_process"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + getDestinationPaths, + moveImageResultsToFinalDestination, +} from "../utilities/utils"; + +/** + * Adds padding of the specified width and color to images, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to pad. + * @param padding The width of the padding to add. + * @param color The color of the padding to add as a hex string with no leading #. + * @returns A promise that resolves when the operation is complete. + */ +export default async function pad(sourcePaths: string[], padding: number, color: string) { + const newPaths = getDestinationPaths(sourcePaths); + const resultPaths: string[] = []; + + for (const imagePath of sourcePaths) { + const dimensions = execSync(`sips -g pixelWidth -g pixelHeight "${imagePath}"`) + .toString() + .split(/(: |\n)/g); + const oldWidth = parseInt(dimensions[4]); + const oldHeight = parseInt(dimensions[8]); + + if (imagePath.toLowerCase().endsWith(".webp")) { + // Convert to PNG, apply padding, then restore to WebP + resultPaths.push( + await execSIPSCommandOnWebP( + `sips --padToHeightWidth ${oldHeight + padding} ${oldWidth + padding} --padColor ${color}`, + imagePath + ) + ); + } else if (imagePath.toLowerCase().endsWith(".svg")) { + // Convert to PNG, apply padding, then restore to SVG + resultPaths.push( + await execSIPSCommandOnSVG( + `sips --padToHeightWidth ${oldHeight + padding} ${oldWidth + padding} --padColor ${color}`, + imagePath + ) + ); + } else { + // Image is not a special format, so pad using SIPS + const newPath = newPaths[sourcePaths.indexOf(imagePath)]; + resultPaths.push(newPath); + + execSync( + `sips --padToHeightWidth ${oldHeight + padding} ${ + oldWidth + padding + } --padColor ${color} -o "${newPath}" "${imagePath}"` + ); + } + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/resizeOperation.ts b/raycast/src/operations/resizeOperation.ts new file mode 100644 index 0000000..469a6de --- /dev/null +++ b/raycast/src/operations/resizeOperation.ts @@ -0,0 +1,85 @@ +/** + * @file operations/resizeOperation.ts + * + * @summary Image resizing operation with support for basic image formats, SVGs, and WebP. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:31:23 + * Last modified : 2023-07-06 14:52:02 + */ + +import { execSync } from "child_process"; +import path from "path"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + getDestinationPaths, + moveImageResultsToFinalDestination, +} from "../utilities/utils"; + +/** + * Resizes images to the specified width and height, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to resize. + * @param width The width to resize the images to, or -1 if the width should be automatically calculated. + * @param height The height to resize the images to, or -1 if the height should be automatically calculated. + * @returns A promise that resolves when the operation is complete. + */ +export default async function resize(sourcePaths: string[], width: number, height: number) { + const pathStrings = '"' + sourcePaths.join('" "') + '"'; + const newPaths = getDestinationPaths(sourcePaths); + + if (pathStrings.toLocaleLowerCase().includes("webp") || pathStrings.toLocaleLowerCase().includes("svg")) { + // Special formats in selection -- Handle each image individually + const resultPaths = []; + for (const imgPath of sourcePaths) { + if (imgPath.toLowerCase().endsWith(".webp")) { + // Convert to PNG, rotate and restore to WebP + if (width != -1 && height == -1) { + resultPaths.push(await execSIPSCommandOnWebP(`sips --resampleWidth ${width}`, imgPath)); + } else if (width == -1 && height != -1) { + resultPaths.push(await execSIPSCommandOnWebP(`sips --resampleHeight ${height}`, imgPath)); + } else { + resultPaths.push(await execSIPSCommandOnWebP(`sips --resampleHeightWidth ${height} ${width}`, imgPath)); + } + } else if (imgPath.toLowerCase().endsWith(".svg")) { + // Convert to PNG, resize, and restore to WebP + if (width != -1 && height == -1) { + resultPaths.push(await execSIPSCommandOnSVG(`sips --resampleWidth ${width}`, imgPath)); + } else if (width == -1 && height != -1) { + resultPaths.push(await execSIPSCommandOnSVG(`sips --resampleHeight ${height}`, imgPath)); + } else { + resultPaths.push(await execSIPSCommandOnSVG(`sips --resampleHeightWidth ${height} ${width}`, imgPath)); + } + } else { + // Image is not a special format, so just rotate it using SIPS + const newPath = newPaths[sourcePaths.indexOf(imgPath)]; + resultPaths.push(newPath); + + if (width != -1 && height == -1) { + execSync(`sips --resampleWidth ${width} -o "${newPath}" "${imgPath}"`); + } else if (width == -1 && height != -1) { + execSync(`sips --resampleHeight ${height} -o "${newPath}" "${imgPath}"`); + } else { + execSync(`sips --resampleHeightWidth ${height} -o "${newPath}" ${width} "${imgPath}"`); + } + } + } + await moveImageResultsToFinalDestination(resultPaths); + } else { + // No special formats -- Run commands on all images at once + const outputLocation = newPaths.length == 1 ? newPaths[0] : path.join(path.dirname(newPaths[0]), "resized"); + + if (newPaths.length > 1) execSync(`mkdir -p "${outputLocation}"`); + + if (width != -1 && height == -1) { + execSync(`sips --resampleWidth ${width} -o "${outputLocation}" ${pathStrings}`); + } else if (width == -1 && height != -1) { + execSync(`sips --resampleHeight ${height} -o "${outputLocation}" ${pathStrings}`); + } else { + execSync(`sips --resampleHeightWidth ${height} ${width} -o "${outputLocation}" ${pathStrings}`); + } + await moveImageResultsToFinalDestination(newPaths); + } +} diff --git a/raycast/src/operations/rotateOperation.ts b/raycast/src/operations/rotateOperation.ts new file mode 100644 index 0000000..1cdd77a --- /dev/null +++ b/raycast/src/operations/rotateOperation.ts @@ -0,0 +1,83 @@ +/** + * @file operations/rotateOperation.ts + * + * @summary Image rotation operation with support for basic image formats, SVGs, WebP, and PDFs. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:24:24 + * Last modified : 2023-07-06 14:52:04 + */ + +import { execSync } from "child_process"; +import * as fs from "fs"; +import path from "path"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + getDestinationPaths, + moveImageResultsToFinalDestination, + rotatePDF, +} from "../utilities/utils"; + +/** + * Rotates images by the given amount of degrees, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to rotate. + * @param degrees The amount of degrees to rotate the images. + * @returns A promise that resolves when the operation is complete. + */ +export default async function rotate(sourcePaths: string[], degrees: number) { + const pathStrings = '"' + sourcePaths.join('" "') + '"'; + const newPaths = getDestinationPaths(sourcePaths); + + if ( + pathStrings.toLowerCase().includes("webp") || + pathStrings.toLowerCase().includes("svg") || + pathStrings.toLowerCase().includes("pdf") + ) { + // Special formats in selection -- Handle each image individually + const resultPaths = []; + for (const imgPath of sourcePaths) { + if (imgPath.toLowerCase().endsWith("webp")) { + // Convert to PNG, flip and restore to WebP + resultPaths.push(await execSIPSCommandOnWebP(`sips --rotate ${degrees}`, imgPath)); + } else if (imgPath.toLowerCase().endsWith("svg")) { + // Convert to PNG, rotate, and restore to SVG + resultPaths.push(await execSIPSCommandOnSVG(`sips --rotate ${degrees}`, imgPath)); + } else if (imgPath.toLowerCase().endsWith("pdf")) { + // Rotate each page of a PDF + resultPaths.push(rotatePDF(imgPath, degrees)); + } else { + // Image is not a special format, so just rotate it using SIPS + const newPath = newPaths[sourcePaths.indexOf(imgPath)]; + resultPaths.push(newPath); + execSync(`sips --rotate ${degrees} -o "${newPath}" "${imgPath}"`); + } + } + await moveImageResultsToFinalDestination(resultPaths); + } else { + // No special formats -- Flip all images at once + if (newPaths.length == 1) { + execSync(`sips --rotate ${degrees} -o "${newPaths[0]}" ${pathStrings}`); + } else { + let exportDir = path.join(path.dirname(newPaths[0]), "rotated"); + + let iter = 1; + while (fs.existsSync(exportDir)) { + exportDir = exportDir + `-${iter}`; + iter++; + } + + await fs.promises.mkdir(exportDir, { recursive: true }); + execSync(`sips --rotate ${degrees} -o "${exportDir}" ${pathStrings}`); + + sourcePaths.forEach((imgPath, index) => { + fs.renameSync(path.join(exportDir, path.basename(imgPath)), newPaths[index]); + }); + + await fs.promises.rm(exportDir, { recursive: true, force: true }); + } + await moveImageResultsToFinalDestination(newPaths); + } +} diff --git a/raycast/src/operations/runOperation.ts b/raycast/src/operations/runOperation.ts new file mode 100644 index 0000000..6466a94 --- /dev/null +++ b/raycast/src/operations/runOperation.ts @@ -0,0 +1,52 @@ +/** + * @file operations/runOperation.ts + * + * @summary Runs an operation on the selected images. + * @author Stephen Kaplan + * + * Created at : 2023-07-18 18:45:28 + * Last modified : 2023-07-18 18:46:08 + */ + +import { showToast, Toast } from "@raycast/api"; + +import { cleanup, showErrorToast } from "../utilities/utils"; + +/** + * Runs an operation on the selected images, displaying a toast while the operation is in progress. + * + * @param params.operation The operation to run. + * @param params.selectedImages The paths of the selected images. + * @param params.inProgressMessage The message to display while the operation is in progress. + * @param params.successMessage The message to display if the operation succeeds. + * @param params.failureMessage The message to display if the operation fails. + * @returns A promise that resolves when the operation is complete. + */ +export default async function runOperation(params: { + operation: () => Promise; + selectedImages: string[]; + inProgressMessage: string; + successMessage: string; + failureMessage: string; +}) { + if (params.selectedImages.length === 0 || (params.selectedImages.length === 1 && params.selectedImages[0] === "")) { + await showToast({ title: "No images selected", style: Toast.Style.Failure }); + return; + } + + const toast = await showToast({ title: params.inProgressMessage, style: Toast.Style.Animated }); + const pluralized = `image${params.selectedImages.length === 1 ? "" : "s"}`; + try { + await params.operation(); + toast.title = `${params.successMessage} ${params.selectedImages.length.toString()} ${pluralized}`; + toast.style = Toast.Style.Success; + } catch (error) { + await showErrorToast( + `${params.failureMessage} ${params.selectedImages.length.toString()} ${pluralized}`, + error as Error, + toast + ); + } finally { + await cleanup(); + } +} diff --git a/raycast/src/operations/scaleOperation.ts b/raycast/src/operations/scaleOperation.ts new file mode 100644 index 0000000..0d36208 --- /dev/null +++ b/raycast/src/operations/scaleOperation.ts @@ -0,0 +1,65 @@ +/** + * @file operations/scaleOperation.ts + * + * @summary Image scaling operation with support for basic image formats, SVGs, and WebP. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:05:46 + * Last modified : 2023-07-06 15:48:36 + */ + +import { execSync } from "child_process"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + getDestinationPaths, + moveImageResultsToFinalDestination, +} from "../utilities/utils"; + +/** + * Scales images by the given scale factor, storing the results according to the user's preferences. + * + * @param sourcePaths The paths of the images to scale. + * @param scaleFactor The factor by which to scale the images. + * @returns A promise that resolves when the operation is complete. + */ +export default async function scale(sourcePaths: string[], scaleFactor: number) { + const newPaths = getDestinationPaths(sourcePaths); + const resultPaths: string[] = []; + + for (const imagePath of sourcePaths) { + const dimensions = execSync(`sips -g pixelWidth -g pixelHeight "${imagePath}"`) + .toString() + .split(/(: |\n)/g); + const oldWidth = parseInt(dimensions[4]); + const oldHeight = parseInt(dimensions[8]); + + if (imagePath.toLowerCase().endsWith("webp")) { + // Convert to PNG, scale, the restore to WebP + resultPaths.push( + await execSIPSCommandOnWebP( + `sips --resampleHeightWidth ${oldHeight * scaleFactor} ${oldWidth * scaleFactor}`, + imagePath + ) + ); + } else if (imagePath.toLowerCase().endsWith("svg")) { + // Convert to PNG, scale, and restore to SVG + resultPaths.push( + await execSIPSCommandOnSVG( + `sips --resampleHeightWidth ${oldHeight * scaleFactor} ${oldWidth * scaleFactor}`, + imagePath + ) + ); + } else { + // File is a normal image type + const newPath = newPaths[sourcePaths.indexOf(imagePath)]; + resultPaths.push(newPath); + execSync( + `sips --resampleHeightWidth ${oldHeight * scaleFactor} ${oldWidth * scaleFactor} -o "${newPath}" "${imagePath}"` + ); + } + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/operations/stripEXIFOperation.ts b/raycast/src/operations/stripEXIFOperation.ts new file mode 100644 index 0000000..80bf4ed --- /dev/null +++ b/raycast/src/operations/stripEXIFOperation.ts @@ -0,0 +1,60 @@ +/** + * @file operations/padOperation.ts + * + * @summary Image sanitization operation with support for basic image formats, SVGs, and WebP. + * @author Stephen Kaplan + * + * Created at : 2023-07-05 23:35:48 + * Last modified : 2024-01-28 05:10:25 + */ + +import { execSync } from "child_process"; + +import { + execSIPSCommandOnSVG, + execSIPSCommandOnWebP, + getDestinationPaths, + moveImageResultsToFinalDestination, +} from "../utilities/utils"; +import { ExifToolLocation } from "../utilities/enums"; +import { environment } from "@raycast/api"; + +/** + * Strips EXIF data from the given images. + * + * @param sourcePaths The paths of the images to strip EXIF data from. + * @param exifToolLocation The location of the ExifTool binary. + * @returns A promise that resolves when the operation is complete. + */ +export default async function stripEXIF(sourcePaths: string[], exifToolLocation: ExifToolLocation) { + const newPaths = getDestinationPaths(sourcePaths); + const resultPaths: string[] = []; + + const exifCommand = + exifToolLocation === ExifToolLocation.ON_PATH + ? "exiftool" + : `"${environment.supportPath}/Image-ExifTool-12.74/exiftool"`; + + // Make sure ExifTool is executable + if (exifToolLocation === ExifToolLocation.SUPPORT_DIR) { + execSync(`chmod +x "${environment.supportPath}/Image-ExifTool-12.74/exiftool"`); + } + + for (const imagePath of sourcePaths) { + if (imagePath.toLowerCase().endsWith(".webp")) { + // Convert to PNG, remove EXIF, then restore to WebP + resultPaths.push(await execSIPSCommandOnWebP(`${exifCommand} -all= "${imagePath}"`, imagePath)); + } else if (imagePath.toLowerCase().endsWith(".svg")) { + // Convert to PNG, remove EXIF, then restore to SVG + resultPaths.push(await execSIPSCommandOnSVG(`${exifCommand} -all= "${imagePath}"`, imagePath)); + } else { + // Image is not a special format, so just strip EXIF data + const newPath = newPaths[sourcePaths.indexOf(imagePath)]; + resultPaths.push(newPath); + + execSync(`${exifCommand} -all= -o "${newPath}" "${imagePath}"`); + } + } + + await moveImageResultsToFinalDestination(resultPaths); +} diff --git a/raycast/src/optimize.ts b/raycast/src/optimize.ts new file mode 100644 index 0000000..82adf29 --- /dev/null +++ b/raycast/src/optimize.ts @@ -0,0 +1,37 @@ +/** + * @file optimize.ts + * + * @summary Raycast command to optimize selected images. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:55:16 + * Last modified : 2023-07-18 18:48:32 + */ + +import { showToast, Toast } from "@raycast/api"; + +import optimize from "./operations/optimizeOperation"; +import { getSelectedImages } from "./utilities/utils"; +import runOperation from "./operations/runOperation"; + +export default async function Command(props: { arguments: { optimizationFactor: string } }) { + const { optimizationFactor } = props.arguments; + + let optimizationValue = 100; + if (optimizationFactor != "") { + optimizationValue = parseFloat(optimizationFactor); + if (!optimizationValue) { + await showToast({ title: "Invalid optimization factor", style: Toast.Style.Failure }); + return; + } + } + + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => optimize(selectedImages, optimizationValue), + selectedImages, + inProgressMessage: "Optimization in progress...", + successMessage: "Optimized", + failureMessage: "Failed to optimize", + }); +} diff --git a/raycast/src/pad.ts b/raycast/src/pad.ts new file mode 100644 index 0000000..e0115d4 --- /dev/null +++ b/raycast/src/pad.ts @@ -0,0 +1,45 @@ +/** + * @file pad.ts + * + * @summary Raycast command to add padding to selected images. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:55:36 + * Last modified : 2023-07-18 18:48:38 + */ + +import { getPreferenceValues, showToast, Toast } from "@raycast/api"; + +import pad from "./operations/padOperation"; +import { getSelectedImages } from "./utilities/utils"; +import { PadPreferences } from "./utilities/preferences"; +import runOperation from "./operations/runOperation"; + +export default async function Command(props: { arguments: { amount: string; hexcolor: string } }) { + const { amount, hexcolor } = props.arguments; + const selectedImages = await getSelectedImages(); + const preferences = getPreferenceValues(); + + const padAmount = parseInt(amount); + if (isNaN(padAmount) || padAmount < 0) { + await showToast({ title: "Padding amount must be a positive integer", style: Toast.Style.Failure }); + return; + } + + let hexString = hexcolor || preferences.defaultPadColor; + if (hexString.startsWith("#")) { + hexString = hexString.substring(1); + } + if (!hexString.match(/[0-9A-Fa-f]{6}/)) { + await showToast({ title: "Invalid HEX Color", style: Toast.Style.Failure }); + return; + } + + await runOperation({ + operation: () => pad(selectedImages, padAmount, hexString), + selectedImages, + inProgressMessage: "Padding in progress...", + successMessage: "Padded", + failureMessage: "Failed to pad", + }); +} diff --git a/raycast/src/resize.ts b/raycast/src/resize.ts new file mode 100644 index 0000000..ff744c8 --- /dev/null +++ b/raycast/src/resize.ts @@ -0,0 +1,44 @@ +/** + * @file resize.ts + * + * @summary Raycast command to resize selected images to a specified width and/or height. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:55:58 + * Last modified : 2023-07-18 18:48:42 + */ + +import { showToast, Toast } from "@raycast/api"; + +import resize from "./operations/resizeOperation"; +import { getSelectedImages } from "./utilities/utils"; +import runOperation from "./operations/runOperation"; + +export default async function Command(props: { arguments: { width: string; height: string } }) { + const { width, height } = props.arguments; + + if (width == "" && height == "") { + await showToast({ title: "Must specify either width or height", style: Toast.Style.Failure }); + return; + } + + const widthInt = width == "" ? -1 : parseInt(width); + const heightInt = height == "" ? -1 : parseInt(height); + + if (isNaN(widthInt)) { + await showToast({ title: "Width must be an integer", style: Toast.Style.Failure }); + return; + } else if (isNaN(heightInt)) { + await showToast({ title: "Height must be an integer", style: Toast.Style.Failure }); + return; + } + + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => resize(selectedImages, widthInt, heightInt), + selectedImages, + inProgressMessage: "Resizing in progress...", + successMessage: "Resized", + failureMessage: "Failed to resize", + }); +} diff --git a/raycast/src/rotate.ts b/raycast/src/rotate.ts new file mode 100644 index 0000000..183258e --- /dev/null +++ b/raycast/src/rotate.ts @@ -0,0 +1,41 @@ +/** + * @file rotate.ts + * + * @summary Raycast command to rotate selected images by a specified number of degrees. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:56:15 + * Last modified : 2023-07-18 18:48:47 + */ + +import { getPreferenceValues, showToast, Toast } from "@raycast/api"; + +import rotate from "./operations/rotateOperation"; +import { getSelectedImages } from "./utilities/utils"; +import { RotatePreferences } from "./utilities/preferences"; +import { parser } from "mathjs"; +import runOperation from "./operations/runOperation"; + +export default async function Command(props: { arguments: { angle: string } }) { + const { angle } = props.arguments; + const preferences = getPreferenceValues(); + + let angleNumber = parseFloat(parser().evaluate(angle).toString()); + if (isNaN(angleNumber)) { + await showToast({ title: "Angle must be a number", style: Toast.Style.Failure }); + return; + } + + if (preferences.rotationUnit === "radians") { + angleNumber = angleNumber * (180 / Math.PI); + } + + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => rotate(selectedImages, angleNumber), + selectedImages, + inProgressMessage: "Rotation in progress...", + successMessage: "Rotated", + failureMessage: "Failed to rotate", + }); +} diff --git a/raycast/src/scale.ts b/raycast/src/scale.ts new file mode 100644 index 0000000..0582b4d --- /dev/null +++ b/raycast/src/scale.ts @@ -0,0 +1,34 @@ +/** + * @file scale.ts + * + * @summary Raycast command to scale selected images by a given factor. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:56:29 + * Last modified : 2023-07-18 18:48:52 + */ + +import { showToast, Toast } from "@raycast/api"; + +import scale from "./operations/scaleOperation"; +import { getSelectedImages } from "./utilities/utils"; +import runOperation from "./operations/runOperation"; + +export default async function Command(props: { arguments: { scaleFactor: string } }) { + const { scaleFactor } = props.arguments; + + const scaleNumber = parseFloat(scaleFactor); + if (isNaN(scaleNumber)) { + await showToast({ title: "Scale factor must be a number", style: Toast.Style.Failure }); + return; + } + + const selectedImages = await getSelectedImages(); + await runOperation({ + operation: () => scale(selectedImages, scaleNumber), + selectedImages, + inProgressMessage: "Scaling in progress...", + successMessage: "Scaled", + failureMessage: "Failed to scale", + }); +} diff --git a/raycast/src/strip-exif.ts b/raycast/src/strip-exif.ts new file mode 100644 index 0000000..040e8d2 --- /dev/null +++ b/raycast/src/strip-exif.ts @@ -0,0 +1,131 @@ +import { execSync } from "child_process"; +import * as https from "https"; +import * as tar from "tar"; +import * as fs from "fs"; +import * as crypto from "crypto"; + +import { confirmAlert, environment, LocalStorage, showToast, Toast } from "@raycast/api"; + +import runOperation from "./operations/runOperation"; +import stripEXIF from "./operations/stripEXIFOperation"; +import { ExifToolLocation } from "./utilities/enums"; +import { getSelectedImages } from "./utilities/utils"; +import path from "path"; + +/** + * Prompts the user to install ExifTool. If the user accepts, ExifTool is installed to the support directory. + */ +async function installExifTool() { + if ( + await confirmAlert({ + title: "Install ExifTool", + message: + "ExifTool is required to strip EXIF data. Would you like to install it now?\n\nThis will use 26.2 MB of disk space.", + primaryAction: { + title: "Install", + }, + }) + ) { + const toast = await showToast({ + title: "Installing ExifTool...", + style: Toast.Style.Animated, + }); + + const supportPath = environment.supportPath; + const tarURL = "https://exiftool.org/Image-ExifTool-12.74.tar.gz"; + const checksum = "aedb28b1427c53205ab261fa31ff3feda73e7f17a0c181453651680e5666c48a"; + + let waiting = true; + https.get(tarURL, async (response) => { + const tarName = "Image-ExifTool-12.74.tar.gz"; + const tarPath = path.join(supportPath, tarName); + const file = fs.createWriteStream(tarPath); + response.pipe(file); + + // Checksum verification + let valid = false; + const hash = crypto.createHash("sha256"); + response.on("data", (data) => hash.update(data)); + response.on("end", async () => { + const hex = hash.digest("hex"); + if (hex !== checksum) { + toast.title = "Checksum verification failed"; + toast.style = Toast.Style.Failure; + waiting = false; + return; + } + valid = true; + }); + + file.on("finish", async () => { + file.close(); + if (valid) { + // Extract the tarball + await tar.x({ file: `${supportPath}/Image-ExifTool-12.74.tar.gz`, cwd: supportPath }); + await LocalStorage.setItem("exifToolLocation", ExifToolLocation.SUPPORT_DIR); + waiting = false; + } + await fs.promises.unlink(tarPath); + toast.title = "Done!"; + toast.style = Toast.Style.Success; + }); + }); + + while (waiting) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } else { + await LocalStorage.removeItem("exifToolLocation"); + } +} + +/** + * Determines whether ExifTool is on the path. If not, prompts the user to install it. + */ +async function setExifToolLocation() { + // See if ExifTool is on the path + try { + execSync("exiftool -ver"); + await LocalStorage.setItem("exifToolLocation", ExifToolLocation.ON_PATH); + } catch (error) { + // If not, prompt the user to install it + await installExifTool(); + } +} + +/** + * Gets the location of ExifTool, either on the path or in the support directory. + * @returns The location of ExifTool, either on the path or in the support directory. + */ +async function getExifToolLocation() { + const initialLocation = await LocalStorage.getItem("exifToolLocation"); + if ( + initialLocation !== ExifToolLocation.ON_PATH && + (initialLocation !== ExifToolLocation.SUPPORT_DIR || + !fs.existsSync(`${environment.supportPath}/Image-ExifTool-12.74/exiftool`)) + ) { + await setExifToolLocation(); + } + return await LocalStorage.getItem("exifToolLocation"); +} + +export default async function Command() { + const selectedImages = await getSelectedImages(); + const exifToolLocation = await getExifToolLocation(); + if (!exifToolLocation) { + await showToast({ + title: "Command Cancelled", + message: "ExifTool is required to strip EXIF data.", + style: Toast.Style.Failure, + }); + return; + } + + await runOperation({ + operation: () => stripEXIF(selectedImages, exifToolLocation as ExifToolLocation), + selectedImages, + inProgressMessage: "Sanitizing...", + successMessage: "Sanitized", + failureMessage: "Failed to sanitize", + }); +} diff --git a/raycast/src/utilities/clipboard.ts b/raycast/src/utilities/clipboard.ts new file mode 100644 index 0000000..9c34fab --- /dev/null +++ b/raycast/src/utilities/clipboard.ts @@ -0,0 +1,148 @@ +/** + * @file utilities/clipboard.ts + * + * @summary Utilities for interacting with and getting images from the clipboard. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:50:26 + * Last modified : 2023-07-06 15:48:31 + */ + +import { runAppleScript } from "run-applescript"; + +/** + * Gets the images from the clipboard as paths to temporary PNG files. + * + * @returns A promise resolving to the list of image paths. + */ +export const getClipboardImages = async (): Promise => { + return runAppleScript(`use framework "AppKit" + use framework "PDFKit" + + set pb to current application's NSPasteboard's generalPasteboard() + set theItems to pb's readObjectsForClasses:({current application's NSURL, current application's NSImage, current application's NSAttributedString}) options:{} + + set theImages to {} + repeat with i from 0 to ((theItems's |count|()) - 1) + set theItem to (theItems's objectAtIndex:i) + if (theItem's |class|()) is current application's NSImage then + copy theItem to end of theImages + else if (theItem's |class|()) is current application's NSURL then + if (theItem's absoluteString() as text) ends with ".pdf" then + set theImage to (current application's PDFDocument's alloc()'s initWithURL:theItem) + else + set theImage to (current application's NSImage's alloc()'s initWithContentsOfURL:theItem) + end if + if theImage is not missing value then + copy theImage to end of theImages + end if + else if (theItem's |class|()) is current application's NSConcreteAttributedString then + repeat with i from 0 to ((theItem's |length|()) - 1) + set attrs to (theItem's attributesAtIndex:i longestEffectiveRange:(missing value) inRange:{i, (theItem's |length|()) - i}) + set theAttachment to (attrs's objectForKey:"NSAttachment") + if theAttachment is not missing value then + set cell to theAttachment's attachmentCell() + set theImage to cell's image() + copy theImage to end of theImages + end if + end repeat + end if + end repeat + + set tempDir to current application's NSTemporaryDirectory() as text + set filePaths to {} + repeat with i from 1 to count theImages + set theImage to item i of theImages + set theFile to tempDir & "clipboardImage_" & i + if theImage's |class|() is current application's PDFDocument then + set theFile to theFile & ".pdf" + (theImage's writeToFile:theFile) + else + set theFile to theFile & ".png" + set theTIFFData to theImage's TIFFRepresentation() + set theBitmap to (current application's NSBitmapImageRep's alloc()'s initWithData:theTIFFData) + set thePNGData to (theBitmap's representationUsingType:(current application's NSBitmapImageFileTypePNG) |properties|:(current application's NSDictionary's alloc()'s init())) + (thePNGData's writeToFile:theFile atomically:false) + end if + copy theFile to end of filePaths + end repeat + + return filePaths`); +}; + +/** + * Gets the files from the clipboard as paths to temporary files. + * + * @returns A promise resolving to the list of file paths. + */ +export const getClipboardFiles = async (): Promise => { + return runAppleScript(`use framework "AppKit" + use framework "PDFKit" + + set pb to current application's NSPasteboard's generalPasteboard() + set theItems to pb's readObjectsForClasses:({current application's NSURL}) options:{} + + set tempDir to current application's NSTemporaryDirectory() as text + set filePaths to {} + repeat with i from 0 to ((theItems's |count|()) - 1) + set theItem to (theItems's objectAtIndex:i) + if (theItem's |class|()) is current application's NSURL then + set theFile to tempDir & "clipboardFile_" & (i + 1) & "." & (theItem's pathExtension()) + -- Copy the file content to the temporary file + (current application's NSFileManager's defaultManager()'s copyItemAtURL:theItem toURL:(current application's |NSURL|'s fileURLWithPath:theFile) |error|:(missing value)) + copy theFile to end of filePaths + end if + end repeat + + return filePaths`); +}; + + +/** + * Copies the images at the given paths to the clipboard. Handles PDFs as well. + * + * @param filePaths The paths of the images to copy. + */ +export const copyImagesAtPathsToClipboard = async (filePaths: string | string[]) => { + const paths = Array.isArray(filePaths) ? filePaths : [filePaths]; + await runAppleScript(`use framework "Foundation" + use framework "PDFKit" + use scripting additions + + set thePasteboard to current application's NSPasteboard's generalPasteboard() + thePasteboard's clearContents() + + -- Handle PDFs separately + set pdfPaths to {"${paths.filter((p) => p.endsWith(".pdf")).join('", "')}"} + + set pdfItems to current application's NSMutableArray's alloc()'s init() + repeat with pdfPath in pdfPaths + if length of pdfPath is not 0 then + set pdfItem to current application's NSPasteboardItem's alloc()'s init() + set pdfData to current application's NSData's dataWithContentsOfFile:pdfPath + pdfItem's setData:pdfData forType:(current application's NSPasteboardTypePDF) + pdfItems's addObject:pdfItem + end if + end repeat + + if pdfItems's |count|() > 0 then + thePasteboard's writeObjects:pdfItems + end if + + -- Handle all other image types + set theFiles to {"${paths.join('", "')}"} + + set theImages to {} + repeat with theFile in theFiles + if length of theFile is not 0 then + set theImage to (current application's NSImage's alloc()'s initWithContentsOfFile:theFile) + if theImage is not missing value then + copy theImage to end of theImages + end if + end if + end repeat + + if (count theImages) > 0 then + thePasteboard's writeObjects:theImages + end if`); +}; diff --git a/raycast/src/utilities/enums.ts b/raycast/src/utilities/enums.ts new file mode 100644 index 0000000..9664643 --- /dev/null +++ b/raycast/src/utilities/enums.ts @@ -0,0 +1,37 @@ +/** + * @file utilities/enums.ts + * + * @summary Enumerations used throughout the extension. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 00:52:09 + * Last modified : 2023-07-06 15:48:29 + */ + +/** + * Directions for flipping images. + */ +export enum Direction { + HORIZONTAL = 0, + VERTICAL = 1, +} + +/** + * Strategy for handling the result of the image processing, i.e. where to save or display the result. + */ +export enum ImageResultHandling { + ReplaceOriginal = "replaceOriginal", + SaveInContainingFolder = "saveInContainingFolder", + CopyToClipboard = "copyToClipboard", + OpenInPreview = "openInPreview", + SaveToDownloads = "saveToDownloads", + SaveToDesktop = "saveToDesktop", +} + +/** + * The place to look for the ExifTool binary. + */ +export enum ExifToolLocation { + ON_PATH = "path", + SUPPORT_DIR = "support", +} diff --git a/raycast/src/utilities/filters.ts b/raycast/src/utilities/filters.ts new file mode 100644 index 0000000..c52e1f2 --- /dev/null +++ b/raycast/src/utilities/filters.ts @@ -0,0 +1,453 @@ +/** + * @file utilities/filters.ts + * + * @summary Helper functions and resources for applying filters to images and PDFs using Core Image and ASObjC. + * @author Stephen Kaplan + * + * Created at : 2023-07-dd 00:44:28 + * Last modified : 2023-07-dd 00:44:28 + */ + +import { runAppleScript, runAppleScriptSync } from "run-applescript"; + +import { Filter } from "./types"; + +/** + * First part of the ASObjC script that applies a filter to an image. Initializes the filter and sets its default values. Initializes the image or PDF document to apply the filter to. + * + * @param source The path to the image or PDF document to apply the filter to. + * @param destination The path to output the filtered image or PDF document. + * @param CIFilterName The name of the CIFilter to apply. + * @returns The beginning of an ASObjC script with filled-in parameters. + */ +const initializeFilterScript = (source: string, destination: string, CIFilterName: string) => { + return `use framework "Foundation" + use framework "Quartz" + use framework "PDFKit" + + set res to "" + set thePDF to missing value + applyFilter("${source}", "${destination}") + on applyFilter(sourcePath, destinationPath) + global thePDF + set repeatCount to 1 + if "${source}" ends with ".pdf" and "${destination}" is not "" then + set thePDF to current application's PDFDocument's alloc()'s initWithURL:(current application's |NSURL|'s fileURLWithPath:sourcePath) + set pageCount to thePDF's pageCount() + set repeatCount to pageCount + end if + + repeat with i from 1 to repeatCount + if repeatCount > 1 then + set thePage to thePDF's pageAtIndex:(i - 1) + set theData to thePage's dataRepresentation() + set theImage to current application's NSImage's alloc()'s initWithData:theData + else + set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:sourcePath + end if + + -- Set up the Filter + set filterName to "${CIFilterName}" + set theFilter to current application's CIFilter's filterWithName:filterName + theFilter's setDefaults()`; +}; + +/** + * Second part of the ASObjC script that applies a filter to an image. Applies the filter to the image, crops the result to the original image size, and calls the saveImage() handler. + */ +const baseFilterResultScript = `-- Get result & crop to original image size + set theBounds to current application's NSMakeRect(0, 0, theImage's |size|()'s width, theImage's |size|()'s height) + set uncroppedOutput to theFilter's valueForKey:(current application's kCIOutputImageKey) + set croppedOutput to uncroppedOutput's imageByCroppingToRect:(uncroppedOutput's extent()) + + -- Convert back to NSImage and save to file + set theRep to current application's NSCIImageRep's imageRepWithCIImage:croppedOutput + set theResult to current application's NSImage's alloc()'s initWithSize:(theRep's |size|()) + theResult's addRepresentation:theRep + saveImage(theResult, sourcePath, destinationPath, i)`; + +/** + * Third part of the ASObjC script that applies a filter to an image. Saves the filtered image to the destination path. Iteratively converts a PDF document to filtered images. + */ +const saveImageScript = `on saveImage(imageToSave, sourcePath, destinationPath, iter) + global thePDF + if destinationPath ends with ".pdf" then + -- Replaces the contents of a PDF page with the supplied NSImage + set newPage to current application's PDFPage's alloc()'s initWithImage:imageToSave + thePDF's removePageAtIndex:(iter - 1) + thePDF's insertPage:newPage atIndex:(iter - 1) + else + -- Saves an NSImage to the supplied file path + set theTIFFData to imageToSave's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theImageProperties to current application's NSDictionary's dictionaryWithObject:1 forKey:(current application's NSImageCompressionFactor) + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + theResultData's writeToFile:destinationPath atomically:false + end if +end saveImage`; + +export const getFilterThumbnail = (filter: Filter, source: string) => { + return runAppleScriptSync(`${initializeFilterScript(source, "", filter.CIFilterName)} + set theCIImage to current application's CIImage's imageWithData:(theImage's TIFFRepresentation()) + theFilter's setValue:theCIImage forKey:"inputImage" + ${baseFilterResultScript} + end repeat + end applyFilter + + on saveImage(imageToSave, sourcePath, destinationPath, iter) + global res + -- Saves an NSImage to the supplied file path + set theTIFFData to imageToSave's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theImageProperties to current application's NSDictionary's dictionaryWithObject:1 forKey:(current application's NSImageCompressionFactor) + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + set base64String to (theResultData's base64EncodedStringWithOptions:0) as text + set res to "data:image/png;base64," & base64String + end saveImage + + return res`); +}; + +/** + * The concluding part of the ASObjC script that applies a filter to an image. Joins all the parts of the script together and runs it. + * + * @param source The path to the image or PDF document to apply the filter to. + * @param destination The path to output the filtered image or PDF document. + * @param CIFilterName The name of the CIFilter to apply. + * @returns A promise that resolves when the script has finished running. + */ +export const applyBasicFilter = async (source: string, destination: string, CIFilterName: string) => { + return runAppleScript(`${initializeFilterScript(source, destination, CIFilterName)} + set theCIImage to current application's CIImage's imageWithData:(theImage's TIFFRepresentation()) + theFilter's setValue:theCIImage forKey:"inputImage" + ${baseFilterResultScript} + end repeat + + -- Save PDFs + if "${source}" ends with ".pdf" then + thePDF's writeToFile:"${destination}" + end if + end applyFilter + ${saveImageScript}`); +}; + +/** + * All supported filters. + */ +export const filters: Filter[] = [ + { + name: "Bloom", + description: "Softens edges and adds a glow", + applyMethod: applyBasicFilter, + CIFilterName: "CIBloom", + thumbnail: "thumbnails/bloom.webp", + }, + { + name: "Bokeh Blur", + description: "Applies a Bokeh effect", + applyMethod: applyBasicFilter, + CIFilterName: "CIBokehBlur", + thumbnail: "thumbnails/bokeh_blur.webp", + }, + { + name: "Box Blur", + description: "Blur effect using a box-shaped convolution kernel", + applyMethod: applyBasicFilter, + CIFilterName: "CIBoxBlur", + thumbnail: "thumbnails/box_blur.webp", + }, + { + name: "Chrome", + description: "Increase brightness and saturation", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectChrome", + thumbnail: "thumbnails/chrome.webp", + }, + { + name: "Circular Screen", + description: "Simulates a circular-shaped halftone screen", + applyMethod: applyBasicFilter, + CIFilterName: "CICircularScreen", + thumbnail: "thumbnails/circular_screen.webp", + }, + { + name: "Circular Wrap", + description: "Wraps an image around a transparent circle", + applyMethod: applyBasicFilter, + CIFilterName: "CICircularWrap", + thumbnail: "thumbnails/circular_wrap.webp", + }, + { + name: "CMYK Halftone", + description: "Creates a halftoned rendition of an image using cyan, magenta, yellow, and black", + applyMethod: applyBasicFilter, + CIFilterName: "CICMYKHalftone", + thumbnail: "thumbnails/cmyk_halftone.webp", + }, + { + name: "Comic", + description: "Makes images look like comic book drawings", + applyMethod: applyBasicFilter, + CIFilterName: "CIComicEffect", + thumbnail: "thumbnails/comic.webp", + }, + { + name: "Crystallize", + description: "Creates polygon-shaped color blocks by aggregating pixel values", + applyMethod: applyBasicFilter, + CIFilterName: "CICrystallize", + thumbnail: "thumbnails/crystallize.webp", + }, + { + name: "Depth Of Field", + description: "Simulates tilt-shift", + applyMethod: applyBasicFilter, + CIFilterName: "CIDepthOfField", + thumbnail: "thumbnails/depth_of_field.webp", + }, + { + name: "Disc Blur", + description: "Blur effect that uses a disc-shaped convolution kernel", + applyMethod: applyBasicFilter, + CIFilterName: "CIDiscBlur", + thumbnail: "thumbnails/disc_blur.webp", + }, + { + name: "Dither", + description: "Adds noise to reduce distortion", + applyMethod: applyBasicFilter, + CIFilterName: "CIDither", + thumbnail: "thumbnails/dither.webp", + }, + { + name: "Document Enhancement", + description: "Removes unwanted shadows, whitens background, and enhances contrast", + applyMethod: applyBasicFilter, + CIFilterName: "CIDocumentEnhancer", + thumbnail: "thumbnails/document_enhancement.webp", + }, + { + name: "Dot Screen", + description: "Simulates the dot pattern of a halftone screen", + applyMethod: applyBasicFilter, + CIFilterName: "CIDotScreen", + thumbnail: "thumbnails/dot_screen.webp", + }, + { + name: "Edges", + description: "Detects edges and highlights them colorfully, blackening other areas", + applyMethod: applyBasicFilter, + CIFilterName: "CIEdges", + thumbnail: "thumbnails/edges.webp", + }, + { + name: "Edge Work", + description: "White woodblock cutout effect", + applyMethod: applyBasicFilter, + CIFilterName: "CIEdgeWork", + thumbnail: "thumbnails/edge_work.webp", + }, + { + name: "Fade", + description: "Decreases saturation", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectFade", + thumbnail: "thumbnails/fade.webp", + }, + { + name: "Gaussian Blur", + description: "Blurs the image using a Gaussian filter", + applyMethod: applyBasicFilter, + CIFilterName: "CIGaussianBlur", + thumbnail: "thumbnails/gaussian_blur.webp", + }, + { + name: "Gloom", + description: "Dulls highlights", + applyMethod: applyBasicFilter, + CIFilterName: "CIGloom", + thumbnail: "thumbnails/gloom.webp", + }, + { + name: "Hatched Screen", + description: "Simulates the hatched pattern of a halftone screen", + applyMethod: applyBasicFilter, + CIFilterName: "CIHatchedScreen", + thumbnail: "thumbnails/hatched_screen.webp", + }, + { + name: "Hexagonal Pixellate", + description: "Pixellates images using hexagons", + applyMethod: applyBasicFilter, + CIFilterName: "CIHexagonalPixellate", + thumbnail: "thumbnails/hexagonal_pixellate.webp", + }, + { + name: "Instant", + description: "Decreases saturation, reduces contrast", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectInstant", + thumbnail: "thumbnails/instant.webp", + }, + { + name: "Invert", + description: "Inverts colors", + applyMethod: applyBasicFilter, + CIFilterName: "CIColorInvert", + thumbnail: "thumbnails/invert.webp", + }, + { + name: "Kaleidoscope", + description: "Creates a kaleidoscopic image by applying 12-way symmetry", + applyMethod: applyBasicFilter, + CIFilterName: "CIKaleidoscope", + thumbnail: "thumbnails/kaleidoscope.webp", + }, + { + name: "Line Overlay", + description: "Black woodblock cutout effect", + applyMethod: applyBasicFilter, + CIFilterName: "CILineOverlay", + thumbnail: "thumbnails/line_overlay.webp", + }, + { + name: "Line Screen", + description: "Simulates the line pattern of a halftone screen", + applyMethod: applyBasicFilter, + CIFilterName: "CILineScreen", + thumbnail: "thumbnails/line_screen.webp", + }, + { + name: "Maximum Component", + description: "Converts image to grayscale using the maximum of the three color components", + applyMethod: applyBasicFilter, + CIFilterName: "CIMaximumComponent", + thumbnail: "thumbnails/maximum_component.webp", + }, + { + name: "Median", + description: "Reduces noise by calculating median pixel values", + applyMethod: applyBasicFilter, + CIFilterName: "CILineOverlay", + thumbnail: "thumbnails/median.webp", + }, + { + name: "Minimum Component", + description: "Converts image to grayscale using the minimum of the three color components", + applyMethod: applyBasicFilter, + CIFilterName: "CIMinimumComponent", + thumbnail: "thumbnails/minimum_component.webp", + }, + { + name: "Mono", + description: "Desaturates images and reduces contrast", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectMono", + thumbnail: "thumbnails/mono.webp", + }, + { + name: "Motion Blur", + description: "Blur effect simulating a camera moving while capturing an image", + applyMethod: applyBasicFilter, + CIFilterName: "CIMotionBlur", + thumbnail: "thumbnails/motion_blur.webp", + }, + { + name: "Noir", + description: "Desaturates images and increases contrast", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectNoir", + thumbnail: "thumbnails/noir.webp", + }, + { + name: "Noise Reduction", + description: "Reduces noise by sharpening areas of low luminance", + applyMethod: applyBasicFilter, + CIFilterName: "CINoiseReduction", + thumbnail: "thumbnails/noise_reduction.webp", + }, + { + name: "Pixellate", + description: "Pixellates images with large square pixels", + applyMethod: applyBasicFilter, + CIFilterName: "CIPixellate", + thumbnail: "thumbnails/pixellate.webp", + }, + { + name: "Posterize", + description: "Flattens colors", + applyMethod: applyBasicFilter, + CIFilterName: "CIColorPosterize", + thumbnail: "thumbnails/posterize.webp", + }, + { + name: "Pointillize", + description: "Pixellates images with dots", + applyMethod: applyBasicFilter, + CIFilterName: "CIPointillize", + thumbnail: "thumbnails/pointillize.webp", + }, + { + name: "Process", + description: "Gives images a cooler toner", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectProcess", + thumbnail: "thumbnails/process.webp", + }, + { + name: "Sepia", + description: "Maps all colors to shades of brown", + applyMethod: applyBasicFilter, + CIFilterName: "CISepiaTone", + thumbnail: "thumbnails/sepia.webp", + }, + { + name: "Sharpen Luminance", + description: "Increases detailed by sharpening based on luminance", + applyMethod: applyBasicFilter, + CIFilterName: "CISharpenLuminance", + thumbnail: "thumbnails/sharpen_luminance.webp", + }, + { + name: "Thermal", + description: "Thermal camera effect", + applyMethod: applyBasicFilter, + CIFilterName: "CIThermal", + thumbnail: "thumbnails/thermal.webp", + }, + { + name: "Tonal", + description: "Decreases saturation and contrast", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectTonal", + thumbnail: "thumbnails/tonal.webp", + }, + { + name: "Transfer", + description: "Makes images warmer", + applyMethod: applyBasicFilter, + CIFilterName: "CIPhotoEffectTransfer", + thumbnail: "thumbnails/transfer.webp", + }, + { + name: "Vignette", + description: "Adds shading to the corners of images", + applyMethod: applyBasicFilter, + CIFilterName: "CIVignette", + thumbnail: "thumbnails/vignette.webp", + }, + { + name: "X-Ray", + description: "X-Ray image effect", + applyMethod: applyBasicFilter, + CIFilterName: "CIXRay", + thumbnail: "thumbnails/x-ray.webp", + }, + { + name: "Zoom Blur", + description: "Blur simulating a camera zooming in while capturing an image", + applyMethod: applyBasicFilter, + CIFilterName: "CIZoomBlur", + thumbnail: "thumbnails/zoom_blur.webp", + }, +]; diff --git a/raycast/src/utilities/generators.ts b/raycast/src/utilities/generators.ts new file mode 100644 index 0000000..1efcd59 --- /dev/null +++ b/raycast/src/utilities/generators.ts @@ -0,0 +1,502 @@ +/** + * @file utilities/generators.ts + * + * @summary Image generators and associated utilities. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 11:54:14 + * Last modified : 2023-07-06 16:47:11 + */ + +import { runAppleScript } from "run-applescript"; + +import { Generator, GeneratorKey } from "./types"; + +/** + * Common single dimension values for images. These are permuted to generate the available image sizes. + */ +export const standardDimensions = [1024, 512, 256, 128, 100, 64, 50, 32]; + +/** + * Generates a placeholder image of the specified dimensions. The image is a solid gray color. + * + * @param width The width of the image. + * @param height The height of the image. + * @param destination The destination path for the image. + * @returns A promise that resolves when the image has been generated and saved. If no destination is specified, the promise resolves with the data URL of the generated image. + */ +export const generatePlaceholder = async (width: number, height: number, destination?: string) => { + return runAppleScript(`use framework "Foundation" + use framework "Quartz" + + set theCIImage to current application's CIImage's imageWithColor:(current application's CIColor's grayColor()) + set theBounds to current application's NSMakeRect(0, 0, ${width}, ${height}) + set croppedOutput to theCIImage's imageByCroppingToRect:theBounds + + -- Convert back to NSImage and save to file + set theRep to current application's NSCIImageRep's imageRepWithCIImage:croppedOutput + set theResult to current application's NSImage's alloc()'s initWithSize:(theRep's |size|()) + theResult's addRepresentation:theRep + ${ + destination == undefined + ? `set theTIFFData to theResult's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + set theBase64String to theResultData's base64EncodedStringWithOptions:0 + return "data:image/png;base64," & theBase64String` + : `saveImage(theResult, "${destination}") + + on saveImage(imageToSave, destinationPath) + -- Saves an NSImage to the supplied file path + set theTIFFData to imageToSave's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + theResultData's writeToFile:destinationPath atomically:false + end saveImage` + }`); +}; + +/** + * Generates a data URL of a preview image for the specified CIFilter. + * + * @param CIFilterName The name of the CIFilter to generate a preview for. + * @param inputs The input key/value pairs for the CIFilter. + * @returns A promise that resolves with the data URL of the generated preview. + */ +export const generatePreview = async (CIFilterName: string, inputs: { [key: string]: unknown }) => { + return runAppleScript(`use framework "Foundation" + use framework "Quartz" + use scripting additions + + set filterName to "${CIFilterName}" + set theFilter to current application's CIFilter's filterWithName:filterName + theFilter's setDefaults() + + set imgWidth to 256 + set imgHeight to 256 + + set theCIImage to current application's CIImage's emptyImage() + ${Object.entries(inputs) + .map(([key, value]) => `theFilter's setValue:(${value}) forKey:"${key}"`) + .join("\n")} + + set theBounds to current application's NSMakeRect(0, 0, imgWidth, imgHeight) + set uncroppedOutput to theFilter's valueForKey:(current application's kCIOutputImageKey) + set croppedOutput to uncroppedOutput's imageByCroppingToRect:theBounds + + -- Convert back to NSImage and save to file + set theRep to current application's NSCIImageRep's imageRepWithCIImage:croppedOutput + set theResult to current application's NSImage's alloc()'s initWithSize:(theRep's |size|()) + theResult's addRepresentation:theRep + + -- Saves an NSImage to the supplied file path + set theTIFFData to theResult's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + set theBase64String to (theResultData's base64EncodedStringWithOptions:0) as text + return "data:image/png;base64," & theBase64String + `); +}; + +/** + * Generates a full-size render of the specified CIFilter. + * + * @param destination The destination path for the generated image. + * @param CIFilterName The name of the CIFilter to generate a preview for. + * @param width The width of the generated image. + * @param height The height of the generated image. + * @param inputs The input key/value pairs for the CIFilter. + * @returns A promise that resolves when the image has been generated and saved. + */ +export const generate = async ( + destination: string, + CIFilterName: string, + width: number, + height: number, + inputs: { [key: string]: unknown } +) => { + return runAppleScript(`use framework "Foundation" + use framework "Quartz" + use scripting additions + + set filterName to "${CIFilterName}" + set theFilter to current application's CIFilter's filterWithName:filterName + theFilter's setDefaults() + + set imgWidth to ${width} + set imgHeight to ${height} + + set theCIImage to current application's CIImage's emptyImage() + ${Object.entries(inputs) + .map(([key, value]) => `theFilter's setValue:(${value}) forKey:"${key}"`) + .join("\n")} + + set theBounds to current application's NSMakeRect(0, 0, imgWidth, imgHeight) + set uncroppedOutput to theFilter's valueForKey:(current application's kCIOutputImageKey) + set croppedOutput to uncroppedOutput's imageByCroppingToRect:theBounds + + -- Convert back to NSImage and save to file + set theRep to current application's NSCIImageRep's imageRepWithCIImage:croppedOutput + set theResult to current application's NSImage's alloc()'s initWithSize:(theRep's |size|()) + theResult's addRepresentation:theRep + saveImage(theResult, "${destination}") + + on saveImage(imageToSave, destinationPath) + -- Saves an NSImage to the supplied file path + set theTIFFData to imageToSave's TIFFRepresentation() + set theBitmapImageRep to current application's NSBitmapImageRep's imageRepWithData:theTIFFData + set theResultData to theBitmapImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value) + theResultData's writeToFile:destinationPath atomically:false + end saveImage`); +}; + +/** + * All available generators. + */ +export const generators: { [key in GeneratorKey]: Generator } = { + Checkerboard: { + applyMethod: generate, + CIFilterName: "CICheckerboardGenerator", + name: "Checkerboard", + thumbnail: "thumbnails/checkerboard.webp", + }, + ConstantColor: { + applyMethod: generate, + CIFilterName: "CIConstantColorGenerator", + name: "Constant Color", + thumbnail: "thumbnails/constant_color.webp", + }, + LenticularHalo: { + applyMethod: generate, + CIFilterName: "CILenticularHaloGenerator", + name: "Lenticular Halo", + thumbnail: "thumbnails/lenticular_halo.webp", + }, + LinearGradient: { + applyMethod: generate, + CIFilterName: "CILinearGradient", + name: "Linear Gradient", + thumbnail: "thumbnails/linear_gradient.webp", + }, + RadialGradient: { + applyMethod: generate, + CIFilterName: "CIRadialGradient", + name: "Radial Gradient", + thumbnail: "thumbnails/radial_gradient.webp", + }, + Random: { + applyMethod: generate, + CIFilterName: "CIRandomGenerator", + name: "Random", + thumbnail: "thumbnails/random.webp", + }, + StarShine: { + applyMethod: generate, + CIFilterName: "CIStarShineGenerator", + name: "Star Shine", + thumbnail: "thumbnails/star_shine.webp", + }, + Stripes: { + applyMethod: generate, + CIFilterName: "CIStripesGenerator", + name: "Stripes", + thumbnail: "thumbnails/stripes.webp", + }, + Sunbeams: { + applyMethod: generate, + CIFilterName: "CISunbeamsGenerator", + name: "Sunbeams", + thumbnail: "thumbnails/sunbeams.webp", + }, +}; + +// The rest of this file is made up of helper functions for generating CIFilter inputs. + +export const getCheckerboardOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => [ + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${ + greenValues[0] / 255 + } blue:${blueValues[0] / 255} alpha:${alphaValues[0] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[1] / 255} green:${ + greenValues[1] / 255 + } blue:${blueValues[1] / 255} alpha:${alphaValues[1] / 255}`, + inputWidth: "imgWidth / 4", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[2] / 255} green:${ + greenValues[2] / 255 + } blue:${blueValues[2] / 255} alpha:${alphaValues[2] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[3] / 255} green:${ + greenValues[3] / 255 + } blue:${blueValues[3] / 255} alpha:${alphaValues[3] / 255}`, + inputWidth: "imgWidth / 8", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[4] / 255} green:${ + greenValues[4] / 255 + } blue:${blueValues[4] / 255} alpha:${alphaValues[4] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[5] / 255} green:${ + greenValues[5] / 255 + } blue:${blueValues[5] / 255} alpha:${alphaValues[5] / 255}`, + inputWidth: "imgWidth / 16", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[6] / 255} green:${ + greenValues[6] / 255 + } blue:${blueValues[6] / 255} alpha:${alphaValues[6] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[7] / 255} green:${ + greenValues[7] / 255 + } blue:${blueValues[7] / 255} alpha:${alphaValues[7] / 255}`, + inputWidth: "imgWidth / 32", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[8] / 255} green:${ + greenValues[8] / 255 + } blue:${blueValues[8] / 255} alpha:${alphaValues[8] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[9] / 255} green:${ + greenValues[9] / 255 + } blue:${blueValues[9] / 255} alpha:${alphaValues[9] / 255}`, + inputWidth: "imgWidth / 64", + }, +]; + +export const getStripeOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => [ + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${ + greenValues[0] / 255 + } blue:${blueValues[0] / 255} alpha:${alphaValues[0] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[1] / 255} green:${ + greenValues[1] / 255 + } blue:${blueValues[1] / 255} alpha:${alphaValues[1] / 255}`, + inputWidth: "imgWidth / 4", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[2] / 255} green:${ + greenValues[2] / 255 + } blue:${blueValues[2] / 255} alpha:${alphaValues[2] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[3] / 255} green:${ + greenValues[3] / 255 + } blue:${blueValues[3] / 255} alpha:${alphaValues[3] / 255}`, + inputWidth: "imgWidth / 8", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[4] / 255} green:${ + greenValues[4] / 255 + } blue:${blueValues[4] / 255} alpha:${alphaValues[4] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[5] / 255} green:${ + greenValues[5] / 255 + } blue:${blueValues[5] / 255} alpha:${alphaValues[5] / 255}`, + inputWidth: "imgWidth / 16", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[6] / 255} green:${ + greenValues[6] / 255 + } blue:${blueValues[6] / 255} alpha:${alphaValues[6] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[7] / 255} green:${ + greenValues[7] / 255 + } blue:${blueValues[7] / 255} alpha:${alphaValues[7] / 255}`, + inputWidth: "imgWidth / 32", + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[8] / 255} green:${ + greenValues[8] / 255 + } blue:${blueValues[8] / 255} alpha:${alphaValues[8] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[9] / 255} green:${ + greenValues[9] / 255 + } blue:${blueValues[9] / 255} alpha:${alphaValues[9] / 255}`, + inputWidth: "imgWidth / 64", + }, +]; + +export const getSolidColorOptions = (redValues: number[], greenValues: number[], blueValues: number[]) => + Array(10) + .fill(0) + .map((_, i) => ({ + inputColor: `current application's CIColor's colorWithRed:${redValues[i] / 255} green:${ + greenValues[i] / 255 + } blue:${blueValues[i] / 255} alpha:1.0`, + })); + +export const getLinearGradientOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => [ + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${ + greenValues[0] / 255 + } blue:${blueValues[0] / 255} alpha:${alphaValues[0] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[1] / 255} green:${ + greenValues[1] / 255 + } blue:${blueValues[1] / 255} alpha:${alphaValues[1] / 255}`, + inputPoint0: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputPoint1: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[2] / 255} green:${ + greenValues[2] / 255 + } blue:${blueValues[2] / 255} alpha:${alphaValues[2] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[3] / 255} green:${ + greenValues[3] / 255 + } blue:${blueValues[3] / 255} alpha:${alphaValues[3] / 255}`, + inputPoint0: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputPoint1: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[4] / 255} green:${ + greenValues[4] / 255 + } blue:${blueValues[4] / 255} alpha:${alphaValues[4] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[5] / 255} green:${ + greenValues[5] / 255 + } blue:${blueValues[5] / 255} alpha:${alphaValues[5] / 255}`, + inputPoint0: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputPoint1: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[6] / 255} green:${ + greenValues[6] / 255 + } blue:${blueValues[6] / 255} alpha:${alphaValues[6] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[7] / 255} green:${ + greenValues[7] / 255 + } blue:${blueValues[7] / 255} alpha:${alphaValues[7] / 255}`, + inputPoint0: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputPoint1: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[8] / 255} green:${ + greenValues[8] / 255 + } blue:${blueValues[8] / 255} alpha:${alphaValues[8] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[9] / 255} green:${ + greenValues[9] / 255 + } blue:${blueValues[9] / 255} alpha:${alphaValues[9] / 255}`, + inputPoint0: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputPoint1: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + }, +]; + +export const getRadialGradientOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => [ + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${ + greenValues[0] / 255 + } blue:${blueValues[0] / 255} alpha:${alphaValues[0] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[1] / 255} green:${ + greenValues[1] / 255 + } blue:${blueValues[1] / 255} alpha:${alphaValues[1] / 255}`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputRadius0: `((random number) * imgWidth)`, + inputRadius1: `((random number) * imgWidth)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[2] / 255} green:${ + greenValues[2] / 255 + } blue:${blueValues[2] / 255} alpha:${alphaValues[2] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[3] / 255} green:${ + greenValues[3] / 255 + } blue:${blueValues[3] / 255} alpha:${alphaValues[3] / 255}`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputRadius0: `((random number) * imgWidth)`, + inputRadius1: `((random number) * imgWidth)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[4] / 255} green:${ + greenValues[4] / 255 + } blue:${blueValues[4] / 255} alpha:${alphaValues[4] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[5] / 255} green:${ + greenValues[5] / 255 + } blue:${blueValues[5] / 255} alpha:${alphaValues[5] / 255}`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputRadius0: `((random number) * imgWidth)`, + inputRadius1: `((random number) * imgWidth)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[6] / 255} green:${ + greenValues[6] / 255 + } blue:${blueValues[6] / 255} alpha:${alphaValues[6] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[7] / 255} green:${ + greenValues[7] / 255 + } blue:${blueValues[7] / 255} alpha:${alphaValues[7] / 255}`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputRadius0: `((random number) * imgWidth)`, + inputRadius1: `((random number) * imgWidth)`, + }, + { + inputColor0: `current application's CIColor's colorWithRed:${redValues[8] / 255} green:${ + greenValues[8] / 255 + } blue:${blueValues[8] / 255} alpha:${alphaValues[8] / 255}`, + inputColor1: `current application's CIColor's colorWithRed:${redValues[9] / 255} green:${ + greenValues[9] / 255 + } blue:${blueValues[9] / 255} alpha:${alphaValues[9] / 255}`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputRadius0: `((random number) * imgWidth)`, + inputRadius1: `((random number) * imgWidth)`, + }, +]; + +export const getStarShineOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => ({ + inputColor: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${greenValues[0] / 255} blue:${ + blueValues[0] / 255 + } alpha:${alphaValues[0] / 255}`, + inputCrossScale: `((random number) * 10)`, + inputCrossAngle: `((random number) * 90)`, + inputCrossOpacity: `((random number) * 9) - 8`, + inputCrossWidth: `((random number) * imgWidth / 5)`, + inputEpsilon: `((random number) * 1)`, + inputRadius: `((random number) * imgWidth / 10)`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, +}); + +export const getLenticularHaloOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => ({ + inputColor: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${greenValues[0] / 255} blue:${ + blueValues[0] / 255 + } alpha:${alphaValues[0] / 255}`, + inputHaloRadius: `((random number) * imgWidth / 10)`, + inputHaloWidth: `((random number) * imgWidth / 10)`, + inputStriationStrength: `((random number) * 1)`, + inputStriationContrast: `((random number) * 5)`, + inputTime: `((random number) * 10)`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, + inputHaloOverlap: `((random number) * 0.99)`, +}); + +export const getSunbeamsOptions = ( + redValues: number[], + greenValues: number[], + blueValues: number[], + alphaValues: number[] +) => ({ + inputColor: `current application's CIColor's colorWithRed:${redValues[0] / 255} green:${greenValues[0] / 255} blue:${ + blueValues[0] / 255 + } alpha:${alphaValues[0] / 255}`, + inputSunRadius: `((random number) * imgWidth / 10)`, + inputMaxStriationRadius: `((random number) * imgWidth / 10)`, + inputStriationStrength: `((random number) * 1)`, + inputStriationContrast: `((random number) * 5)`, + inputTime: `((random number) * 10)`, + inputCenter: `current application's CIVector's vectorWithX:((random number) * imgWidth) Y:((random number) * imgHeight)`, +}); diff --git a/raycast/src/utilities/preferences.ts b/raycast/src/utilities/preferences.ts new file mode 100644 index 0000000..74ff3f5 --- /dev/null +++ b/raycast/src/utilities/preferences.ts @@ -0,0 +1,77 @@ +/** + * @file utilities/preferences.ts + * + * @summary Preferences for the extension as a whole and for individual commands. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 15:34:53 + * Last modified : 2023-07-06 15:41:56 + */ + +/** + * Preferences for the extension as a whole. + */ +export interface ExtensionPreferences { + /** + * The source of input images -- either "Clipboard" or the name of a file manager (e.g. "Finder" or "Path Finder"). + */ + inputMethod: string; + + /** + * The strategy for handling the result of the image processing, i.e. where to save or display the result. One of {@link ImageResultHandling}. + */ + imageResultHandling: string; + + /** + * Whether to use lossless conversions with cwebp + */ + cwebpLossless: boolean; +} + +/** + * Preferences for the convert command. Specifies which image formats to show in the conversion formats list. + */ +export interface ConvertPreferences { + showASTC: boolean; + showBMP: boolean; + showDDS: boolean; + showEXR: boolean; + showGIF: boolean; + showHEIC: boolean; + showHEICS: boolean; + showICNS: boolean; + showICO: boolean; + showJPEG: boolean; + showJP2: boolean; + showKTX: boolean; + showPBM: boolean; + showPDF: boolean; + showPNG: boolean; + showPSD: boolean; + showPVR: boolean; + showTGA: boolean; + showTIFF: boolean; + showWEBP: boolean; + showSVG: boolean; + [key: string]: boolean; +} + +/** + * Preferences for the rotate command. + */ +export interface RotatePreferences { + /** + * The unit to use when specifying the rotation angle, either "degrees" or "radians". + */ + rotationUnit: string; +} + +/** + * Preferences for the pad command. + */ +export interface PadPreferences { + /** + * The default color to use when padding images and no color argument is provided. + */ + defaultPadColor: string; +} diff --git a/raycast/src/utilities/types.ts b/raycast/src/utilities/types.ts new file mode 100644 index 0000000..064c82d --- /dev/null +++ b/raycast/src/utilities/types.ts @@ -0,0 +1,103 @@ +/** + * @file utilities/types.ts + * + * @summary Types used throughout the extension. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:47:41 + * Last modified : 2023-07-06 15:48:21 + */ + +/** + * A wrapper around a CIFilter that can be applied to images. + */ +export type Filter = { + /** + * The name of the filter. + */ + name: string; + + /** + * A brief description of what the filter does. + */ + description: string; + + /** + * The method to apply the filter to an image. + * + * @param source The path of the image to apply the filter to. + * @param destination The path to save the filtered image to. + * @param CIFilterName The name of the CIFilter to apply. + * @returns A promise that resolves when the operation is complete. + */ + applyMethod: (source: string, destination: string, CIFilterName: string) => Promise; + + /** + * The CIFilter name to use when applying the filter. + */ + CIFilterName: string; + + /** + * The location in the extension's assets folder of the thumbnail image for the filter. + */ + thumbnail: string; +}; + +/** + * A wrapper around a CIFilter belonging to CICategoryGenerator that can be used to generate images. + */ +export type Generator = { + /** + * The name of the generator. + */ + name: string; + + /** + * The method to generate an image. + * + * @param destination The path to save the generated image to. + * @param CIFilterName The name of the CIFilter to use to generate the image. + * @param width The width of the generated image. + * @param height The height of the generated image. + * @param inputs The inputs to the CIFilter. + * @returns A promise that resolves when the operation is complete. + */ + applyMethod: ( + destination: string, + CIFilterName: string, + width: number, + height: number, + inputs: { [key: string]: unknown } + ) => Promise; + + /** + * The CIFilter name to use when generating the image. + */ + CIFilterName: string; + + /** + * The location in the extension's assets folder of the thumbnail image for the generator. + */ + thumbnail: string; +}; + +/** + * Keys for generator filters. + */ +export type GeneratorKey = + | "Checkerboard" + | "ConstantColor" + | "LenticularHalo" + | "LinearGradient" + | "RadialGradient" + | "Random" + | "StarShine" + | "Stripes" + | "Sunbeams"; + +/** + * The options to use when generating an image. Corresponds to the key-value pairs in the CIFilter's input dictionary. + */ +export type GeneratorOptions = { + [key: string]: unknown; +}; diff --git a/raycast/src/utilities/utils.ts b/raycast/src/utilities/utils.ts new file mode 100644 index 0000000..b548fcd --- /dev/null +++ b/raycast/src/utilities/utils.ts @@ -0,0 +1,985 @@ +/** + * @file utilities/utils.ts + * + * @summary Helper functions used throughout the extension. + * @author Stephen Kaplan + * + * Created at : 2023-07-06 14:48:00 + * Last modified : 2024-01-27 13:31:10 + */ + +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import path from "path"; +import { runAppleScript, runAppleScriptSync } from "run-applescript"; + +import { + Clipboard, + environment, + getFrontmostApplication, + getPreferenceValues, + LocalStorage, + showToast, + Toast, +} from "@raycast/api"; + +import { Direction, ImageResultHandling } from "./enums"; +import { copyImagesAtPathsToClipboard, getClipboardFiles, getClipboardImages } from "./clipboard"; +import { ExtensionPreferences } from "./preferences"; + +/** + * Gets currently selected images in Finder. + * + * @returns A promise resolving to the comma-separated list of images as a string. + */ +const getSelectedFinderImages = async (): Promise => { + return runAppleScript( + `set imageTypes to {"PNG", "JPG", "JPEG", "TIF", "HEIF", "GIF", "ICO", "ICNS", "ASTC", "BMP", "DDS", "EXR", "JP2", "KTX", "Portable Bitmap", "Adobe Photoshop", "PVR", "TGA", "WebP", "SVG", "PDF", "HEIC"} + + tell application "Finder" + set theSelection to selection + + if theSelection is {} and (count Finder windows) > 0 then + repeat with i from 1 to (count Finder windows) + activate window i + set theSelection to selection + + set selectionKinds to {} + repeat with j from 1 to (count theSelection) + set selectionKinds to selectionKinds & kind of (item j of theSelection) + end repeat + + set containsImage to false + repeat with imageType in imageTypes + if selectionKinds contains imageType then + set containsImage to true + exit repeat + end if + end repeat + end repeat + end if + + if theSelection is {} then + return + else if (theSelection count) is equal to 1 then + repeat with imageType in imageTypes + if (kind of the first item of theSelection) contains imageType then + return the POSIX path of (theSelection as alias) + exit repeat + end if + end repeat + else + set thePaths to {} + repeat with i from 1 to (theSelection count) + repeat with imageType in imageTypes + if (kind of (item i of theSelection)) contains imageType then + copy (POSIX path of (item i of theSelection as alias)) to end of thePaths + exit repeat + end if + end repeat + end repeat + return thePaths + end if + end tell` + ); +}; + +/** + * Gets currently selected images in Path Finder. + * + * @returns A promise resolving to the comma-separated list of images as a string. + */ +const getSelectedPathFinderImages = async (): Promise => { + return runAppleScript( + `set imageTypes to {"PNG", "JPG", "JPEG", "TIF", "HEIF", "GIF", "ICO", "ICNS", "ASTC", "BMP", "DDS", "EXR", "JP2", "KTX", "Portable Bitmap", "Adobe Photoshop", "PVR", "TGA", "WebP", "SVG", "PDF", "HEIC"} + + tell application "Path Finder" + set theSelection to selection + + if theSelection is {} and (count windows) > 0 then + repeat with i from 1 to (count windows) + activate window i + set theSelection to selection + + set selectionKinds to {} + repeat with j from 1 to (count theSelection) + set selectionKinds to selectionKinds & kind of (item j of theSelection) + end repeat + + set containsImage to false + repeat with imageType in imageTypes + if selectionKinds contains imageType then + set containsImage to true + exit repeat + end if + end repeat + end repeat + end if + + if theSelection is {} then + return + else if (theSelection count) is equal to 1 then + repeat with imageType in imageTypes + if (kind of the first item of theSelection) contains imageType then + return the POSIX path of first item of theSelection + exit repeat + end if + end repeat + else + set thePaths to {} + repeat with i from 1 to (theSelection count) + repeat with imageType in imageTypes + if (kind of (item i of theSelection)) contains imageType then + copy (POSIX path of (item i of theSelection)) to end of thePaths + exit repeat + end if + end repeat + end repeat + return thePaths + end if + end tell` + ); +}; + +/** + * Gets currently selected files in Finder. + * + * @returns A promise resolving to the comma-separated list of files as a string. + */ +const getSelectedFinderFiles = async (): Promise => { + return runAppleScript( + ` + tell application "Finder" + set theSelection to selection + + if (count of theSelection) > 1 then + set thePaths to {} + repeat with i from 1 to (count of theSelection) + set theFile to item i of theSelection + set thePath to (POSIX path of (theFile as alias)) + copy thePath to the end of thePaths + end repeat + return thePaths + else + return the POSIX path of (theSelection as alias) + end if + + end tell` + ); +}; + + +/** + * Gets currently selected files in Path Finder. + * + * @returns A promise resolving to the comma-separated list of files as a string. + */ +const getSelectedPathFinderFiles = async (): Promise => { + return runAppleScript( + ` + + tell application "Path Finder" + set theSelection to selection + + if theSelection is {} and (count windows) > 0 then + repeat with i from 1 to (count windows) + activate window i + set theSelection to selection + + set containsFile to true + end repeat + end if + + if theSelection is {} then + return + else if (theSelection count) is equal to 1 then + return the POSIX path of first item of theSelection + else + set thePaths to {} + repeat with i from 1 to (theSelection count) + copy (POSIX path of (item i of theSelection)) to end of thePaths + end repeat + return thePaths + end if + end tell + + ` + ); +}; + +/** + * Cleans up temporary files created by the extension. + * + * @returns A promise resolving when the cleanup is complete. + */ +export const cleanup = async () => { + const itemsToRemove = (await LocalStorage.getItem("itemsToRemove")) ?? ""; + const itemsToRemoveArray = itemsToRemove.toString().split(", "); + for (const item of itemsToRemoveArray) { + if (fs.existsSync(item)) { + await fs.promises.rm(item); + } + } + await LocalStorage.removeItem("itemsToRemove"); +}; + +/** + * Gets selected images in the preferred file manager application. + * + * @returns A promise resolving to the list of selected image paths. + */ +export const getSelectedImages = async (): Promise => { + const selectedImages: string[] = []; + + // Get name of preferred file manager + const extensionPreferences = getPreferenceValues(); + const inputMethod = extensionPreferences.inputMethod; + let inputMethodError = false; + + if (inputMethod == "Clipboard") { + // Extract images from clipboard + try { + const clipboardImages = (await getClipboardImages()).split(", "); + await LocalStorage.setItem("itemsToRemove", clipboardImages.join(", ")); + if (clipboardImages.filter((i) => i.trim().length > 0).length > 0) { + return clipboardImages; + } + } catch (error) { + // Error getting images from clipboard, fall back to Finder/Path Finder + console.error("Couldn't get images from clipboard"); + inputMethodError = true; + } + } + + // Get name of frontmost application + let activeApp = inputMethod; + try { + activeApp = (await getFrontmostApplication()).name; + } catch { + console.error("Couldn't get frontmost application"); + } + + // Attempt to get selected images from Path Finder + try { + if (inputMethod == "Path Finder") { + const pathFinderImages = (await getSelectedPathFinderImages()).split(", "); + pathFinderImages.forEach((imgPath) => { + if (!selectedImages.includes(imgPath)) { + selectedImages.push(imgPath); + } + }); + if (selectedImages.length > 0) { + return selectedImages; + } + } + } catch (error) { + // Error getting images from Path Finder, fall back to Finder + console.error("Couldn't get images from Path Finder"); + inputMethodError = true; + } + + // Get selected images from Finder -- use as fallback for desktop selections & on error + const finderImages = (await getSelectedFinderImages()).split(", "); + if (activeApp == "Finder" || inputMethod == "Finder" || inputMethodError) { + selectedImages.push(...finderImages); + } else { + // Add desktop selections + finderImages.forEach((imgPath) => { + if (imgPath.split("/").at(-2) == "Desktop" && !selectedImages.includes(imgPath)) { + selectedImages.push(imgPath); + } + }); + } + + return selectedImages; +}; + +/** + * Gets selected files in the preferred file manager application. + * + * @returns A promise resolving to the list of selected image paths. + */ +export const getSelectedFiles = async (): Promise => { + const selectedFiles: string[] = []; + + // Get name of preferred file manager + const extensionPreferences = getPreferenceValues(); + const inputMethod = extensionPreferences.inputMethod; + let inputMethodError = false; + + // console.log('nik inputMethod', inputMethod) + + if (inputMethod == "Clipboard") { + // Extract images from clipboard + try { + const clipboardFiles = (await getClipboardFiles()).split(", "); + await LocalStorage.setItem("itemsToRemove", clipboardFiles.join(", ")); + if (clipboardFiles.filter((i) => i.trim().length > 0).length > 0) { + return clipboardFiles; + } + } catch (error) { + // Error getting images from clipboard, fall back to Finder/Path Finder + console.error("Couldn't get images from clipboard"); + inputMethodError = true; + } + } + + // Get name of frontmost application + let activeApp = inputMethod; + try { + activeApp = (await getFrontmostApplication()).name; + } catch { + console.error("Couldn't get frontmost application"); + } + + // Attempt to get selected images from Path Finder + try { + if (inputMethod == "Path Finder") { + const pathFinderFiles = (await getSelectedPathFinderFiles()).split(", "); + pathFinderFiles.forEach((imgPath) => { + if (!selectedFiles.includes(imgPath)) { + selectedFiles.push(imgPath); + } + }); + if (selectedFiles.length > 0) { + return selectedFiles; + } + } + } catch (error) { + // Error getting images from Path Finder, fall back to Finder + console.error("Couldn't get images from Path Finder"); + inputMethodError = true; + } + + // Get selected images from Finder -- use as fallback for desktop selections & on error + const finderFiles = (await getSelectedFinderFiles()).split(", "); + // console.log('nik finderFiles', finderFiles) + if (activeApp == "Finder" || inputMethod == "Finder" || inputMethodError) { + selectedFiles.push(...finderFiles); + } else { + // Add desktop selections + finderFiles.forEach((imgPath) => { + if (imgPath.split("/").at(-2) == "Desktop" && !selectedFiles.includes(imgPath)) { + selectedFiles.push(imgPath); + } + }); + } + + return selectedFiles; +}; + +/** + * Puts the produced images in the user's preferred location, deleting the files at the given paths. + * + * @param imagePaths The paths of the produced images. + * @returns A promise resolving when the operation is complete. + */ +export const moveImageResultsToFinalDestination = async (imagePaths: string[]) => { + const preferences = getPreferenceValues(); + // Handle the result per the user's preference + if (preferences.imageResultHandling == ImageResultHandling.CopyToClipboard) { + await copyImagesAtPathsToClipboard(imagePaths); + deleteFiles(imagePaths); + } else if (preferences.imageResultHandling == ImageResultHandling.OpenInPreview) { + console.log(imagePaths); + await openPathsInPreview(imagePaths); + deleteFiles(imagePaths); + } +}; + +/** + * Executes a SIPS command on a WebP image, using a temporary PNG in the process. + * + * @param command The SIPS command to execute. + * @param webpPath The path of the WebP image. + * @returns A promise resolving to the path of the resulting image. + */ +export const execSIPSCommandOnWebP = async (command: string, webpPath: string): Promise => { + const preferences = getPreferenceValues(); + const tmpPath = `${environment.supportPath}/tmp.png`; + + let newPath = webpPath; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + const cpuType = os.cpus()[0].model.includes("Apple") ? "arm" : "x86"; + + if (cpuType == "arm") { + // Make sure the arm binaries are executable + execSync(`chmod +x ${environment.assetsPath}/webp/arm/dwebp`); + execSync(`chmod +x ${environment.assetsPath}/webp/arm/cwebp`); + // Remove x86 binaries if they exist + if (fs.existsSync(`${environment.assetsPath}/webp/x86/dwebp`)) { + await fs.promises.rm(`${environment.assetsPath}/webp/x86/dwebp`); + } + if (fs.existsSync(`${environment.assetsPath}/webp/x86/cwebp`)) { + await fs.promises.rm(`${environment.assetsPath}/webp/x86/cwebp`); + } + } else { + // Make sure the x86 binaries are executable + execSync(`chmod +x ${environment.assetsPath}/webp/x86/dwebp`); + execSync(`chmod +x ${environment.assetsPath}/webp/x86/cwebp`); + + // Remove arm binaries if they exist + if (fs.existsSync(`${environment.assetsPath}/webp/arm/dwebp`)) { + await fs.promises.rm(`${environment.assetsPath}/webp/arm/dwebp`); + } + if (fs.existsSync(`${environment.assetsPath}/webp/arm/cwebp`)) { + await fs.promises.rm(`${environment.assetsPath}/webp/arm/cwebp`); + } + } + + execSync( + `${environment.assetsPath}/webp/${cpuType}/dwebp "${webpPath}" -o "${tmpPath}" && ${command} "${tmpPath}" && ${environment.assetsPath}/webp/${cpuType}/cwebp "${tmpPath}" -o "${newPath}" ; rm "${tmpPath}"` + ); + return newPath; +}; + +/** + * Executes a SIPS command on an SVG image, using a temporary PNG in the process. + * + * @param command The SIPS command to execute. + * @param svgPath The path of the SVG image. + */ +export const execSIPSCommandOnSVG = async (command: string, svgPath: string): Promise => { + const preferences = getPreferenceValues(); + const tmpPath = `${environment.supportPath}/tmp.bmp`; + + let newPath = svgPath; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + convertSVG("BMP", svgPath, tmpPath); + execSync(`chmod +x ${environment.assetsPath}/potrace/potrace`); + execSync( + `${command} "${tmpPath}" && ${environment.assetsPath}/potrace/potrace -s --tight -o "${newPath}" "${tmpPath}"; rm "${tmpPath}"` + ); + return newPath; +}; + +/** + * Converts an SVG to a SIPS-compatible image type. + * + * @param targetType The desired image type. + * @param svgPath The path of the SVG image. + * @param newPath The path to save the resulting image in. + */ +export const convertSVG = (targetType: string, svgPath: string, newPath: string) => { + runAppleScriptSync(`use framework "Foundation" + use scripting additions + + -- Load SVG image from file + set svgFilePath to "${svgPath}" + set svgData to current application's NSData's alloc()'s initWithContentsOfFile:svgFilePath + + -- Create image from SVG data + set svgImage to current application's NSImage's alloc()'s initWithData:svgData + + -- Convert image to PNG data + set tiffData to svgImage's TIFFRepresentation() + set theBitmap to current application's NSBitmapImageRep's alloc()'s initWithData:tiffData + set pngData to theBitmap's representationUsingType:(current application's NSBitmapImageFileType${targetType}) |properties|:(missing value) + + -- Save PNG data to file + pngData's writeToFile:"${newPath}" atomically:false`); +}; + +/** + * Converts a PDF to a SIPS-compatible image type, with each page stored in its own image file. + * + * @param targetType The desired image type. + * @param pdfPath The path of the PDF document. + * @param newPathBase The folder to place the resulting images in. + */ +export const convertPDF = (targetType: string, pdfPath: string, newPathBase: string) => { + const preferences = getPreferenceValues(); + runAppleScriptSync(`use framework "Foundation" + use framework "PDFKit" + + -- Load the PDF file as NSData + set pdfData to current application's NSData's dataWithContentsOfFile:"${pdfPath}" + + -- Create a PDFDocument from the PDF data + set pdfDoc to current application's PDFDocument's alloc()'s initWithData:pdfData + + ${ + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ? `set pageImages to current application's NSMutableArray's alloc()'s init()` + : `` + } + + set pageCount to (pdfDoc's pageCount()) - 1 + repeat with pageIndex from 0 to pageCount + -- Create an NSImage from each page of the PDF document + set pdfPage to (pdfDoc's pageAtIndex:pageIndex) + set pdfRect to (pdfPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox)) + set pdfImage to (current application's NSImage's alloc()'s initWithSize:{item 1 of item 2 of pdfRect, item 2 of item 2 of pdfRect}) + pdfImage's lockFocus() + (pdfPage's drawWithBox:(current application's kPDFDisplayBoxMediaBox)) + pdfImage's unlockFocus() + + ${ + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard + ? `pageImages's addObject:pdfImage` + : ` + + -- Convert the NSImage to PNG data + set pngData to pdfImage's TIFFRepresentation() + set pngRep to (current application's NSBitmapImageRep's imageRepWithData:pngData) + set pngData to (pngRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value)) + + -- Write the PNG data to a new file + set filePath to "${newPathBase}/page-" & pageIndex + 1 & ".${targetType.toLowerCase()}" + set fileURL to current application's NSURL's fileURLWithPath:filePath + ${preferences.imageResultHandling == ImageResultHandling.OpenInPreview ? `pageImages's addObject:fileURL` : ``} + pngData's writeToURL:fileURL atomically:false` + } + end repeat + + ${ + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ? ` + -- Open the images of each page in Preview, then delete their temporary files + tell application "Finder" + set previewPath to POSIX path of ((application file id "com.apple.Preview") as text) + set previewURL to current application's NSURL's fileURLWithPath:previewPath + end tell + + set workspace to current application's NSWorkspace's sharedWorkspace() + set config to current application's NSWorkspaceOpenConfiguration's configuration() + workspace's openURLs:pageImages withApplicationAtURL:previewURL configuration:config completionHandler:(missing value) + delay 1 + + set fileManager to current application's NSFileManager's defaultManager() + repeat with imageURL in pageImages + fileManager's removeItemAtURL:imageURL |error|:(missing value) + end repeat + ` + : `` + } + + ${ + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard + ? ` + -- Copy the image of each page to the clipboard + set thePasteboard to current application's NSPasteboard's generalPasteboard() + thePasteboard's clearContents() + thePasteboard's writeObjects:pageImages` + : `` + }`); +}; + +/** + * Rotates each page of a PDF by the specified degrees. + * + * @param pdfPath The path of the PDF to rotate. + * @param degrees The amount to rotate each page by. Must be a multiple of 90. + */ +export const rotatePDF = (pdfPath: string, degrees: number): string => { + const preferences = getPreferenceValues(); + + let newPath = pdfPath; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath, path.extname(newPath)) + ".pdf"); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath, path.extname(newPath)) + ".pdf"); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath, path.extname(newPath)) + ".pdf"); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + runAppleScriptSync(`use framework "Foundation" + use framework "PDFKit" + + -- Load the PDF file as NSData + set pdfData to current application's NSData's dataWithContentsOfFile:"${pdfPath}" + + -- Create a PDFDocument from the PDF data + set pdfDoc to current application's PDFDocument's alloc()'s initWithData:pdfData + + -- Loop through the pages and rotate each one + repeat with i from 0 to ((pdfDoc's pageCount()) - 1) + set pdfPage to (pdfDoc's pageAtIndex:i) + pdfPage's setRotation:((pdfPage's |rotation|()) + ${degrees}) + end repeat + + -- Write the modified PDF data to a new file + set rotatedPdfData to pdfDoc's dataRepresentation() + rotatedPdfData's writeToFile:"${newPath}" atomically:false`); + return newPath; +}; + +/** + * + * @param pdfPath The PDF to flip each page of. + * @param direction The direction to flip. Must be a valid {@link Direction}. + */ +export const flipPDF = (pdfPath: string, direction: Direction) => { + const preferences = getPreferenceValues(); + + let newPath = pdfPath; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + let iter = 2; + while (fs.existsSync(newPath) && os.tmpdir() != path.dirname(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + + const flipInstruction = + direction == Direction.HORIZONTAL + ? `(transform's scaleXBy:-1 yBy:1) + (transform's translateXBy:(-(item 1 of item 2 of pdfRect)) yBy:0)` + : `(transform's scaleXBy:1 yBy:-1) + (transform's translateXBy:0 yBy:(-(item 2 of item 2 of pdfRect)))`; + + runAppleScriptSync(`use framework "Foundation" + use framework "PDFKit" + + -- Load the PDF file as NSData + set pdfData to current application's NSData's dataWithContentsOfFile:"${pdfPath}" + + -- Create a PDFDocument from the PDF data + set pdfDoc to current application's PDFDocument's alloc()'s initWithData:pdfData + + -- Flip each page + repeat with i from 0 to ((pdfDoc's pageCount()) - 1) + set thePDFPage to (pdfDoc's pageAtIndex:i) + set pdfRect to (thePDFPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox)) + set flippedPdfImage to (current application's NSImage's alloc()'s initWithSize:(item 2 of pdfRect)) + + flippedPdfImage's lockFocus() + set transform to current application's NSAffineTransform's alloc()'s init() + ${flipInstruction} + transform's concat() + (thePDFPage's drawWithBox:(current application's kPDFDisplayBoxMediaBox)) + flippedPdfImage's unlockFocus() + + set newPage to (current application's PDFPage's alloc()'s initWithImage:flippedPdfImage) + + (pdfDoc's removePageAtIndex:i) + (pdfDoc's insertPage:newPage atIndex:i) + end repeat + + -- Write the modified PDF data to the file + set flippedPdfData to pdfDoc's dataRepresentation() + flippedPdfData's writeToFile:"${newPath}" atomically:false`); + return newPath; +}; + +/** + * Gets the destination path for an image, given the original path and the desired extension, taking the user's preferences into account. + * @param originalPath The original path of the image. + * @param targetExtension The desired extension of the image. If not provided, the original extension will be used. + * @returns The destination path for the image. + */ +export const getImageDestination = (originalPath: string, targetExtension?: string): string => { + const preferences = getPreferenceValues(); + + // Decompose the original path into its components + const originalExtension = path.extname(originalPath); + const originalName = path.basename(originalPath, originalExtension); + const originalDir = path.dirname(originalPath); + + // Construct & return the new path + const newExtension = targetExtension ? `${targetExtension}` : originalExtension; + const newFileName = `${originalName}.${newExtension}`; + + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + const desktopPath = path.join(os.homedir(), "Downloads"); + return path.join(desktopPath, newFileName); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + const desktopPath = path.join(os.homedir(), "Desktop"); + return path.join(desktopPath, newFileName); + } + return path.join(originalDir, newFileName); +}; + +/** + * Opens the specified paths in Preview. + * + * @param filePaths The paths of the files to open. + */ +export const openPathsInPreview = async (filePaths: string | string[]) => { + const paths = Array.isArray(filePaths) ? filePaths : [filePaths]; + const containsSVG = paths.some((p) => path.extname(p) == ".svg"); + + await runAppleScript(`use framework "Foundation" + use scripting additions + set pageImages to {${paths.map((p) => `current application's NSURL's fileURLWithPath:"${p}"`).join(", ")}} + + set workspace to current application's NSWorkspace's sharedWorkspace() + set config to current application's NSWorkspaceOpenConfiguration's configuration() + + ${ + containsSVG + ? `tell application "Finder" + set safariPath to POSIX path of ((application file id "com.apple.Safari") as text) + set safariURL to current application's NSURL's fileURLWithPath:safariPath + end tell + + workspace's openURLs:pageImages withApplicationAtURL:safariURL configuration:config completionHandler:(missing value) + + tell application "Safari" + set finished to false + set iter to 0 + repeat while ((count of windows) = 0 or finished is not true) and iter < 10 + delay 0.5 + set iter to iter + 1 + + set currentStatus to true + repeat with doc in (path of documents as list) + repeat with thePath in {"${paths.map((p) => encodeURI(`file://${p}`)).join('", "')}"} + if thePath is not in doc then + set currentStatus to false + end if + end repeat + end repeat + set finished to currentStatus + end repeat + end tell + ` + : `tell application "Finder" + set previewPath to POSIX path of ((application file id "com.apple.Preview") as text) + set previewURL to current application's NSURL's fileURLWithPath:previewPath + end tell + + workspace's openURLs:pageImages withApplicationAtURL:previewURL configuration:config completionHandler:(missing value) + + tell application "Preview" + set finished to false + set iter to 0 + repeat while ((count of windows) = 0 or finished is not true) and iter < 10 + delay 0.5 + set iter to iter + 1 + + set currentStatus to true + repeat with doc in (path of documents as list) + repeat with thePath in {"${paths.join('", "')}"} + if thePath is not in doc then + set currentStatus to false + end if + end repeat + end repeat + set finished to currentStatus + end repeat + end tell` + }`); +}; + +/** + * Deletes the files at the given paths. + * + * @param filePaths The paths of the files to delete. + */ +export const deleteFiles = (filePaths: string | string[]) => { + const paths = Array.isArray(filePaths) ? filePaths : [filePaths]; + for (const path of paths) { + fs.unlinkSync(path); + } +}; + +/** + * Returns the name of the frontmost application based on whether it owns the menubar. + * + * @returns The name of the frontmost application, or "Finder" if no application owns the menubar, which shouldn't generally happen. + */ +export const getMenubarOwningApplication = () => { + return runAppleScriptSync(`use framework "Foundation" + use scripting additions + set workspace to current application's NSWorkspace's sharedWorkspace() + set runningApps to workspace's runningApplications() + + set targetApp to missing value + repeat with theApp in runningApps + if theApp's ownsMenuBar() then + set targetApp to theApp + exit repeat + end if + end repeat + + if targetApp is missing value then + return "Finder" + else + return targetApp's localizedName() as text + end if`); +}; + +/** + * Returns the current directory of the file manager. Tries Path Finder first, if it's the frontmost application, then falls back to Finder. + * + * @returns The current directory of the file manager. + */ +export const getCurrentDirectory = () => { + // Get name of frontmost application + let activeApp = "Finder"; + try { + activeApp = getMenubarOwningApplication(); + } catch { + console.error("Couldn't get frontmost application"); + } + + // Attempt to get current directory of Path Finder + try { + if (activeApp == "Path Finder") { + return runAppleScriptSync(`tell application "Path Finder" + if 1 ≤ (count finder windows) then + get POSIX path of (target of finder window 1) + else + get POSIX path of desktop + end if + end tell`); + } + } catch (error) { + // Error getting directory of Path Finder, fall back to Finder + console.error("Couldn't get current directory of Path Finder"); + } + + // Fallback to getting current directory from Finder + return runAppleScriptSync(`tell application "Finder" + if 1 <= (count Finder windows) then + get POSIX path of (target of window 1 as alias) + else + get POSIX path of (desktop as alias) + end if + end tell`); +}; + +/** + * Returns the destination paths for the given original paths, based on the user's preferences. + * + * @param originalPaths The original paths of image files. + * @param generated Whether the images were generated by the extension. + * @returns The destination paths for the given original paths. + */ +export const getDestinationPaths = (originalPaths: string[], generated = false): string[] => { + const preferences = getPreferenceValues(); + return originalPaths.map((imgPath) => { + let newPath = imgPath; + if (preferences.imageResultHandling == ImageResultHandling.SaveToDownloads) { + newPath = path.join(os.homedir(), "Downloads", path.basename(newPath)); + } else if (preferences.imageResultHandling == ImageResultHandling.SaveToDesktop) { + newPath = path.join(os.homedir(), "Desktop", path.basename(newPath)); + } else if ( + (preferences.imageResultHandling == ImageResultHandling.SaveInContainingFolder || + preferences.imageResultHandling == ImageResultHandling.ReplaceOriginal) && + (preferences.inputMethod == "Clipboard" || generated) + ) { + newPath = path.join(getCurrentDirectory(), path.basename(newPath)); + } else if ( + preferences.imageResultHandling == ImageResultHandling.CopyToClipboard || + preferences.imageResultHandling == ImageResultHandling.OpenInPreview + ) { + newPath = path.join(os.tmpdir(), path.basename(newPath)); + } + + if ( + preferences.imageResultHandling != ImageResultHandling.ReplaceOriginal && + os.tmpdir() != path.dirname(newPath) + ) { + let iter = 2; + while (fs.existsSync(newPath)) { + newPath = path.join( + path.dirname(newPath), + path.basename(newPath, path.extname(newPath)) + ` (${iter})${path.extname(newPath)}` + ); + iter++; + } + } + + return newPath; + }); +}; + +/** + * Shows or updates a toast to display the given error, and logs the error to the console. + * + * @param title The title of the toast. + * @param error The error to show. + * @param toast The toast to update. + */ +export const showErrorToast = async (title: string, error: Error, toast?: Toast) => { + console.error(error); + if (!toast) { + toast = await showToast({ + title: title, + message: error.message, + style: Toast.Style.Failure, + primaryAction: { + title: "Copy Error", + onAction: async () => { + await Clipboard.copy(error.message); + }, + }, + }); + } else { + toast.title = title; + toast.message = error.message; + toast.style = Toast.Style.Failure; + toast.primaryAction = { + title: "Copy Error", + onAction: async () => { + await Clipboard.copy(error.message); + }, + }; + } +}; diff --git a/raycast/tsconfig.json b/raycast/tsconfig.json new file mode 100644 index 0000000..3546500 --- /dev/null +++ b/raycast/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node 16", + "include": ["src/**/*"], + "compilerOptions": { + "lib": ["es2021"], + "module": "commonjs", + "target": "es2021", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "resolveJsonModule": true + } +}