mirror of
https://github.com/Snigdha-OS/package-browser.git
synced 2025-09-05 20:26:42 +02:00
Compare commits
19 Commits
f36187ec73
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0d1f1c5e74 | ||
![]() |
9b968adbff | ||
![]() |
751a77ac42 | ||
![]() |
0e824e3a04 | ||
![]() |
16611f41c2 | ||
![]() |
b6084343da | ||
![]() |
c09cd6bd43 | ||
![]() |
b2485151b6 | ||
![]() |
154cc8a6b0 | ||
![]() |
f1022a5a41 | ||
![]() |
7f264113c7 | ||
![]() |
c9b4e6615a | ||
![]() |
073d31a3b7 | ||
![]() |
b21d623aa0 | ||
![]() |
fc5b5ba217 | ||
![]() |
6bc0c56de2 | ||
![]() |
176bd3ef9d | ||
![]() |
3f2a66057a | ||
![]() |
9ff67af9f2 |
34
README.md
34
README.md
@@ -1,4 +1,12 @@
|
||||
# 📦 Package Browser - Search Packages
|
||||
# 📦 Package Browser - Search Packages
|
||||
|
||||
<p align="center">
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge" alt="License: MIT"></a>
|
||||
<a href="https://github.com/Snigdha-OS/package-browser/releases"><img src="https://img.shields.io/github/package-json/v/Snigdha-OS/package-browser?style=for-the-badge" alt="Version"></a>
|
||||
<a href="https://github.com/Snigdha-OS/package-browser/issues"><img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=for-the-badge" alt="Contributions Welcome"></a>
|
||||
<!-- <a href="https://github.com/Snigdha-OS/package-browser/actions"><img src="https://img.shields.io/github/actions/workflow/status/Snigdha-OS/package-browser/ci.yml?branch=main&style=for-the-badge" alt="Build Status"></a> -->
|
||||
<a href="https://snigdha-os.github.io"><img src="https://img.shields.io/website?url=https%3A%2F%2Fsnigdha-os.github.io&style=for-the-badge" alt="Website Status"></a>
|
||||
</p>
|
||||
|
||||
🚀 **Package Browser** is a user-friendly web application designed to simplify browsing, searching, and exploring packages available for Snigdha OS. This tool helps users find the right software with ease, whether for development, daily use, or penetration testing.
|
||||
|
||||
@@ -11,25 +19,27 @@
|
||||
|
||||
## 🗂️ Repository Structure
|
||||
|
||||
```
|
||||
```plaintext
|
||||
package-browser/
|
||||
.
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── config.sh
|
||||
├── eslint.config.js
|
||||
├── index.html
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
├── pnpm-lock.yaml
|
||||
├── postcss.config.js
|
||||
├── public
|
||||
│ ├── favicon.ico
|
||||
│ └── snigdhaos-og-image.png
|
||||
├── push.sh
|
||||
├── README.md
|
||||
├── src
|
||||
│ ├── App.tsx
|
||||
│ ├── components
|
||||
│ │ ├── Header.tsx
|
||||
│ │ ├── InstallGuide.tsx
|
||||
│ │ ├── Logo.tsx
|
||||
│ │ ├── PackageCard
|
||||
│ │ │ ├── Badge.tsx
|
||||
│ │ │ ├── ExpandButton.tsx
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PackageCard.tsx
|
||||
│ │ ├── PackageList.tsx
|
||||
│ │ ├── SearchBar.tsx
|
||||
@@ -37,7 +47,13 @@ package-browser/
|
||||
│ ├── hooks
|
||||
│ │ ├── usePackages.ts
|
||||
│ │ └── useTheme.ts
|
||||
│ ├── i18n.tsx
|
||||
│ ├── index.css
|
||||
│ ├── locales
|
||||
│ │ ├── index.tsx
|
||||
│ │ ├── en.tsx
|
||||
│ │ ├── uk.tsx
|
||||
│ │ └── pl.tsx
|
||||
│ ├── main.tsx
|
||||
│ ├── services
|
||||
│ │ └── api.ts
|
||||
@@ -47,7 +63,7 @@ package-browser/
|
||||
├── tsconfig.app.json
|
||||
├── tsconfig.json
|
||||
├── tsconfig.node.json
|
||||
└── vite.config.ts
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
## 🛠️ Installation
|
||||
@@ -89,4 +105,4 @@ This project is licensed under the [MIT License](LICENSE).
|
||||
|
||||
## 💬 Feedback
|
||||
|
||||
We’d love to hear your feedback and suggestions! Feel free to open an issue or contact us directly via the [Snigdha OS](https://github.com/Snigdha-OS) organization.
|
||||
We’d love to hear your feedback and suggestions! Feel free to open an issue or contact us directly via the [Snigdha OS](https://github.com/Snigdha-OS) organization.
|
||||
|
10
index.html
10
index.html
@@ -6,12 +6,12 @@
|
||||
<meta name="description" content="Browse and explore Snigdha OS packages easily with this lightweight package viewer." />
|
||||
|
||||
<!-- Author Metadata -->
|
||||
<meta name="author" content="Eshan Roy, d3v1l0n" />
|
||||
<meta name="author:website" content="https://www.eshanized.github.io/, https://www.d3v1l0n.github.io/" />
|
||||
<meta name="author:github" content="https://github.com/eshanized, https://github.com/d3v1l0n" />
|
||||
<meta name="author" content="Eshan Roy, d3v1l0n, XlebyllleK" />
|
||||
<meta name="author:website" content="https://www.eshanized.github.io/, https://www.d3v1l0n.github.io/, https://github.com/Xlebylllek/" />
|
||||
<meta name="author:github" content="https://github.com/eshanized, https://github.com/d3v1l0n, https://github.com/XlebyllleK" />
|
||||
<meta name="author:twitter" content="https://twitter.com/eshanized, https://twitter.com/d3v1l0n" />
|
||||
<meta name="author:bio" content="Eshan Roy is a software developer, open-source enthusiast, and the creator of Snigdha OS. d3v1l0n is a cybersecurity expert and open-source contributor working on various security-focused tools." />
|
||||
<meta name="author:email" content="m.eshanized@gmail.com, d3v1l0n@outlook.in" />
|
||||
<meta name="author:bio" content="Eshan Roy is a software developer, open-source enthusiast, and the creator of Snigdha OS. d3v1l0n is a cybersecurity expert and open-source contributor working on various security-focused tools. XlebyllleK is a software developer, open-source enthusiast, and the creator of Snigdha OS." />
|
||||
<meta name="author:email" content="m.eshanized@gmail.com, d3v1l0n@outlook.in, celestifyx@gmail.com" />
|
||||
|
||||
<!-- Open Graph Meta Tags for social media sharing -->
|
||||
<meta property="og:title" content="Snigdha OS Packages" />
|
||||
|
4051
package-lock.json
generated
4051
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,11 @@
|
||||
"gh-pages": "^6.3.0",
|
||||
"lucide-react": "^0.471.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"i18next": "^24.2.1",
|
||||
"react-i18next": "^15.4.0",
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"i18next-browser-languagedetector": "^8.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
130
pnpm-lock.yaml
generated
130
pnpm-lock.yaml
generated
@@ -11,6 +11,15 @@ importers:
|
||||
gh-pages:
|
||||
specifier: ^6.3.0
|
||||
version: 6.3.0
|
||||
i18next:
|
||||
specifier: ^24.2.1
|
||||
version: 24.2.1(typescript@5.7.3)
|
||||
i18next-browser-languagedetector:
|
||||
specifier: ^8.0.2
|
||||
version: 8.0.2
|
||||
i18next-http-backend:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
lucide-react:
|
||||
specifier: ^0.471.0
|
||||
version: 0.471.0(react@19.0.0)
|
||||
@@ -20,6 +29,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-i18next:
|
||||
specifier: ^15.4.0
|
||||
version: 15.4.0(i18next@24.2.1(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.17.0
|
||||
@@ -141,6 +153,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -690,6 +706,9 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -930,6 +949,23 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
|
||||
i18next-browser-languagedetector@8.0.2:
|
||||
resolution: {integrity: sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==}
|
||||
|
||||
i18next-http-backend@3.0.1:
|
||||
resolution: {integrity: sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==}
|
||||
|
||||
i18next@24.2.1:
|
||||
resolution: {integrity: sha512-Q2wC1TjWcSikn1VAJg13UGIjc+okpFxQTxjVAymOnSA3RpttBQNMPf2ovcgoFVsV4QNxTfNZMAxorXZXsk4fBA==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -1078,6 +1114,15 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
@@ -1223,6 +1268,19 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
react-i18next@15.4.0:
|
||||
resolution: {integrity: sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1238,6 +1296,9 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1344,6 +1405,9 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
trim-repeated@1.0.0:
|
||||
resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1429,6 +1493,16 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1554,6 +1628,10 @@ snapshots:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
@@ -2037,6 +2115,12 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -2310,6 +2394,26 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
|
||||
i18next-browser-languagedetector@8.0.2:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
|
||||
i18next-http-backend@3.0.1:
|
||||
dependencies:
|
||||
cross-fetch: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
i18next@24.2.1(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
@@ -2435,6 +2539,10 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
@@ -2551,6 +2659,15 @@ snapshots:
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-i18next@15.4.0(i18next@24.2.1(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 24.2.1(typescript@5.7.3)
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@19.0.0: {}
|
||||
@@ -2563,6 +2680,8 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve@1.22.10:
|
||||
@@ -2701,6 +2820,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
trim-repeated@1.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
@@ -2751,6 +2872,15 @@ snapshots:
|
||||
jiti: 1.21.7
|
||||
yaml: 2.7.0
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
36
src/App.tsx
36
src/App.tsx
@@ -13,6 +13,10 @@ import {
|
||||
Repository
|
||||
} from './types';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from './i18n';
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
const { packages, loading, error } = usePackages();
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -47,6 +51,8 @@ export default function App(): JSX.Element {
|
||||
setSelectedRepository(repo);
|
||||
};
|
||||
|
||||
const count = filteredPackages.length;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-nord-6 dark:bg-nord-0 transition-colors" role="main">
|
||||
<Header onRepositoryChange={handleRepositoryFilterChange} />
|
||||
@@ -59,26 +65,30 @@ export default function App(): JSX.Element {
|
||||
|
||||
{/* Package Counter */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-sm text-nord-3 dark:text-nord-4" aria-live="polite">
|
||||
Showing {filteredPackages.length} package{filteredPackages.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
<p className="text-sm text-nord-3 dark:text-nord-4" aria-live="polite">{(count === 1) ? translate("App.package_count.single", {
|
||||
count
|
||||
}) : (count >= 2) && (count <= 4) ? translate("App.package_count.multiple", {
|
||||
count
|
||||
}) : translate("App.package_count.plural", {
|
||||
count
|
||||
})}</p>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
{error ? (
|
||||
<div className="rounded-lg bg-nord-11/10 dark:bg-nord-11/20 p-4 text-nord-11" role="alert">
|
||||
<p>An error occurred while fetching packages: {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-2 inline-block text-sm text-nord-10 hover:underline"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<p>{translate("App.error.fetching-packages", {
|
||||
error
|
||||
})}</p>
|
||||
|
||||
<button onClick={
|
||||
() => window.location.reload()
|
||||
} className="mt-2 inline-block text-sm text-nord-10 hover:underline">{translate("App.error.retry-fetching-packages")}</button>
|
||||
</div>
|
||||
) : filteredPackages.length === 0 ? (
|
||||
) : (count === 0) ? (
|
||||
// Empty State
|
||||
<div className="text-center text-nord-3 dark:text-nord-4 mt-12">
|
||||
<p>No packages found matching your search.</p>
|
||||
<p>{translate("App.no-packages-found")}</p>
|
||||
</div>
|
||||
) : (
|
||||
// Package List
|
||||
@@ -89,7 +99,7 @@ export default function App(): JSX.Element {
|
||||
<footer className="bg-nord-5 dark:bg-nord-1 border-t border-nord-4 dark:border-nord-2 mt-12 transition-colors">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-sm text-nord-3 dark:text-nord-4">
|
||||
Data Source:{' '}
|
||||
{translate("App.footer")}{' '}
|
||||
<a
|
||||
href="https://github.com/Snigdha-OS/snigdhaos-core"
|
||||
className="text-nord-10 hover:text-nord-9 dark:text-nord-8 dark:hover:text-nord-7"
|
||||
|
@@ -1,18 +1,31 @@
|
||||
import {
|
||||
JSX
|
||||
JSX,
|
||||
useEffect
|
||||
} from 'react';
|
||||
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { Logo } from './Logo';
|
||||
import {
|
||||
ThemeToggle
|
||||
} from './ThemeToggle';
|
||||
|
||||
import {
|
||||
Repository
|
||||
Logo
|
||||
} from './Logo';
|
||||
|
||||
import {
|
||||
Repository,
|
||||
Languages
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
MIRRORS
|
||||
} from '../services/api';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
import i18next from 'i18next';
|
||||
|
||||
interface HeaderProps {
|
||||
onRepositoryChange: (repo: Repository) => void;
|
||||
}
|
||||
@@ -23,12 +36,24 @@ export function Header({
|
||||
const usedRepositories = new Set(Object.values(MIRRORS).map(mirror => mirror.repository));
|
||||
|
||||
const filteredRepository = Object.keys(Repository).reduce((acc, key) => {
|
||||
// This code is flawed because it explicitly checks for 'ALL', reducing flexibility.
|
||||
// A more generic approach should be used to filter unused repositories while preserving 'ALL'.
|
||||
if ((key === 'ALL') || usedRepositories.has(Repository[key as keyof typeof Repository])) acc[key] = Repository[key as keyof typeof Repository];
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const language = event.target.value;
|
||||
|
||||
i18next.changeLanguage(language).then(() => {
|
||||
localStorage.setItem('selectedLanguage', language);
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const savedLanguage = localStorage.getItem('selectedLanguage');
|
||||
if (savedLanguage && (savedLanguage !== i18next.language)) i18next.changeLanguage(savedLanguage);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className="bg-gradient-to-r from-nord-9 to-nord-8 via-nord-10 text-nord-6 shadow-lg">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
@@ -46,7 +71,18 @@ export function Header({
|
||||
(e) => onRepositoryChange(e.target.value as Repository)
|
||||
} defaultValue="all" className="bg-nord-5 dark:bg-nord-1 text-black dark:text-white border-2 border-nord-4 dark:border-nord-2 rounded-lg py-2 px-4 focus:ring-2 focus:ring-nord-8">
|
||||
{Object.values(filteredRepository).map((repository) => (
|
||||
<option key={repository} value={repository}>{((repository === Repository.ALL) ? 'All Repositories' : repository.charAt(0).toUpperCase() + repository.slice(1))}</option>
|
||||
<option key={repository} value={repository}>{((repository === Repository.ALL) ? translate("Header.all-repositories") : repository.charAt(0).toUpperCase() + repository.slice(1))}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Language Dropdown */}
|
||||
<div>
|
||||
<select onChange={handleLanguageChange} defaultValue={i18next.language} className="bg-nord-5 dark:bg-nord-1 text-black dark:text-white border-2 border-nord-4 dark:border-nord-2 rounded-lg py-2 px-4 focus:ring-2 focus:ring-nord-8">
|
||||
{Object.entries(Languages).map(([key, label]) => (
|
||||
<option key={key} value={key.toLowerCase()}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
@@ -5,6 +5,10 @@ import {
|
||||
|
||||
import { Terminal, Copy, Check } from 'lucide-react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface InstallGuideProps {
|
||||
packageName: string;
|
||||
}
|
||||
@@ -25,20 +29,13 @@ export function InstallGuide({ packageName }: InstallGuideProps): JSX.Element {
|
||||
{/* Title Section */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Terminal className="h-5 w-5 text-nord-9 dark:text-nord-7" />
|
||||
<span className="text-nord-2 dark:text-nord-5 text-sm font-semibold uppercase tracking-wide">
|
||||
Installation Command
|
||||
</span>
|
||||
<span className="text-nord-2 dark:text-nord-5 text-sm font-semibold uppercase tracking-wide">{translate("InstallGuide.installation-command")}</span>
|
||||
</div>
|
||||
|
||||
{/* Command Display Section */}
|
||||
<div className="flex items-center justify-between bg-nord-4 dark:bg-nord-2 rounded-lg px-5 py-3">
|
||||
<code className="text-nord-8 dark:text-nord-6 font-mono text-sm truncate">{command}</code>
|
||||
<button
|
||||
onClick={copyCommand}
|
||||
className="flex items-center justify-center w-8 h-8 rounded-full bg-nord-3 hover:bg-nord-7 dark:bg-nord-5 dark:hover:bg-nord-8 transition-colors"
|
||||
title={copied ? 'Copied!' : 'Copy to clipboard'}
|
||||
aria-label={copied ? 'Copied' : 'Copy command'}
|
||||
>
|
||||
<button onClick={copyCommand} className="flex items-center justify-center w-8 h-8 rounded-full bg-nord-3 hover:bg-nord-7 dark:bg-nord-5 dark:hover:bg-nord-8 transition-colors" title={copied ? translate("InstallGuide.copy.success") : translate("InstallGuide.copy.prompt")}>
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-nord-6 dark:text-nord-0" />
|
||||
) : (
|
||||
|
@@ -2,6 +2,10 @@ import {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
export function Logo(): JSX.Element {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -22,10 +26,8 @@ export function Logo(): JSX.Element {
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Snigdha OS Package List</h1>
|
||||
<p className="text-nord-4 text-sm mt-1">
|
||||
Browse and search through the official Snigdha OS package repository
|
||||
</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">{translate("Logo.title")}</h1>
|
||||
<p className="text-nord-4 text-sm mt-1">{translate("Logo.description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -7,6 +7,10 @@ import { Box, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Package } from '../types';
|
||||
import { InstallGuide } from './InstallGuide';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface PackageCardProps {
|
||||
package: Package;
|
||||
}
|
||||
@@ -49,12 +53,12 @@ export function PackageCard({ package: pkg }: PackageCardProps): JSX.Element {
|
||||
{expanded ? (
|
||||
<>
|
||||
<ChevronUp className="h-5 w-5 animate-bounce" />
|
||||
Hide Installation
|
||||
{translate("PackageCard.hide-installation")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-5 w-5 animate-bounce" />
|
||||
Show Installation
|
||||
{translate("PackageCard.show-installation")}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
@@ -1,38 +0,0 @@
|
||||
import React, {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
color?: 'default' | 'success' | 'warning' | 'error'; // Add more colors as needed
|
||||
size?: 'small' | 'medium' | 'large'; // Size options
|
||||
ariaLabel?: string; // For accessibility
|
||||
}
|
||||
|
||||
const colorClasses = {
|
||||
default: 'bg-gradient-to-r from-nord-7 to-nord-8/80 dark:from-nord-8/50 dark:to-nord-9/80 text-nord-0 dark:text-nord-6',
|
||||
success: 'bg-green-500 text-white',
|
||||
warning: 'bg-yellow-500 text-black',
|
||||
error: 'bg-red-500 text-white',
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
small: 'text-xs px-2 py-1',
|
||||
medium: 'text-sm px-3 py-1.5',
|
||||
large: 'text-base px-4 py-2',
|
||||
};
|
||||
|
||||
export function Badge({ children, color = 'default', size = 'medium', ariaLabel }: BadgeProps): JSX.Element {
|
||||
const badgeColorClass = colorClasses[color] || colorClasses.default;
|
||||
const badgeSizeClass = sizeClasses[size] || sizeClasses.medium;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full font-semibold shadow-md hover:shadow-lg transition-all duration-300 ${badgeColorClass} ${badgeSizeClass}`}
|
||||
aria-label={ariaLabel}
|
||||
title={ariaLabel} // Optional: Add title for tooltips
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
interface ExpandButtonProps {
|
||||
expanded: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function ExpandButton({ expanded, onClick }: ExpandButtonProps): JSX.Element {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-2 px-3 py-1 text-sm font-medium text-nord-9 dark:text-nord-8 hover:text-nord-10 dark:hover:text-nord-7 focus:outline-none focus:ring-2 focus:ring-nord-8 dark:focus:ring-nord-9 rounded-lg transition-all duration-300"
|
||||
>
|
||||
<span
|
||||
className={`transform transition-transform duration-300 ${
|
||||
expanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
>
|
||||
{expanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
</span>
|
||||
{expanded ? 'Hide installation' : 'Show installation'}
|
||||
</button>
|
||||
);
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
useState,
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import { Boxes } from 'lucide-react';
|
||||
import { Package } from '../../types';
|
||||
import { InstallGuide } from '../InstallGuide';
|
||||
import { Badge } from './Badge';
|
||||
import { ExpandButton } from './ExpandButton';
|
||||
|
||||
interface PackageCardProps {
|
||||
package: Package;
|
||||
}
|
||||
|
||||
export function PackageCard({ package: pkg }: PackageCardProps): JSX.Element {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="group bg-nord-5 dark:bg-nord-1 rounded-xl shadow-lg border border-nord-4 dark:border-nord-2 hover:shadow-xl transition-all duration-300 overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="p-5">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="p-3 bg-nord-7/10 dark:bg-nord-7/20 rounded-full shadow-md group-hover:scale-105 transition-transform">
|
||||
<Boxes className="h-6 w-6 text-nord-7" />
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-lg text-nord-0 dark:text-nord-6 group-hover:text-nord-8 transition-colors">
|
||||
{pkg.name}
|
||||
</h3>
|
||||
<span className="text-sm text-nord-3 dark:text-nord-4">{pkg.version}</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-nord-2 dark:text-nord-4 line-clamp-2">
|
||||
{pkg.description}
|
||||
</p>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<Badge>{pkg.repository}</Badge>
|
||||
<ExpandButton
|
||||
expanded={expanded}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
aria-label={expanded ? 'Collapse package details' : 'Expand package details'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Section */}
|
||||
{expanded && (
|
||||
<div className="border-t border-nord-4 dark:border-nord-2 bg-nord-6 dark:bg-nord-0 p-5 transition-opacity duration-300">
|
||||
<InstallGuide packageName={pkg.name} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,29 +1,42 @@
|
||||
import {
|
||||
JSX
|
||||
JSX,
|
||||
useMemo
|
||||
} from 'react';
|
||||
|
||||
import { Package } from '../types';
|
||||
import { PackageCard } from './PackageCard';
|
||||
import {
|
||||
Package
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
PackageCard
|
||||
} from './PackageCard';
|
||||
|
||||
interface PackageListProps {
|
||||
packages: Package[];
|
||||
loading: boolean;
|
||||
packages: Package[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function PackageList({ packages, loading }: PackageListProps): JSX.Element {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
export function PackageList({
|
||||
packages,
|
||||
loading
|
||||
}: PackageListProps): JSX.Element {
|
||||
if (loading) return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
{packages.map((pkg) => (
|
||||
<PackageCard key={pkg.name} package={pkg} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const sortedPackages = useMemo(
|
||||
() => [...packages].sort(
|
||||
(a, b) => a.name.localeCompare(b.name)
|
||||
), [packages]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
{sortedPackages.map((pkg) => (
|
||||
<PackageCard key={pkg.name} package={pkg} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -4,6 +4,10 @@ import {
|
||||
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface SearchBarProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
@@ -26,7 +30,7 @@ export function SearchBar({
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="Search packages..."
|
||||
placeholder={translate("SearchBar.placeholder")}
|
||||
className="block w-full pl-10 pr-4 py-3 border-2 border-nord-4 dark:border-nord-2 rounded-xl bg-nord-5 dark:bg-nord-1 focus:ring-2 focus:ring-nord-8 focus:border-transparent text-nord-0 dark:text-nord-6 placeholder-nord-3 dark:placeholder-nord-4 transition-all ease-in-out duration-200 shadow-md hover:shadow-lg"
|
||||
/>
|
||||
|
||||
|
42
src/i18n.tsx
Normal file
42
src/i18n.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import {
|
||||
initReactI18next
|
||||
} from "react-i18next";
|
||||
|
||||
import {
|
||||
LOCALES
|
||||
} from './locales';
|
||||
|
||||
const resources = Object.fromEntries(
|
||||
Object.entries(LOCALES).map(([key, value]) => [
|
||||
key.toLowerCase(),
|
||||
|
||||
{
|
||||
translation: value
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
i18n.use(initReactI18next).use(LanguageDetector).init({
|
||||
resources,
|
||||
fallbackLng: (localStorage.getItem('selectedLanguage') || "en"),
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator'],
|
||||
caches: ['localStorage']
|
||||
}
|
||||
});
|
||||
|
||||
export const translate = (key: string, options?: {
|
||||
[key: string]: any
|
||||
}): string => {
|
||||
return i18n.t(key, options);
|
||||
};
|
||||
|
||||
export default i18n;
|
50
src/locales/en.tsx
Normal file
50
src/locales/en.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const EN = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "All Repositories"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Installation Command",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Copy to clipboard",
|
||||
"success": "Copied!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Snigdha OS Package List",
|
||||
"description": "Browse and search through the official Snigdha OS package repository",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Show Installation",
|
||||
"hide-installation": "Hide Installation"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Search packages..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Showing {{count}} package",
|
||||
"multiple": "Showing {{count}} packages",
|
||||
"plural": "Showing {{count}} packages",
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "An error occurred while fetching packages: {{error}}",
|
||||
"retry-fetching-packages": "Retry"
|
||||
},
|
||||
|
||||
"no-packages-found": "No packages found matching your search.",
|
||||
"footer": "Data Source:"
|
||||
}
|
||||
};
|
17
src/locales/index.tsx
Normal file
17
src/locales/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
EN
|
||||
} from './en'; // English
|
||||
|
||||
import {
|
||||
UK
|
||||
} from './uk'; // Ukrainian
|
||||
|
||||
import {
|
||||
PL
|
||||
} from './pl'; // Polish
|
||||
|
||||
export const LOCALES = {
|
||||
EN,
|
||||
UK,
|
||||
PL
|
||||
};
|
50
src/locales/pl.tsx
Normal file
50
src/locales/pl.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const PL = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "Wszystkie repozytoria"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Polecenie instalacji",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Skopiuj do schowka",
|
||||
"success": "Skopiowano!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Lista pakietów Snigdha OS",
|
||||
"description": "Przeglądaj i wyszukuj w oficjalnym repozytorium pakietów Snigdha OS",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Pokaż instalację",
|
||||
"hide-installation": "Ukryj instalację"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Szukaj pakietów..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Wyświetlanie {{count}} pakietu",
|
||||
"multiple": "Wyświetlanie {{count}} pakietów",
|
||||
"plural": "Wyświetlanie {{count}} pakietów",
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "Wystąpił błąd podczas pobierania pakietów: {{error}}",
|
||||
"retry-fetching-packages": "Spróbuj ponownie"
|
||||
},
|
||||
|
||||
"no-packages-found": "Nie znaleziono pakietów odpowiadających Twojemu wyszukiwaniu.",
|
||||
"footer": "Źródło danych:"
|
||||
}
|
||||
};
|
50
src/locales/uk.tsx
Normal file
50
src/locales/uk.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const UK = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "Усі репозиторії"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Команда для встановлення",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Копіювати в буфер обміну",
|
||||
"success": "Скопійовано!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Список пакетів Snigdha OS",
|
||||
"description": "Переглядайте та шукайте в офіційному репозиторії пакетів Snigdha OS",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Показати інструкцію з встановлення",
|
||||
"hide-installation": "Сховати інструкцію з встановлення"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Шукати пакети..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Показано {{count}} пакет",
|
||||
"multiple": "Показано {{count}} пакети",
|
||||
"plural": "Показано {{count}} пакетів"
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "Сталася помилка при отриманні пакетів: {{error}}",
|
||||
"retry-fetching-packages": "Спробувати ще раз"
|
||||
},
|
||||
|
||||
"no-packages-found": "Не знайдено жодного пакета, що відповідає вашому запиту.",
|
||||
"footer": "Джерело даних:"
|
||||
}
|
||||
};
|
17
src/main.tsx
17
src/main.tsx
@@ -1,10 +1,17 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import {
|
||||
StrictMode
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
createRoot
|
||||
} from 'react-dom/client';
|
||||
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
import './i18n';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
|
@@ -9,33 +9,27 @@ export const MIRRORS: Record<string, {
|
||||
repository: Repository;
|
||||
}> = {
|
||||
'core': {
|
||||
url: 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-core/refs/heads/master/packages.txt',
|
||||
url: 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-pkgbuilds/refs/heads/master/snigdhaos-core/packages.json',
|
||||
repository: ('core' as Repository)
|
||||
},
|
||||
|
||||
'extra': {
|
||||
url: 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-extra/refs/heads/master/packages.txt',
|
||||
url: 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-pkgbuilds/refs/heads/master/snigdhaos-extra/packages.json',
|
||||
repository: ('extra' as Repository)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data from a single mirror (Core or Extra repository)
|
||||
// Fetch data from a single mirror
|
||||
async function fetchFromMirror(url: string, repository: Repository): Promise<Package[]> {
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
|
||||
// Parse the text file content and return a list of packages
|
||||
return text.split('\n').filter(Boolean).map((line) => {
|
||||
const [
|
||||
name,
|
||||
version,
|
||||
...description
|
||||
] = line.split(' ');
|
||||
const data = await response.json();
|
||||
|
||||
// Parse the json file content and return a list of packages
|
||||
return data.map((item: any) => {
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
description: description.join(' '),
|
||||
name: item.name,
|
||||
version: item.version,
|
||||
description: item.description,
|
||||
repository
|
||||
};
|
||||
});
|
||||
|
@@ -10,6 +10,13 @@ export enum Repository {
|
||||
// Type alias for UI themes
|
||||
export type Theme = ('light' | 'dark');
|
||||
|
||||
// Enumeration of supported languages
|
||||
export enum Languages {
|
||||
EN = "English",
|
||||
UK = "Українська",
|
||||
PL = "Polski"
|
||||
}
|
||||
|
||||
// Interface representing a single package
|
||||
export interface Package {
|
||||
/** The name of the package */
|
||||
|
Reference in New Issue
Block a user