diff --git a/api/README.md b/api/README.md
index 34aaed0..fe5aef6 100644
--- a/api/README.md
+++ b/api/README.md
@@ -130,4 +130,14 @@ Gets the details for a single, specific intrinsic. The following data is returne
}
]
}
-```
\ No newline at end of file
+```
+
+### `GET /version`
+Gets version information for the data. The following data is returned:
+```json
+{
+ "intelVersion": "M.m.p (Major.minor.patch version as reported by Intel)",
+ "intelUpdate": "yyyy-MM-dd (date of Intel's last update prior to scraping)",
+ "scrapeDate": "yyyy-MM-dd (date of last update)"
+}
+```
diff --git a/api/src/main/resources/logback.xml b/api/src/main/resources/logback.xml
index 2e46a79..97606fb 100644
--- a/api/src/main/resources/logback.xml
+++ b/api/src/main/resources/logback.xml
@@ -8,7 +8,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/frontend/composeApp/build.gradle.kts b/frontend/composeApp/build.gradle.kts
index e892cf8..33a3b0a 100644
--- a/frontend/composeApp/build.gradle.kts
+++ b/frontend/composeApp/build.gradle.kts
@@ -47,12 +47,14 @@ kotlin {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.logging)
+ implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
- implementation(libs.dotenv)
implementation(libs.json)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(libs.logback.classic)
+ implementation(libs.material3)
+ implementation(libs.lucide)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
@@ -65,7 +67,6 @@ kotlin {
}
}
-
compose.desktop {
application {
mainClass = "com.jaytux.simd.frontend.MainKt"
@@ -77,3 +78,7 @@ compose.desktop {
}
}
}
+
+compose.resources {
+ //
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-Italic-VariableFont_wght.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-Italic-VariableFont_wght.ttf
new file mode 100644
index 0000000..6acf196
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-Italic-VariableFont_wght.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-VariableFont_wght.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-VariableFont_wght.ttf
new file mode 100644
index 0000000..8c07df8
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/Alegreya-VariableFont_wght.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/OFL.txt b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/OFL.txt
new file mode 100644
index 0000000..0042c66
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2011 The Alegreya Project Authors (https://github.com/huertatipografica/Alegreya)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/README.txt b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/README.txt
new file mode 100644
index 0000000..6c8e9b0
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/README.txt
@@ -0,0 +1,75 @@
+Alegreya Variable Font
+======================
+
+This download contains Alegreya as both variable fonts and static fonts.
+
+Alegreya is a variable font with this axis:
+ wght
+
+This means all the styles are contained in these files:
+ Alegreya/Alegreya-VariableFont_wght.ttf
+ Alegreya/Alegreya-Italic-VariableFont_wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Alegreya:
+ Alegreya/static/Alegreya-Regular.ttf
+ Alegreya/static/Alegreya-Medium.ttf
+ Alegreya/static/Alegreya-SemiBold.ttf
+ Alegreya/static/Alegreya-Bold.ttf
+ Alegreya/static/Alegreya-ExtraBold.ttf
+ Alegreya/static/Alegreya-Black.ttf
+ Alegreya/static/Alegreya-Italic.ttf
+ Alegreya/static/Alegreya-MediumItalic.ttf
+ Alegreya/static/Alegreya-SemiBoldItalic.ttf
+ Alegreya/static/Alegreya-BoldItalic.ttf
+ Alegreya/static/Alegreya-ExtraBoldItalic.ttf
+ Alegreya/static/Alegreya-BlackItalic.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Black.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Black.ttf
new file mode 100644
index 0000000..846ec96
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Black.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BlackItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BlackItalic.ttf
new file mode 100644
index 0000000..ea26069
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BlackItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Bold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Bold.ttf
new file mode 100644
index 0000000..fe6306a
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Bold.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BoldItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BoldItalic.ttf
new file mode 100644
index 0000000..1876276
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-BoldItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBold.ttf
new file mode 100644
index 0000000..8efdcd0
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBold.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBoldItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..7c9d661
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-ExtraBoldItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Italic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Italic.ttf
new file mode 100644
index 0000000..20ea1d7
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Italic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Medium.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Medium.ttf
new file mode 100644
index 0000000..d04ac69
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Medium.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-MediumItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-MediumItalic.ttf
new file mode 100644
index 0000000..1425d18
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-MediumItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Regular.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Regular.ttf
new file mode 100644
index 0000000..3270a9f
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-Regular.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBold.ttf
new file mode 100644
index 0000000..b941c35
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBold.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBoldItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBoldItalic.ttf
new file mode 100644
index 0000000..cc93f0c
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Alegreya/static/Alegreya-SemiBoldItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/LICENSE.txt b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/LICENSE.txt
new file mode 100644
index 0000000..75b5248
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/README.txt b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/README.txt
new file mode 100644
index 0000000..ebe9bf2
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/README.txt
@@ -0,0 +1,77 @@
+Roboto Mono Variable Font
+=========================
+
+This download contains Roboto Mono as both variable fonts and static fonts.
+
+Roboto Mono is a variable font with this axis:
+ wght
+
+This means all the styles are contained in these files:
+ Roboto_Mono/RobotoMono-VariableFont_wght.ttf
+ Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Roboto Mono:
+ Roboto_Mono/static/RobotoMono-Thin.ttf
+ Roboto_Mono/static/RobotoMono-ExtraLight.ttf
+ Roboto_Mono/static/RobotoMono-Light.ttf
+ Roboto_Mono/static/RobotoMono-Regular.ttf
+ Roboto_Mono/static/RobotoMono-Medium.ttf
+ Roboto_Mono/static/RobotoMono-SemiBold.ttf
+ Roboto_Mono/static/RobotoMono-Bold.ttf
+ Roboto_Mono/static/RobotoMono-ThinItalic.ttf
+ Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf
+ Roboto_Mono/static/RobotoMono-LightItalic.ttf
+ Roboto_Mono/static/RobotoMono-Italic.ttf
+ Roboto_Mono/static/RobotoMono-MediumItalic.ttf
+ Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf
+ Roboto_Mono/static/RobotoMono-BoldItalic.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (LICENSE.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf
new file mode 100644
index 0000000..1a4d694
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-VariableFont_wght.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-VariableFont_wght.ttf
new file mode 100644
index 0000000..fc02de4
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/RobotoMono-VariableFont_wght.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Bold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Bold.ttf
new file mode 100644
index 0000000..d884128
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Bold.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-BoldItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-BoldItalic.ttf
new file mode 100644
index 0000000..e9c4802
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-BoldItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLight.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLight.ttf
new file mode 100644
index 0000000..9ff7ac6
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLight.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf
new file mode 100644
index 0000000..9e962d4
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Italic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Italic.ttf
new file mode 100644
index 0000000..61e5303
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Italic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Light.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Light.ttf
new file mode 100644
index 0000000..4893662
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Light.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-LightItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-LightItalic.ttf
new file mode 100644
index 0000000..39b7250
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-LightItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Medium.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Medium.ttf
new file mode 100644
index 0000000..f6c149a
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Medium.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-MediumItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-MediumItalic.ttf
new file mode 100644
index 0000000..70a1a75
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-MediumItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Regular.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Regular.ttf
new file mode 100644
index 0000000..6df2b25
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Regular.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBold.ttf
new file mode 100644
index 0000000..82ddc82
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBold.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf
new file mode 100644
index 0000000..15b0846
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Thin.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Thin.ttf
new file mode 100644
index 0000000..aeb997b
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-Thin.ttf differ
diff --git a/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ThinItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ThinItalic.ttf
new file mode 100644
index 0000000..ab99e08
Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/Roboto_Mono/static/RobotoMono-ThinItalic.ttf differ
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt
index 3b2f0b1..3b49883 100644
--- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt
@@ -1,36 +1,32 @@
package com.jaytux.simd.frontend
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material.Button
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.*
import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import org.jetbrains.compose.resources.painterResource
+import com.jaytux.simd.frontend.theme.SimdTheme
+import com.jaytux.simd.frontend.ui.DataState
+import com.jaytux.simd.frontend.ui.ErrorSnackBar
+import com.jaytux.simd.frontend.ui.topBar
import org.jetbrains.compose.ui.tooling.preview.Preview
-import frontend.composeapp.generated.resources.Res
-import frontend.composeapp.generated.resources.compose_multiplatform
-
@Composable
@Preview
fun App() {
- MaterialTheme {
- var showContent by remember { mutableStateOf(false) }
- Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
- Button(onClick = { showContent = !showContent }) {
- Text("Click me!")
- }
- AnimatedVisibility(showContent) {
- val greeting = remember { Greeting().greet() }
- Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
- Image(painterResource(Res.drawable.compose_multiplatform), null)
- Text("Compose: $greeting")
- }
+ SimdTheme {
+ val scope = rememberCoroutineScope()
+ val snackState = remember { SnackbarHostState() }
+ val data = DataState(scope, {
+ println(it)
+ snackState.showSnackbar(it, duration = SnackbarDuration.Short)
+ })
+
+ Scaffold(
+ topBar = { topBar(data) },
+ snackbarHost = { SnackbarHost(snackState) { ErrorSnackBar(it) } }
+ ) {
+ Surface(Modifier.padding(top = it.calculateTopPadding())) {
+ mainView(data)
}
}
}
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt
deleted file mode 100644
index dc92ff9..0000000
--- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.jaytux.simd.frontend
-
-class Greeting {
- fun greet(): String {
- return "Hello, X!"
- }
-}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt
new file mode 100644
index 0000000..160202b
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt
@@ -0,0 +1,34 @@
+package com.jaytux.simd.frontend
+
+import androidx.compose.runtime.Composable
+
+sealed class Either {
+ class Left(val value: L) : Either()
+ class Right(val value: R) : Either()
+
+ fun fold(onLeft: (L) -> T, onRight: (R) -> T): T = when(this) {
+ is Left -> onLeft(value)
+ is Right -> onRight(value)
+ }
+
+ suspend fun foldSuspend(onLeft: suspend (L) -> T, onRight: suspend (R) -> T): T = when(this) {
+ is Left -> onLeft(value)
+ is Right -> onRight(value)
+ }
+
+ @Composable
+ fun foldCompose(onLeft: @Composable (L) -> T, onRight: @Composable (R) -> T): T = when(this) {
+ is Left -> onLeft(value)
+ is Right -> onRight(value)
+ }
+}
+
+fun T.left() = Either.Left(this)
+fun T.right() = Either.Right(this)
+
+fun Either.map(f: (R) -> X): Either = when(this) {
+ is Either.Left -> this
+ is Either.Right -> Either.Right(f(value))
+}
+
+typealias OrError = Either
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt
index fffb17b..06ed0dc 100644
--- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt
@@ -1,5 +1,10 @@
package com.jaytux.simd.frontend
+import androidx.compose.runtime.Composable
+import com.jaytux.simd.frontend.ui.DataState
import io.ktor.client.*
-expect fun getKtorClient(): HttpClient
\ No newline at end of file
+expect fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient
+
+@Composable
+expect fun mainView(data: DataState): Unit
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt
new file mode 100644
index 0000000..aef5697
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt
@@ -0,0 +1,6 @@
+package com.jaytux.simd.frontend
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+
+fun MutableState.immutable(): State = this
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt
index 5323e37..125d8f8 100644
--- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt
@@ -1,7 +1,15 @@
package com.jaytux.simd.frontend.client
-import com.jaytux.simd.frontend.getKtorClient
+import com.jaytux.simd.frontend.*
import io.ktor.client.*
+import io.ktor.client.call.*
+import io.ktor.client.plugins.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.coroutines.delay
import kotlinx.datetime.LocalDate
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
@@ -10,12 +18,14 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.Json
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@OptIn(ExperimentalUuidApi::class)
object Client {
- val httpClient: HttpClient by lazy { getKtorClient() }
+ val httpClient: HttpClient by lazy { getKtorClient{ install(ContentNegotiation) { json() } } }
+ val baseUrl = "https://simd.jaytux.com/api"
object UUIDSerializer : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
@@ -65,12 +75,114 @@ object Client {
@Serializable data class Paginated(val page: Long, val totalPages: Long, val items: List)
interface PaginatedResponse {
+ fun currentPage(): Long
+ fun totalPages(): Long
fun currentItems(): List
fun hasNextPage(): Boolean
- suspend fun loadNextPage()
+ suspend fun loadNextPage(): OrError
}
- suspend fun getAll(): PaginatedResponse {
- //
+ private inline suspend fun throttle(millis: Long, crossinline block: suspend () -> T): T {
+ delay(millis)
+ return block()
}
+
+ private suspend inline fun makeBasicRequest(url: String, crossinline builder: URLBuilder.() -> Unit = {}): OrError = try {
+ val resp = httpClient.get(url) {
+ header("Accept", "application/json")
+ expectSuccess = true
+ url { builder() }
+ }
+ resp.body().right()
+ } catch (e: Exception) {
+ "Failed to load $url: ${e.message ?: "Unknown error"}".left()
+ }
+
+ private suspend inline fun getPaginated(url: String): OrError> {
+ val loader: suspend (Long?) -> OrError> = { page: Long? ->
+ makeBasicRequest>(url + (if (page != null) "/$page" else ""))
+ }
+
+ return loader(null).map { p0 ->
+ object : PaginatedResponse {
+ var page = p0.page
+ val total = p0.totalPages
+ var current = p0.items
+
+ override fun currentItems(): List = current
+ override fun hasNextPage(): Boolean = page < total
+ override fun currentPage(): Long = page
+ override fun totalPages(): Long = total
+
+ override suspend fun loadNextPage(): OrError {
+ if(hasNextPage()) {
+ return loader(page + 1).map {
+ page = it.page
+ current = it.items
+ }
+ }
+ else {
+ return "No more pages (for URL ${url})".left()
+ }
+ }
+
+ }
+ }
+ }
+
+ private suspend inline fun getPaginatedQuery(url: String, crossinline addParams: URLBuilder.() -> Unit = {}): OrError> {
+ val loader: suspend (Long?) -> OrError> = { page: Long? ->
+ makeBasicRequest>(url + (if (page != null) "/$page" else "")) {
+ addParams()
+ if(page != null) parameters.append("page", "$page")
+ }
+ }
+
+ return loader(null).map { p0 ->
+ object : PaginatedResponse {
+ var page = p0.page
+ val total = p0.totalPages
+ var current = p0.items
+
+ override fun currentItems(): List = current
+ override fun hasNextPage(): Boolean = page < total
+ override fun currentPage(): Long = page
+ override fun totalPages(): Long = total
+
+ override suspend fun loadNextPage(): OrError {
+ if(hasNextPage()) {
+ return loader(page + 1).map {
+ page = it.page
+ current = it.items
+ }
+ }
+ else {
+ return "No more pages (for URL ${url})".left()
+ }
+ }
+
+ }
+ }
+ }
+
+ suspend fun getAll(): OrError> = getPaginated("$baseUrl/all")
+ suspend fun getCpuid(): OrError> = getPaginated("$baseUrl/cpuid")
+ suspend fun getTech(): OrError> = getPaginated("$baseUrl/tech")
+ suspend fun getCategory(): OrError> = getPaginated("$baseUrl/category")
+ suspend fun getTypes(): OrError> = getPaginated("$baseUrl/types")
+
+ suspend fun getSearch(
+ name: String? = null, returnT: String? = null, cpuid: List = listOf(),
+ tech: List = listOf(), category: List = listOf(), desc: String? = null
+ ): OrError> = getPaginatedQuery("$baseUrl/search") {
+ if(name != null) parameters.append("name", name)
+ if(returnT != null) parameters.append("returnT", returnT)
+ if(cpuid.isNotEmpty()) parameters.append("cpuid", Json.encodeToString(cpuid))
+ if(tech.isNotEmpty()) parameters.append("tech", Json.encodeToString(tech))
+ if(category.isNotEmpty()) parameters.append("category", Json.encodeToString(category))
+ if(desc != null) parameters.append("desc", desc)
+ }
+
+ suspend fun getDetails(id: Uuid): OrError = makeBasicRequest("$baseUrl/details/$id")
+ suspend fun getVersion(): OrError = makeBasicRequest("$baseUrl/version")
}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt
new file mode 100644
index 0000000..18dddad
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt
@@ -0,0 +1,53 @@
+package com.jaytux.simd.frontend.client
+
+import androidx.compose.runtime.mutableStateOf
+import com.jaytux.simd.frontend.OrError
+import com.jaytux.simd.frontend.immutable
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+object Loader {
+ val progressInit = "Initializing..."
+ val progressFailed = "Finished (failed)"
+ val progressFinished = "Finished"
+
+ fun CoroutineScope.loadAll(
+ client: suspend () -> OrError>,
+ onError: suspend (String) -> Unit,
+ onFinishSuccess: suspend (List) -> Unit,
+ onStatusChange: suspend (String) -> Unit,
+ onProgress: suspend (List) -> Unit = {},
+ throttleBefore: Long = 0,
+ ) = launch {
+ delay(throttleBefore) // in case of UI-driven loading
+
+ onStatusChange(progressInit)
+
+ client().foldSuspend({
+ onStatusChange(progressFailed)
+ onError(it)
+ }) { pg ->
+ onStatusChange("Loading ${pg.currentPage() + 1}/${pg.totalPages()}")
+ val res = pg.currentItems().toMutableList()
+ var anyError = false
+
+ while (pg.hasNextPage() && !anyError) {
+ pg.loadNextPage().foldSuspend({
+ onStatusChange(progressFailed)
+ onError(it)
+ anyError = true
+ }) {
+ onStatusChange("Loading ${pg.currentPage() + 1}/${pg.totalPages()}")
+ res.addAll(pg.currentItems())
+ onProgress(res)
+ }
+ }
+
+ if (!anyError) {
+ onStatusChange(progressFinished)
+ onFinishSuccess(res)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt
new file mode 100644
index 0000000..eff9065
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt
@@ -0,0 +1,225 @@
+package com.jaytux.simd.frontend.theme
+import androidx.compose.ui.graphics.Color
+
+val primaryLight = Color(0xFF005CB9)
+val onPrimaryLight = Color(0xFFFFFFFF)
+val primaryContainerLight = Color(0xFF5B9BFF)
+val onPrimaryContainerLight = Color(0xFF00326A)
+val secondaryLight = Color(0xFF176648)
+val onSecondaryLight = Color(0xFFFFFFFF)
+val secondaryContainerLight = Color(0xFF367F5F)
+val onSecondaryContainerLight = Color(0xFFE3FFED)
+val tertiaryLight = Color(0xFF8C3D9B)
+val onTertiaryLight = Color(0xFFFFFFFF)
+val tertiaryContainerLight = Color(0xFFD07BDE)
+val onTertiaryContainerLight = Color(0xFF5A036C)
+val errorLight = Color(0xFFBA1A1A)
+val onErrorLight = Color(0xFFFFFFFF)
+val errorContainerLight = Color(0xFFFFDAD6)
+val onErrorContainerLight = Color(0xFF93000A)
+val backgroundLight = Color(0xFFF9F9FF)
+val onBackgroundLight = Color(0xFF191C22)
+val surfaceLight = Color(0xFFF9F9FF)
+val onSurfaceLight = Color(0xFF191C22)
+val surfaceVariantLight = Color(0xFFDEE2F1)
+val onSurfaceVariantLight = Color(0xFF424752)
+val outlineLight = Color(0xFF727784)
+val outlineVariantLight = Color(0xFFC2C6D4)
+val scrimLight = Color(0xFF000000)
+val inverseSurfaceLight = Color(0xFF2E3037)
+val inverseOnSurfaceLight = Color(0xFFEFF0F9)
+val inversePrimaryLight = Color(0xFFAAC7FF)
+val surfaceDimLight = Color(0xFFD8D9E2)
+val surfaceBrightLight = Color(0xFFF9F9FF)
+val surfaceContainerLowestLight = Color(0xFFFFFFFF)
+val surfaceContainerLowLight = Color(0xFFF2F3FC)
+val surfaceContainerLight = Color(0xFFECEDF6)
+val surfaceContainerHighLight = Color(0xFFE7E8F0)
+val surfaceContainerHighestLight = Color(0xFFE1E2EB)
+
+val primaryLightMediumContrast = Color(0xFF00356F)
+val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
+val primaryContainerLightMediumContrast = Color(0xFF1D6BCC)
+val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryLightMediumContrast = Color(0xFF003F29)
+val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightMediumContrast = Color(0xFF307A5A)
+val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryLightMediumContrast = Color(0xFF5D096F)
+val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightMediumContrast = Color(0xFF9C4CAC)
+val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val errorLightMediumContrast = Color(0xFF740006)
+val onErrorLightMediumContrast = Color(0xFFFFFFFF)
+val errorContainerLightMediumContrast = Color(0xFFCF2C27)
+val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
+val backgroundLightMediumContrast = Color(0xFFF9F9FF)
+val onBackgroundLightMediumContrast = Color(0xFF191C22)
+val surfaceLightMediumContrast = Color(0xFFF9F9FF)
+val onSurfaceLightMediumContrast = Color(0xFF0E1117)
+val surfaceVariantLightMediumContrast = Color(0xFFDEE2F1)
+val onSurfaceVariantLightMediumContrast = Color(0xFF313641)
+val outlineLightMediumContrast = Color(0xFF4D525E)
+val outlineVariantLightMediumContrast = Color(0xFF686D7A)
+val scrimLightMediumContrast = Color(0xFF000000)
+val inverseSurfaceLightMediumContrast = Color(0xFF2E3037)
+val inverseOnSurfaceLightMediumContrast = Color(0xFFEFF0F9)
+val inversePrimaryLightMediumContrast = Color(0xFFAAC7FF)
+val surfaceDimLightMediumContrast = Color(0xFFC5C6CE)
+val surfaceBrightLightMediumContrast = Color(0xFFF9F9FF)
+val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightMediumContrast = Color(0xFFF2F3FC)
+val surfaceContainerLightMediumContrast = Color(0xFFE7E8F0)
+val surfaceContainerHighLightMediumContrast = Color(0xFFDBDCE5)
+val surfaceContainerHighestLightMediumContrast = Color(0xFFD0D1DA)
+
+val primaryLightHighContrast = Color(0xFF002B5D)
+val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
+val primaryContainerLightHighContrast = Color(0xFF004892)
+val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val secondaryLightHighContrast = Color(0xFF003321)
+val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightHighContrast = Color(0xFF005439)
+val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryLightHighContrast = Color(0xFF4F0060)
+val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightHighContrast = Color(0xFF732584)
+val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val errorLightHighContrast = Color(0xFF600004)
+val onErrorLightHighContrast = Color(0xFFFFFFFF)
+val errorContainerLightHighContrast = Color(0xFF98000A)
+val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
+val backgroundLightHighContrast = Color(0xFFF9F9FF)
+val onBackgroundLightHighContrast = Color(0xFF191C22)
+val surfaceLightHighContrast = Color(0xFFF9F9FF)
+val onSurfaceLightHighContrast = Color(0xFF000000)
+val surfaceVariantLightHighContrast = Color(0xFFDEE2F1)
+val onSurfaceVariantLightHighContrast = Color(0xFF000000)
+val outlineLightHighContrast = Color(0xFF272C37)
+val outlineVariantLightHighContrast = Color(0xFF444955)
+val scrimLightHighContrast = Color(0xFF000000)
+val inverseSurfaceLightHighContrast = Color(0xFF2E3037)
+val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
+val inversePrimaryLightHighContrast = Color(0xFFAAC7FF)
+val surfaceDimLightHighContrast = Color(0xFFB7B8C1)
+val surfaceBrightLightHighContrast = Color(0xFFF9F9FF)
+val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightHighContrast = Color(0xFFEFF0F9)
+val surfaceContainerLightHighContrast = Color(0xFFE1E2EB)
+val surfaceContainerHighLightHighContrast = Color(0xFFD3D4DC)
+val surfaceContainerHighestLightHighContrast = Color(0xFFC5C6CE)
+
+val primaryDark = Color(0xFFAAC7FF)
+val onPrimaryDark = Color(0xFF002F65)
+val primaryContainerDark = Color(0xFF5B9BFF)
+val onPrimaryContainerDark = Color(0xFF00326A)
+val secondaryDark = Color(0xFF8CD6B0)
+val onSecondaryDark = Color(0xFF003825)
+val secondaryContainerDark = Color(0xFF367F5F)
+val onSecondaryContainerDark = Color(0xFFE3FFED)
+val tertiaryDark = Color(0xFFF6ADFF)
+val onTertiaryDark = Color(0xFF560068)
+val tertiaryContainerDark = Color(0xFFD07BDE)
+val onTertiaryContainerDark = Color(0xFF5A036C)
+val errorDark = Color(0xFFFFB4AB)
+val onErrorDark = Color(0xFF690005)
+val errorContainerDark = Color(0xFF93000A)
+val onErrorContainerDark = Color(0xFFFFDAD6)
+val backgroundDark = Color(0xFF111319)
+val onBackgroundDark = Color(0xFFE1E2EB)
+val surfaceDark = Color(0xFF111319)
+val onSurfaceDark = Color(0xFFE1E2EB)
+val surfaceVariantDark = Color(0xFF424752)
+val onSurfaceVariantDark = Color(0xFFC2C6D4)
+val outlineDark = Color(0xFF8C919E)
+val outlineVariantDark = Color(0xFF424752)
+val scrimDark = Color(0xFF000000)
+val inverseSurfaceDark = Color(0xFFE1E2EB)
+val inverseOnSurfaceDark = Color(0xFF2E3037)
+val inversePrimaryDark = Color(0xFF005CB9)
+val surfaceDimDark = Color(0xFF111319)
+val surfaceBrightDark = Color(0xFF363940)
+val surfaceContainerLowestDark = Color(0xFF0B0E14)
+val surfaceContainerLowDark = Color(0xFF191C22)
+val surfaceContainerDark = Color(0xFF1D2026)
+val surfaceContainerHighDark = Color(0xFF272A30)
+val surfaceContainerHighestDark = Color(0xFF32353B)
+
+val primaryDarkMediumContrast = Color(0xFFCDDDFF)
+val onPrimaryDarkMediumContrast = Color(0xFF002551)
+val primaryContainerDarkMediumContrast = Color(0xFF5B9BFF)
+val onPrimaryContainerDarkMediumContrast = Color(0xFF000C22)
+val secondaryDarkMediumContrast = Color(0xFFA1ECC5)
+val onSecondaryDarkMediumContrast = Color(0xFF002C1C)
+val secondaryContainerDarkMediumContrast = Color(0xFF569F7C)
+val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
+val tertiaryDarkMediumContrast = Color(0xFFFDCDFF)
+val onTertiaryDarkMediumContrast = Color(0xFF450054)
+val tertiaryContainerDarkMediumContrast = Color(0xFFD07BDE)
+val onTertiaryContainerDarkMediumContrast = Color(0xFF1C0023)
+val errorDarkMediumContrast = Color(0xFFFFD2CC)
+val onErrorDarkMediumContrast = Color(0xFF540003)
+val errorContainerDarkMediumContrast = Color(0xFFFF5449)
+val onErrorContainerDarkMediumContrast = Color(0xFF000000)
+val backgroundDarkMediumContrast = Color(0xFF111319)
+val onBackgroundDarkMediumContrast = Color(0xFFE1E2EB)
+val surfaceDarkMediumContrast = Color(0xFF111319)
+val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkMediumContrast = Color(0xFF424752)
+val onSurfaceVariantDarkMediumContrast = Color(0xFFD8DCEB)
+val outlineDarkMediumContrast = Color(0xFFADB2C0)
+val outlineVariantDarkMediumContrast = Color(0xFF8B909D)
+val scrimDarkMediumContrast = Color(0xFF000000)
+val inverseSurfaceDarkMediumContrast = Color(0xFFE1E2EB)
+val inverseOnSurfaceDarkMediumContrast = Color(0xFF272A30)
+val inversePrimaryDarkMediumContrast = Color(0xFF004690)
+val surfaceDimDarkMediumContrast = Color(0xFF111319)
+val surfaceBrightDarkMediumContrast = Color(0xFF42444B)
+val surfaceContainerLowestDarkMediumContrast = Color(0xFF05070D)
+val surfaceContainerLowDarkMediumContrast = Color(0xFF1B1E24)
+val surfaceContainerDarkMediumContrast = Color(0xFF25282E)
+val surfaceContainerHighDarkMediumContrast = Color(0xFF303339)
+val surfaceContainerHighestDarkMediumContrast = Color(0xFF3B3E44)
+
+val primaryDarkHighContrast = Color(0xFFEBF0FF)
+val onPrimaryDarkHighContrast = Color(0xFF000000)
+val primaryContainerDarkHighContrast = Color(0xFFA4C3FF)
+val onPrimaryContainerDarkHighContrast = Color(0xFF000B20)
+val secondaryDarkHighContrast = Color(0xFFBAFFDA)
+val onSecondaryDarkHighContrast = Color(0xFF000000)
+val secondaryContainerDarkHighContrast = Color(0xFF88D2AD)
+val onSecondaryContainerDarkHighContrast = Color(0xFF000E07)
+val tertiaryDarkHighContrast = Color(0xFFFFEAFC)
+val onTertiaryDarkHighContrast = Color(0xFF000000)
+val tertiaryContainerDarkHighContrast = Color(0xFFF5A7FF)
+val onTertiaryContainerDarkHighContrast = Color(0xFF1A0022)
+val errorDarkHighContrast = Color(0xFFFFECE9)
+val onErrorDarkHighContrast = Color(0xFF000000)
+val errorContainerDarkHighContrast = Color(0xFFFFAEA4)
+val onErrorContainerDarkHighContrast = Color(0xFF220001)
+val backgroundDarkHighContrast = Color(0xFF111319)
+val onBackgroundDarkHighContrast = Color(0xFFE1E2EB)
+val surfaceDarkHighContrast = Color(0xFF111319)
+val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkHighContrast = Color(0xFF424752)
+val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF)
+val outlineDarkHighContrast = Color(0xFFEBF0FF)
+val outlineVariantDarkHighContrast = Color(0xFFBEC2D1)
+val scrimDarkHighContrast = Color(0xFF000000)
+val inverseSurfaceDarkHighContrast = Color(0xFFE1E2EB)
+val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
+val inversePrimaryDarkHighContrast = Color(0xFF004690)
+val surfaceDimDarkHighContrast = Color(0xFF111319)
+val surfaceBrightDarkHighContrast = Color(0xFF4D5057)
+val surfaceContainerLowestDarkHighContrast = Color(0xFF000000)
+val surfaceContainerLowDarkHighContrast = Color(0xFF1D2026)
+val surfaceContainerDarkHighContrast = Color(0xFF2E3037)
+val surfaceContainerHighDarkHighContrast = Color(0xFF393B42)
+val surfaceContainerHighestDarkHighContrast = Color(0xFF44474E)
+
+
+
+
+
+
+
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt
new file mode 100644
index 0000000..5e8f96c
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt
@@ -0,0 +1,259 @@
+package com.jaytux.simd.frontend.theme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.Color
+
+private val lightScheme = lightColorScheme(
+ primary = primaryLight,
+ onPrimary = onPrimaryLight,
+ primaryContainer = primaryContainerLight,
+ onPrimaryContainer = onPrimaryContainerLight,
+ secondary = secondaryLight,
+ onSecondary = onSecondaryLight,
+ secondaryContainer = secondaryContainerLight,
+ onSecondaryContainer = onSecondaryContainerLight,
+ tertiary = tertiaryLight,
+ onTertiary = onTertiaryLight,
+ tertiaryContainer = tertiaryContainerLight,
+ onTertiaryContainer = onTertiaryContainerLight,
+ error = errorLight,
+ onError = onErrorLight,
+ errorContainer = errorContainerLight,
+ onErrorContainer = onErrorContainerLight,
+ background = backgroundLight,
+ onBackground = onBackgroundLight,
+ surface = surfaceLight,
+ onSurface = onSurfaceLight,
+ surfaceVariant = surfaceVariantLight,
+ onSurfaceVariant = onSurfaceVariantLight,
+ outline = outlineLight,
+ outlineVariant = outlineVariantLight,
+ scrim = scrimLight,
+ inverseSurface = inverseSurfaceLight,
+ inverseOnSurface = inverseOnSurfaceLight,
+ inversePrimary = inversePrimaryLight,
+ surfaceDim = surfaceDimLight,
+ surfaceBright = surfaceBrightLight,
+ surfaceContainerLowest = surfaceContainerLowestLight,
+ surfaceContainerLow = surfaceContainerLowLight,
+ surfaceContainer = surfaceContainerLight,
+ surfaceContainerHigh = surfaceContainerHighLight,
+ surfaceContainerHighest = surfaceContainerHighestLight,
+)
+
+private val darkScheme = darkColorScheme(
+ primary = primaryDark,
+ onPrimary = onPrimaryDark,
+ primaryContainer = primaryContainerDark,
+ onPrimaryContainer = onPrimaryContainerDark,
+ secondary = secondaryDark,
+ onSecondary = onSecondaryDark,
+ secondaryContainer = secondaryContainerDark,
+ onSecondaryContainer = onSecondaryContainerDark,
+ tertiary = tertiaryDark,
+ onTertiary = onTertiaryDark,
+ tertiaryContainer = tertiaryContainerDark,
+ onTertiaryContainer = onTertiaryContainerDark,
+ error = errorDark,
+ onError = onErrorDark,
+ errorContainer = errorContainerDark,
+ onErrorContainer = onErrorContainerDark,
+ background = backgroundDark,
+ onBackground = onBackgroundDark,
+ surface = surfaceDark,
+ onSurface = onSurfaceDark,
+ surfaceVariant = surfaceVariantDark,
+ onSurfaceVariant = onSurfaceVariantDark,
+ outline = outlineDark,
+ outlineVariant = outlineVariantDark,
+ scrim = scrimDark,
+ inverseSurface = inverseSurfaceDark,
+ inverseOnSurface = inverseOnSurfaceDark,
+ inversePrimary = inversePrimaryDark,
+ surfaceDim = surfaceDimDark,
+ surfaceBright = surfaceBrightDark,
+ surfaceContainerLowest = surfaceContainerLowestDark,
+ surfaceContainerLow = surfaceContainerLowDark,
+ surfaceContainer = surfaceContainerDark,
+ surfaceContainerHigh = surfaceContainerHighDark,
+ surfaceContainerHighest = surfaceContainerHighestDark,
+)
+
+private val mediumContrastLightColorScheme = lightColorScheme(
+ primary = primaryLightMediumContrast,
+ onPrimary = onPrimaryLightMediumContrast,
+ primaryContainer = primaryContainerLightMediumContrast,
+ onPrimaryContainer = onPrimaryContainerLightMediumContrast,
+ secondary = secondaryLightMediumContrast,
+ onSecondary = onSecondaryLightMediumContrast,
+ secondaryContainer = secondaryContainerLightMediumContrast,
+ onSecondaryContainer = onSecondaryContainerLightMediumContrast,
+ tertiary = tertiaryLightMediumContrast,
+ onTertiary = onTertiaryLightMediumContrast,
+ tertiaryContainer = tertiaryContainerLightMediumContrast,
+ onTertiaryContainer = onTertiaryContainerLightMediumContrast,
+ error = errorLightMediumContrast,
+ onError = onErrorLightMediumContrast,
+ errorContainer = errorContainerLightMediumContrast,
+ onErrorContainer = onErrorContainerLightMediumContrast,
+ background = backgroundLightMediumContrast,
+ onBackground = onBackgroundLightMediumContrast,
+ surface = surfaceLightMediumContrast,
+ onSurface = onSurfaceLightMediumContrast,
+ surfaceVariant = surfaceVariantLightMediumContrast,
+ onSurfaceVariant = onSurfaceVariantLightMediumContrast,
+ outline = outlineLightMediumContrast,
+ outlineVariant = outlineVariantLightMediumContrast,
+ scrim = scrimLightMediumContrast,
+ inverseSurface = inverseSurfaceLightMediumContrast,
+ inverseOnSurface = inverseOnSurfaceLightMediumContrast,
+ inversePrimary = inversePrimaryLightMediumContrast,
+ surfaceDim = surfaceDimLightMediumContrast,
+ surfaceBright = surfaceBrightLightMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
+ surfaceContainerLow = surfaceContainerLowLightMediumContrast,
+ surfaceContainer = surfaceContainerLightMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
+)
+
+private val highContrastLightColorScheme = lightColorScheme(
+ primary = primaryLightHighContrast,
+ onPrimary = onPrimaryLightHighContrast,
+ primaryContainer = primaryContainerLightHighContrast,
+ onPrimaryContainer = onPrimaryContainerLightHighContrast,
+ secondary = secondaryLightHighContrast,
+ onSecondary = onSecondaryLightHighContrast,
+ secondaryContainer = secondaryContainerLightHighContrast,
+ onSecondaryContainer = onSecondaryContainerLightHighContrast,
+ tertiary = tertiaryLightHighContrast,
+ onTertiary = onTertiaryLightHighContrast,
+ tertiaryContainer = tertiaryContainerLightHighContrast,
+ onTertiaryContainer = onTertiaryContainerLightHighContrast,
+ error = errorLightHighContrast,
+ onError = onErrorLightHighContrast,
+ errorContainer = errorContainerLightHighContrast,
+ onErrorContainer = onErrorContainerLightHighContrast,
+ background = backgroundLightHighContrast,
+ onBackground = onBackgroundLightHighContrast,
+ surface = surfaceLightHighContrast,
+ onSurface = onSurfaceLightHighContrast,
+ surfaceVariant = surfaceVariantLightHighContrast,
+ onSurfaceVariant = onSurfaceVariantLightHighContrast,
+ outline = outlineLightHighContrast,
+ outlineVariant = outlineVariantLightHighContrast,
+ scrim = scrimLightHighContrast,
+ inverseSurface = inverseSurfaceLightHighContrast,
+ inverseOnSurface = inverseOnSurfaceLightHighContrast,
+ inversePrimary = inversePrimaryLightHighContrast,
+ surfaceDim = surfaceDimLightHighContrast,
+ surfaceBright = surfaceBrightLightHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
+ surfaceContainerLow = surfaceContainerLowLightHighContrast,
+ surfaceContainer = surfaceContainerLightHighContrast,
+ surfaceContainerHigh = surfaceContainerHighLightHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
+)
+
+private val mediumContrastDarkColorScheme = darkColorScheme(
+ primary = primaryDarkMediumContrast,
+ onPrimary = onPrimaryDarkMediumContrast,
+ primaryContainer = primaryContainerDarkMediumContrast,
+ onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
+ secondary = secondaryDarkMediumContrast,
+ onSecondary = onSecondaryDarkMediumContrast,
+ secondaryContainer = secondaryContainerDarkMediumContrast,
+ onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
+ tertiary = tertiaryDarkMediumContrast,
+ onTertiary = onTertiaryDarkMediumContrast,
+ tertiaryContainer = tertiaryContainerDarkMediumContrast,
+ onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
+ error = errorDarkMediumContrast,
+ onError = onErrorDarkMediumContrast,
+ errorContainer = errorContainerDarkMediumContrast,
+ onErrorContainer = onErrorContainerDarkMediumContrast,
+ background = backgroundDarkMediumContrast,
+ onBackground = onBackgroundDarkMediumContrast,
+ surface = surfaceDarkMediumContrast,
+ onSurface = onSurfaceDarkMediumContrast,
+ surfaceVariant = surfaceVariantDarkMediumContrast,
+ onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
+ outline = outlineDarkMediumContrast,
+ outlineVariant = outlineVariantDarkMediumContrast,
+ scrim = scrimDarkMediumContrast,
+ inverseSurface = inverseSurfaceDarkMediumContrast,
+ inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
+ inversePrimary = inversePrimaryDarkMediumContrast,
+ surfaceDim = surfaceDimDarkMediumContrast,
+ surfaceBright = surfaceBrightDarkMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
+ surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
+ surfaceContainer = surfaceContainerDarkMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
+)
+
+private val highContrastDarkColorScheme = darkColorScheme(
+ primary = primaryDarkHighContrast,
+ onPrimary = onPrimaryDarkHighContrast,
+ primaryContainer = primaryContainerDarkHighContrast,
+ onPrimaryContainer = onPrimaryContainerDarkHighContrast,
+ secondary = secondaryDarkHighContrast,
+ onSecondary = onSecondaryDarkHighContrast,
+ secondaryContainer = secondaryContainerDarkHighContrast,
+ onSecondaryContainer = onSecondaryContainerDarkHighContrast,
+ tertiary = tertiaryDarkHighContrast,
+ onTertiary = onTertiaryDarkHighContrast,
+ tertiaryContainer = tertiaryContainerDarkHighContrast,
+ onTertiaryContainer = onTertiaryContainerDarkHighContrast,
+ error = errorDarkHighContrast,
+ onError = onErrorDarkHighContrast,
+ errorContainer = errorContainerDarkHighContrast,
+ onErrorContainer = onErrorContainerDarkHighContrast,
+ background = backgroundDarkHighContrast,
+ onBackground = onBackgroundDarkHighContrast,
+ surface = surfaceDarkHighContrast,
+ onSurface = onSurfaceDarkHighContrast,
+ surfaceVariant = surfaceVariantDarkHighContrast,
+ onSurfaceVariant = onSurfaceVariantDarkHighContrast,
+ outline = outlineDarkHighContrast,
+ outlineVariant = outlineVariantDarkHighContrast,
+ scrim = scrimDarkHighContrast,
+ inverseSurface = inverseSurfaceDarkHighContrast,
+ inverseOnSurface = inverseOnSurfaceDarkHighContrast,
+ inversePrimary = inversePrimaryDarkHighContrast,
+ surfaceDim = surfaceDimDarkHighContrast,
+ surfaceBright = surfaceBrightDarkHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
+ surfaceContainerLow = surfaceContainerLowDarkHighContrast,
+ surfaceContainer = surfaceContainerDarkHighContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
+)
+
+@Immutable
+data class ColorFamily(
+ val color: Color,
+ val onColor: Color,
+ val colorContainer: Color,
+ val onColorContainer: Color
+)
+
+val unspecified_scheme = ColorFamily(
+ Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
+)
+
+@Composable
+fun SimdTheme(
+ content: @Composable() () -> Unit
+) {
+ MaterialTheme(
+ colorScheme = darkScheme,
+ typography = buildTypography(),
+ content = content
+ )
+}
+
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt
new file mode 100644
index 0000000..4821b3e
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt
@@ -0,0 +1,43 @@
+package com.jaytux.simd.frontend.theme
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.font.FontFamily
+import com.jaytux.simd.frontend.App
+import frontend.composeapp.generated.resources.Alegreya
+import frontend.composeapp.generated.resources.Res
+import frontend.composeapp.generated.resources.Roboto_Mono
+import frontend.composeapp.generated.resources.allFontResources
+import org.jetbrains.compose.resources.Font
+
+@Composable
+fun Roboto() = FontFamily(Font(Res.font.Roboto_Mono))
+
+@Composable
+fun Alegreya() = FontFamily(Font(Res.font.Alegreya))
+
+@Composable
+fun buildTypography(): Typography {
+ val baseline = MaterialTheme.typography
+ val displayFontFamily = Alegreya()
+ val bodyFontFamily = Roboto()
+ val AppTypography = Typography(
+ displayLarge = baseline.displayLarge.copy(fontFamily = displayFontFamily),
+ displayMedium = baseline.displayMedium.copy(fontFamily = displayFontFamily),
+ displaySmall = baseline.displaySmall.copy(fontFamily = displayFontFamily),
+ headlineLarge = baseline.headlineLarge.copy(fontFamily = displayFontFamily),
+ headlineMedium = baseline.headlineMedium.copy(fontFamily = displayFontFamily),
+ headlineSmall = baseline.headlineSmall.copy(fontFamily = displayFontFamily),
+ titleLarge = baseline.titleLarge.copy(fontFamily = displayFontFamily),
+ titleMedium = baseline.titleMedium.copy(fontFamily = displayFontFamily),
+ titleSmall = baseline.titleSmall.copy(fontFamily = displayFontFamily),
+ bodyLarge = baseline.bodyLarge.copy(fontFamily = bodyFontFamily),
+ bodyMedium = baseline.bodyMedium.copy(fontFamily = bodyFontFamily),
+ bodySmall = baseline.bodySmall.copy(fontFamily = bodyFontFamily),
+ labelLarge = baseline.labelLarge.copy(fontFamily = bodyFontFamily),
+ labelMedium = baseline.labelMedium.copy(fontFamily = bodyFontFamily),
+ labelSmall = baseline.labelSmall.copy(fontFamily = bodyFontFamily),
+ )
+ return AppTypography
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt
new file mode 100644
index 0000000..4b9c32c
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt
@@ -0,0 +1,175 @@
+package com.jaytux.simd.frontend.ui
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import com.jaytux.simd.frontend.Either
+import com.jaytux.simd.frontend.client.Client
+import com.jaytux.simd.frontend.client.Loader
+import com.jaytux.simd.frontend.client.Loader.loadAll
+import com.jaytux.simd.frontend.immutable
+import com.jaytux.simd.frontend.left
+import com.jaytux.simd.frontend.right
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
+
+@OptIn(ExperimentalUuidApi::class)
+class DataState(
+ val scope: CoroutineScope,
+ val addSnackBar: suspend (message: String) -> Unit,
+) {
+ data class SubState(
+ val stateMut: MutableState = mutableStateOf(""),
+ val dataMut: MutableState> = mutableStateOf(listOf())
+ ) {
+ val state = stateMut.immutable()
+ val data = dataMut.immutable()
+ }
+
+ // --- Intrinsic Summaries ---
+ private val _summary = SubState()
+ val summaryState = _summary.state
+ val summaries = _summary.data
+
+ // --- CPUIDs ---
+ private val _cpuid = SubState()
+ val cpuidState = _cpuid.state
+ val cpuid = _cpuid.data
+ private val _selectedCpuids = mutableStateOf(listOf())
+ val selectedCpuids = _selectedCpuids.immutable()
+
+ // --- Techs ---
+ private val _techs = SubState()
+ val techsState = _techs.state
+ val techs = _techs.data
+ private val _selectedTechs = mutableStateOf(listOf())
+ val selectedTechs = _selectedTechs.immutable()
+
+ // --- CppTypes ---
+ private val _cppTypes = SubState()
+ val cppTypesState = _cppTypes.state
+ val cppTypes = _cppTypes.data
+ private var filterByType: String? = null
+
+ // --- Categories ---
+ private val _categories = SubState()
+ val categoriesState = _categories.state
+ val categories = _categories.data
+ private val _selectedCats = mutableStateOf(listOf())
+ val selectedCats = _selectedCats.immutable()
+
+ // --- Details ---
+ private val _intrinsics = mutableMapOf?>>()
+ private val _intrinsicLoaders = mutableMapOf()
+
+ // --- Version ---
+ private val _version = mutableStateOf(null)
+ val version = _version.immutable()
+
+ // --- Filtering ---
+ private var _filterJob: Job? = null
+ private val _filterData = mutableStateOf(listOf())
+ val filterData = _filterData.immutable()
+
+ init {
+ _filterJob = scope.loadAll(
+ client = { Client.getAll() },
+ onError = addSnackBar,
+ onFinishSuccess = { /*_summary.dataMut.value = it*/ _filterData.value = it },
+ onStatusChange = { _summary.stateMut.value = it },
+ onProgress = { /*_summary.dataMut.value = it*/ _filterData.value = it }
+ )
+ scope.loadAll(
+ client = { Client.getCpuid() },
+ onError = addSnackBar,
+ onFinishSuccess = { _cpuid.dataMut.value = it },
+ onStatusChange = { _cpuid.stateMut.value = it }
+ )
+ scope.loadAll(
+ client = { Client.getTech() },
+ onError = addSnackBar,
+ onFinishSuccess = { _techs.dataMut.value = it },
+ onStatusChange = { _techs.stateMut.value = it }
+ )
+ scope.loadAll(
+ client = { Client.getCategory() },
+ onError = addSnackBar,
+ onFinishSuccess = { _categories.dataMut.value = it },
+ onStatusChange = { _categories.stateMut.value = it }
+ )
+ scope.loadAll(
+ client = { Client.getTypes() },
+ onError = addSnackBar,
+ onFinishSuccess = { _cppTypes.dataMut.value = it },
+ onStatusChange = { _cppTypes.stateMut.value = it }
+ )
+
+ scope.launch {
+ Client.getVersion().foldSuspend(addSnackBar) {
+ _version.value = it
+ }
+ }
+ }
+
+ operator fun get(uuid: Uuid): State?> =
+ _intrinsics.getOrPut(uuid) { mutableStateOf(null) }
+
+ fun toggleTech(idx: Int) {
+ if (idx in _selectedTechs.value) _selectedTechs.value -= idx
+ else _selectedTechs.value += idx
+ }
+
+ fun toggleCategory(idx: Int) {
+ if (idx in _selectedCats.value) _selectedCats.value -= idx
+ else _selectedCats.value += idx
+ }
+
+ fun toggleCpuid(idx: Int) {
+ if (idx in _selectedCpuids.value) _selectedCpuids.value -= idx
+ else _selectedCpuids.value += idx
+ }
+
+ fun setReturn(type: String) {
+ filterByType = if(type !in cppTypes.value) null else type
+ }
+
+ fun loadDetails(uuid: Uuid) {
+ val current = _intrinsicLoaders[uuid]
+ if(current != null && !current.isActive) return
+
+ _intrinsicLoaders[uuid] = scope.launch {
+ Client.getDetails(uuid).foldSuspend({
+ val msg = "Failed to load details for $uuid: $it"
+ addSnackBar(msg)
+ _intrinsics.getOrPut(uuid) { mutableStateOf(null) }.value = msg.left()
+ }) {
+ _intrinsics.getOrPut(uuid) { mutableStateOf(null) }.value = it.right()
+ }
+ }
+ }
+
+ fun doFilter(name: String, desc: String) {
+ _filterJob?.cancel()
+ _filterJob = scope.loadAll(
+ client = {
+ Client.getSearch(
+ name = name.ifBlank { null },
+ returnT = filterByType,
+ cpuid = _selectedCpuids.value.map { cpuid.value[it] },
+ tech = _selectedTechs.value.map { techs.value[it] },
+ category = _selectedCats.value.map { categories.value[it] },
+ desc = desc.ifBlank { null }
+ )
+ },
+ onError = addSnackBar,
+ onFinishSuccess = { _filterData.value = it },
+ onStatusChange = {},
+ onProgress = { _filterData.value = it },
+ throttleBefore = 250L
+ )
+ }
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt
new file mode 100644
index 0000000..a648121
--- /dev/null
+++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt
@@ -0,0 +1,386 @@
+package com.jaytux.simd.frontend.ui
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.PopupProperties
+import com.composables.icons.lucide.*
+import com.jaytux.simd.frontend.OrError
+import com.jaytux.simd.frontend.client.Client
+import com.jaytux.simd.frontend.client.Loader
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlin.uuid.ExperimentalUuidApi
+
+@Composable
+fun fontSizeDp(style: TextStyle = LocalTextStyle.current) = with(LocalDensity.current) {
+ style.fontSize.toDp()
+}
+
+@Composable
+fun Spinner(text: String, style: TextStyle = LocalTextStyle.current) = Row {
+ CircularProgressIndicator(Modifier.size(fontSizeDp(style) * 2).scale(0.5f))
+ Spacer(Modifier.width(5.dp))
+ Text(text, Modifier.align(Alignment.CenterVertically), style = style)
+}
+
+@Composable
+fun ErrorSnackBar(data: SnackbarData) {
+ Snackbar(
+ containerColor = MaterialTheme.colorScheme.error,
+ contentColor = MaterialTheme.colorScheme.onError,
+ ) {
+ Box(Modifier.padding(5.dp)) {
+ Text(data.visuals.message, modifier = Modifier.align(Alignment.Center))
+ }
+ }
+}
+
+@Composable
+fun MicroHeader(text: String, modifier: Modifier = Modifier) =
+ Text(text, modifier, style = MaterialTheme.typography.bodyLarge)
+
+@Composable
+fun Collapsible(expanded: Boolean, onClick: () -> Unit, content: @Composable () -> Unit) {
+ Row(Modifier.clickable { onClick() }) {
+ Icon(if(expanded) Lucide.ChevronDown else Lucide.ChevronRight, "Expand/Collapse")
+ Spacer(Modifier.width(10.dp))
+ content()
+ }
+}
+
+@Composable
+fun Checkable(checked: Boolean, onClick: () -> Unit, content: @Composable () -> Unit) {
+ Row(Modifier.clickable { onClick() }) {
+ Icon(if(checked) Lucide.SquareCheckBig else Lucide.Square, "Check/Uncheck")
+ Spacer(Modifier.width(10.dp))
+ content()
+ }
+}
+
+@Composable
+fun Indented(content: @Composable ColumnScope.() -> Unit) {
+ Row(Modifier.padding(start = 20.dp)) {
+ Column {
+ content()
+ }
+ }
+}
+
+fun LazyListScope.FilterBlock(
+ title: String,
+ expanded: Boolean,
+ status: String,
+ options: List,
+ selected: List,
+ onToggleExpand: () -> Unit,
+ onToggleSelect: (Int) -> Unit,
+ renderT: @Composable (T) -> Unit
+) {
+ item {
+ Collapsible(expanded, onToggleExpand) {
+ MicroHeader("$title (${selected.size} selected)")
+ }
+ }
+
+ if(expanded) {
+ if(options.isEmpty()) {
+ item {
+ Indented {
+ Spinner(status)
+ }
+ }
+ }
+ else {
+ itemsIndexed(options) { i, t ->
+ Indented {
+ Checkable(i in selected, { onToggleSelect(i) }) {
+ renderT(t)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun topBar(data: DataState) = Surface(tonalElevation = 10.dp, shadowElevation = 10.dp, color = MaterialTheme.colorScheme.primaryContainer) {
+ val version by data.version
+ Box(Modifier.fillMaxWidth().padding(10.dp)) {
+ Box(Modifier.align(Alignment.Center)) {
+ Text(
+ "C/C++ (SIMD) Intrinsics",
+ style = MaterialTheme.typography.headlineLarge
+ )
+ }
+
+ version?.let {
+ Column(Modifier.align(Alignment.CenterEnd)) {
+ Row {
+ Text(
+ "Intel data: ${it.intelVersion} (last update: ${it.intelUpdate})",
+ style = MaterialTheme.typography.labelSmall
+ )
+ // TODO: add other versioning data (AMD, ARM, ...) when added to DB/backend
+ }
+
+ Text("Last database update: ${it.scrapeDate}", style = MaterialTheme.typography.labelSmall)
+ }
+ }
+ }
+}
+
+@Composable
+fun ColumnScope.FilterColumn(data: DataState) {
+ var expandTech by remember { mutableStateOf(false) }
+ var expandCategory by remember { mutableStateOf(false) }
+ var expandCpuid by remember { mutableStateOf(false) }
+ val techs by data.techs; val techMsg by data.techsState; val techSel by data.selectedTechs
+ val cats by data.categories; val catMsg by data.categoriesState; val catSel by data.selectedCats
+ val cpuid by data.cpuid; val cpuidMsg by data.cpuidState; val cpuidSel by data.selectedCpuids
+
+ Text("Filter intrinsics by:", style = MaterialTheme.typography.headlineSmall)
+
+ LazyColumn {
+ FilterBlock(
+ "ISA extensions", expandTech, techMsg, techs, techSel,
+ { expandTech = !expandTech }, { data.toggleTech(it) }
+ ) { Text(it) }
+ FilterBlock(
+ "Categories", expandCategory, catMsg, cats, catSel,
+ { expandCategory = !expandCategory }, { data.toggleCategory(it) }
+ ) { Text(it) }
+ FilterBlock(
+ "CPUIDs", expandCpuid, cpuidMsg, cpuid, cpuidSel,
+ { expandCpuid = !expandCpuid }, { data.toggleCpuid(it) }
+ ) { Text(it) }
+ }
+}
+
+@Composable
+fun IntrinsicCard(
+ summary: Client.IntrinsicSummary,
+ details: OrError?,
+ requestLoad: suspend (Client.IntrinsicSummary) -> Unit
+) {
+ var expanded by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
+ Surface(
+ Modifier.fillMaxWidth().padding(5.dp).clickable {
+ expanded = !expanded
+ if(expanded && details == null) scope.launch { requestLoad(summary) }
+ },
+ tonalElevation = 2.dp,
+ shadowElevation = 2.dp,
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Column(Modifier.padding(10.dp)) {
+ Text(summary.name, style = MaterialTheme.typography.headlineSmall)
+ if(expanded) {
+ Indented {
+ details?.foldCompose({
+ Text(it, color = MaterialTheme.colorScheme.error)
+ }) {
+ Text("Synopsis")
+ Indented {
+ Text("${it.returnType} ${it.name}(${it.params.joinToString(", ") { p -> "${p.type} ${p.name}" }}) [${it.category}]")
+ it.cpuid?.let { cpuid -> Text("CPUID: $cpuid") }
+ }
+
+ Spacer(Modifier.height(5.dp))
+ Text("Description")
+ Indented {
+ Text(it.description)
+ it.instructions?.let { insn ->
+ Text("Instruction(s):")
+ Indented {
+ insn.forEach { ins ->
+ Text("${ins.mnemonic} ${ins.form ?: ""}")
+ }
+ }
+ }
+ }
+
+ it.operations?.let { ops ->
+ Spacer(Modifier.height(5.dp))
+ Text("Operations")
+ Indented {
+ Surface(Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surfaceDim) {
+ Text(ops)
+ }
+ }
+ }
+
+ it.performance?.let { perf ->
+ if(perf.isEmpty()) return@let
+ Spacer(Modifier.height(5.dp))
+ Text("Performance")
+ Indented {
+ Row {
+ Column {
+ Text("Architecture")
+ perf.forEach { p -> Text(p.platform) }
+ }
+
+ Spacer(Modifier.width(20.dp))
+
+ Column {
+ Text("Latency (cycles)")
+ perf.forEach { p -> Text(p.latency?.toString() ?: "-") }
+ }
+
+ Spacer(Modifier.width(20.dp))
+
+ Column {
+ Text("Throughput (CPI)")
+ perf.forEach { p -> Text(p.throughput?.toString() ?: "-") }
+ }
+ }
+ }
+ }
+ } ?: Text("Loading details...", style = MaterialTheme.typography.bodySmall)
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalUuidApi::class)
+@Composable
+fun ColumnScope.IntrinsicColumn(data: DataState) {
+ var nameFilter by remember { mutableStateOf("") }
+ var descFilter by remember { mutableStateOf("") }
+ var retFilter by remember { mutableStateOf("") }
+ val intrinsicState by data.summaryState
+ val intrinsicList by data.filterData
+ val typeOptions by data.cppTypes
+ val typeMsg by data.cppTypesState
+
+ var expandFilter by remember { mutableStateOf(false) }
+
+ OutlinedTextField(
+ nameFilter, { nameFilter = it; data.doFilter(nameFilter, descFilter) },
+ Modifier.fillMaxWidth(),
+ leadingIcon = { Icon(Lucide.SearchCode, "Search") },
+ label = { Text("Filter by intrinsic name...") }
+ )
+
+ Text(
+ (if (expandFilter) "Hide" else "Show") + " additional filters",
+ Modifier.clickable { expandFilter = !expandFilter }.align(Alignment.End),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline
+ )
+
+ if (expandFilter) {
+ var showingOptions by remember { mutableStateOf(false) }
+ OutlinedTextField(
+ retFilter, { retFilter = it; showingOptions = true },
+ Modifier.clickable { showingOptions = true }.fillMaxWidth(0.66f).onFocusChanged {
+ showingOptions = it.isFocused
+ if(!it.isFocused) data.setReturn(retFilter)
+ },
+ trailingIcon = {
+ Icon(if (showingOptions) Lucide.ChevronUp else Lucide.ChevronDown, "Show options")
+ },
+ label = { Text("Filter by return type...") },
+ isError = retFilter !in typeOptions
+ )
+ DropdownMenu(
+ showingOptions, { showingOptions = false },
+ properties = PopupProperties()
+ ) {
+ if (typeOptions.isEmpty()) {
+ DropdownMenuItem(
+ { Spinner(typeMsg) }, { retFilter = "" }
+ )
+ } else {
+ DropdownMenuItem(
+ { Text("(none)") }, { retFilter = "" }
+ )
+ typeOptions.filter{ retFilter in it }.take(10).forEach { it ->
+ DropdownMenuItem(
+ { Text(it) }, { retFilter = it }
+ )
+ }
+ }
+ }
+
+ OutlinedTextField(
+ descFilter,
+ { descFilter = it; data.doFilter(nameFilter, descFilter) },
+ Modifier.fillMaxWidth(),
+ label = { Text("Filter by description...") })
+ }
+
+ if(intrinsicList.isEmpty()) {
+ Box(Modifier.fillMaxSize()) {
+ Column(Modifier.align(Alignment.Center)) {
+ Text("Loading intrinsics...")
+ Spinner(intrinsicState)
+ }
+ }
+ }
+ else {
+ if(intrinsicState != Loader.progressFinished) {
+ Spinner(intrinsicState, MaterialTheme.typography.bodySmall)
+ }
+
+ LazyColumn {
+ items(intrinsicList) { it ->
+ val details by data[it.id]
+ IntrinsicCard(it, details) { data.loadDetails(it.id) }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt
new file mode 100644
index 0000000..b38b2ab
--- /dev/null
+++ b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt
@@ -0,0 +1,35 @@
+package com.jaytux.simd.frontend
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.jaytux.simd.frontend.ui.DataState
+import com.jaytux.simd.frontend.ui.FilterColumn
+import com.jaytux.simd.frontend.ui.IntrinsicColumn
+import com.jaytux.simd.frontend.ui.topBar
+
+@Composable
+actual fun mainView(data: DataState) = Row {
+ Surface(
+ Modifier.weight(0.25f).fillMaxSize(),
+ tonalElevation = 3.dp,
+ shadowElevation = 3.dp,
+ color = MaterialTheme.colorScheme.primary,
+ shape = MaterialTheme.shapes.medium.copy(topStart = CornerSize(0.dp), topEnd = CornerSize(0.dp))
+ ) {
+ Column(Modifier.padding(5.dp)) {
+ FilterColumn(data)
+ }
+ }
+ Column(Modifier.weight(0.66f).padding(15.dp)) {
+ IntrinsicColumn(data)
+ }
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt
index b7fddb1..0794c28 100644
--- a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt
+++ b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt
@@ -2,5 +2,9 @@ package com.jaytux.simd.frontend
import io.ktor.client.*
import io.ktor.client.engine.cio.*
+import java.awt.Desktop
+import java.net.URI
-actual fun getKtorClient(): HttpClient = HttpClient(CIO)
\ No newline at end of file
+actual fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(CIO) {
+ builder()
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt
new file mode 100644
index 0000000..104417a
--- /dev/null
+++ b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt
@@ -0,0 +1,14 @@
+package com.jaytux.simd.frontend
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import com.jaytux.simd.frontend.ui.DataState
+import com.jaytux.simd.frontend.ui.topBar
+
+@Composable
+actual fun mainView(data: DataState) = Row {
+ Column { }
+ Column { }
+}
\ No newline at end of file
diff --git a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt
index a12ba8d..b0c4e4b 100644
--- a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt
+++ b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt
@@ -1,6 +1,10 @@
package com.jaytux.simd.frontend
+import androidx.compose.runtime.Composable
import io.ktor.client.*
import io.ktor.client.engine.js.*
+import kotlinx.browser.window
-actual fun getKtorClient(): HttpClient = HttpClient(Js)
\ No newline at end of file
+actual fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(Js) {
+ builder()
+}
\ No newline at end of file
diff --git a/frontend/gradle/libs.versions.toml b/frontend/gradle/libs.versions.toml
index 34b12b8..f0e8658 100644
--- a/frontend/gradle/libs.versions.toml
+++ b/frontend/gradle/libs.versions.toml
@@ -9,6 +9,7 @@ dotenv = "6.5.1"
serialization = "1.8.1"
logback = "1.5.6"
json = "20231013"
+ui-text-google-fonts = "1.8.0"
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -22,14 +23,17 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
+ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
-dotenv = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" }
json = { module = "org.json:json", version.ref = "json" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.2" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
+material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform" }
+lucide = { module = "com.composables:icons-lucide", version="1.0.0" }
+
[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }