mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-04-06 00:36:16 +02:00
Merge branch 'master' into feature/easy-setup-wizard-ux
This commit is contained in:
commit
e0dcd129e6
195
admin/docs/faq.md
Normal file
195
admin/docs/faq.md
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
## General Questions
|
||||||
|
|
||||||
|
### What is N.O.M.A.D.?
|
||||||
|
N.O.M.A.D. (Node for Offline Media, Archives, and Data) is a personal server that gives you access to knowledge, education, and AI assistance without requiring an internet connection. It runs on your own hardware, keeping your data private and accessible anytime.
|
||||||
|
|
||||||
|
### Do I need internet to use N.O.M.A.D.?
|
||||||
|
No — that's the whole point. Once your content is downloaded, everything works offline. You only need internet to:
|
||||||
|
- Download new content
|
||||||
|
- Update the software
|
||||||
|
- Sync the latest versions of Wikipedia, maps, etc.
|
||||||
|
|
||||||
|
### What hardware do I need?
|
||||||
|
N.O.M.A.D. is designed for capable hardware, especially if you want to use the AI features. Recommended:
|
||||||
|
- Modern multi-core CPU
|
||||||
|
- 16GB+ RAM (32GB+ for best AI performance)
|
||||||
|
- SSD storage (size depends on content — 500GB minimum, 2TB+ recommended)
|
||||||
|
- GPU recommended for faster AI responses
|
||||||
|
|
||||||
|
### How much storage do I need?
|
||||||
|
It depends on what you download:
|
||||||
|
- Full Wikipedia: ~95GB
|
||||||
|
- Khan Academy courses: ~50GB
|
||||||
|
- Medical references: ~500MB
|
||||||
|
- US state maps: ~2-3GB each
|
||||||
|
- AI models: 10-40GB depending on model
|
||||||
|
|
||||||
|
Start with essentials and add more as needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Questions
|
||||||
|
|
||||||
|
### How do I add more Wikipedia content?
|
||||||
|
1. Go to **Settings** (hamburger menu → Settings)
|
||||||
|
2. Click **ZIM Manager**
|
||||||
|
3. Browse available content
|
||||||
|
4. Click Download on items you want
|
||||||
|
|
||||||
|
### How do I add more educational courses?
|
||||||
|
1. Open **Kolibri**
|
||||||
|
2. Sign in as an admin
|
||||||
|
3. Go to **Device → Channels**
|
||||||
|
4. Browse and import available channels
|
||||||
|
|
||||||
|
### How current is the content?
|
||||||
|
Content is as current as when it was last downloaded. Wikipedia snapshots are typically updated monthly. Check the file names or descriptions for dates.
|
||||||
|
|
||||||
|
### Can I add my own files?
|
||||||
|
Currently, N.O.M.A.D. uses standard content formats (ZIM files for Kiwix, Kolibri channels for education). Custom content support may be added in future versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### A feature isn't loading or shows a blank page
|
||||||
|
|
||||||
|
**Try these steps:**
|
||||||
|
1. Wait 30 seconds — some features take time to start
|
||||||
|
2. Refresh the page (Ctrl+R or Cmd+R)
|
||||||
|
3. Go back to the Command Center and try again
|
||||||
|
4. Check Settings → System to see if the service is running
|
||||||
|
5. Try restarting the service (Stop, then Start in Apps manager)
|
||||||
|
|
||||||
|
### Maps show a gray/blank area
|
||||||
|
|
||||||
|
The Maps feature requires downloaded map data. If you see a blank area:
|
||||||
|
1. Go to **Settings → Maps Manager**
|
||||||
|
2. Download map regions for your area
|
||||||
|
3. Wait for downloads to complete
|
||||||
|
4. Return to Maps and refresh
|
||||||
|
|
||||||
|
### AI responses are slow
|
||||||
|
|
||||||
|
Local AI requires significant computing power. To improve speed:
|
||||||
|
- Close other applications on the server
|
||||||
|
- Ensure adequate cooling (overheating causes throttling)
|
||||||
|
- Consider using a smaller/faster AI model if available
|
||||||
|
- Add a GPU if your hardware supports it
|
||||||
|
|
||||||
|
### "Service unavailable" or connection errors
|
||||||
|
|
||||||
|
The service might still be starting up. Wait 1-2 minutes and try again.
|
||||||
|
|
||||||
|
If the problem persists:
|
||||||
|
1. Go to **Settings → Apps**
|
||||||
|
2. Find the problematic service
|
||||||
|
3. Click **Restart**
|
||||||
|
4. Wait 30 seconds, then try again
|
||||||
|
|
||||||
|
### Downloads are stuck or failing
|
||||||
|
|
||||||
|
1. Check your internet connection
|
||||||
|
2. Go to **Settings** and check available storage
|
||||||
|
3. If storage is full, delete unused content
|
||||||
|
4. Cancel the stuck download and try again
|
||||||
|
|
||||||
|
### The server won't start
|
||||||
|
|
||||||
|
If you can't access the Command Center at all:
|
||||||
|
1. Verify the server hardware is powered on
|
||||||
|
2. Check network connectivity
|
||||||
|
3. Try accessing directly via the server's IP address
|
||||||
|
4. Check server logs if you have console access
|
||||||
|
|
||||||
|
### I forgot my Kolibri password
|
||||||
|
|
||||||
|
Kolibri passwords are managed separately:
|
||||||
|
1. If you're an admin, you can reset user passwords in Kolibri's user management
|
||||||
|
2. If you forgot the admin password, you may need to reset it via command line (contact your administrator)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updates and Maintenance
|
||||||
|
|
||||||
|
### How do I update N.O.M.A.D.?
|
||||||
|
1. Go to **Settings → Check for Updates**
|
||||||
|
2. If an update is available, click to install
|
||||||
|
3. The system will download updates and restart automatically
|
||||||
|
4. This typically takes 2-5 minutes
|
||||||
|
|
||||||
|
### Should I update regularly?
|
||||||
|
Yes, while you have internet access. Updates include:
|
||||||
|
- Bug fixes
|
||||||
|
- New features
|
||||||
|
- Security improvements
|
||||||
|
- Performance enhancements
|
||||||
|
|
||||||
|
### How do I update content (Wikipedia, etc.)?
|
||||||
|
Content updates are separate from software updates:
|
||||||
|
1. Go to **Settings → ZIM Manager**
|
||||||
|
2. Check for newer versions of your installed content
|
||||||
|
3. Download updated versions as needed
|
||||||
|
|
||||||
|
Tip: New Wikipedia snapshots are released approximately monthly.
|
||||||
|
|
||||||
|
### What happens if an update fails?
|
||||||
|
The system is designed to recover gracefully. If an update fails:
|
||||||
|
1. The previous version should continue working
|
||||||
|
2. Try the update again later
|
||||||
|
3. Check Settings → System for error messages
|
||||||
|
|
||||||
|
### Command-Line Maintenance
|
||||||
|
|
||||||
|
For advanced troubleshooting or when you can't access the web interface, N.O.M.A.D. includes helper scripts in `/opt/project-nomad`:
|
||||||
|
|
||||||
|
**Start all services:**
|
||||||
|
```bash
|
||||||
|
sudo bash /opt/project-nomad/start_nomad.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Stop all services:**
|
||||||
|
```bash
|
||||||
|
sudo bash /opt/project-nomad/stop_nomad.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update Command Center:**
|
||||||
|
```bash
|
||||||
|
sudo bash /opt/project-nomad/update_nomad.sh
|
||||||
|
```
|
||||||
|
*Note: This updates the Command Center only, not individual apps. Update apps through the web interface.*
|
||||||
|
|
||||||
|
**Uninstall N.O.M.A.D.:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/master/install/uninstall_nomad.sh -o uninstall_nomad.sh
|
||||||
|
sudo bash uninstall_nomad.sh
|
||||||
|
```
|
||||||
|
*Warning: This cannot be undone. All data will be deleted.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Privacy and Security
|
||||||
|
|
||||||
|
### Is my data private?
|
||||||
|
Yes. N.O.M.A.D. runs entirely on your hardware. Your searches, AI conversations, and usage data never leave your server.
|
||||||
|
|
||||||
|
### Can others access my server?
|
||||||
|
By default, N.O.M.A.D. is accessible on your local network. Anyone on the same network can access it. For public networks, consider additional security measures.
|
||||||
|
|
||||||
|
### Does the AI send data anywhere?
|
||||||
|
No. The AI runs completely locally. Your conversations are not sent to any external service.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting More Help
|
||||||
|
|
||||||
|
### The AI can help
|
||||||
|
Try asking Open WebUI for help. The local AI can answer questions about many topics, including technical troubleshooting.
|
||||||
|
|
||||||
|
### Check the documentation
|
||||||
|
You're in the docs now. Use the menu to find specific topics.
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
See what's changed in each version: **[Release Notes](/docs/release-notes)**
|
||||||
238
admin/docs/getting-started.md
Normal file
238
admin/docs/getting-started.md
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
# Getting Started with N.O.M.A.D.
|
||||||
|
|
||||||
|
This guide will help you install and set up your N.O.M.A.D. server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
N.O.M.A.D. runs on any **Debian-based Linux** system (Ubuntu recommended). The installation is terminal-based, and everything is accessed through a web browser — no desktop environment needed.
|
||||||
|
|
||||||
|
**Minimum Specs** (Command Center only):
|
||||||
|
- 2 GHz dual-core processor
|
||||||
|
- 4 GB RAM
|
||||||
|
- 5 GB free storage
|
||||||
|
- Internet connection (for initial install)
|
||||||
|
|
||||||
|
**Recommended Specs** (with AI features):
|
||||||
|
- AMD Ryzen 7 / Intel Core i7 or better
|
||||||
|
- 32 GB RAM
|
||||||
|
- NVIDIA RTX 3060 or better (more VRAM = larger AI models)
|
||||||
|
- 250 GB+ free storage (SSD preferred)
|
||||||
|
|
||||||
|
The Command Center itself is lightweight — your hardware requirements depend on which tools and content you choose to install.
|
||||||
|
|
||||||
|
### Install N.O.M.A.D.
|
||||||
|
|
||||||
|
Open a terminal and run these two commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/master/install/install_nomad.sh -o install_nomad.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash install_nomad.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. Once the install finishes, open a browser and go to:
|
||||||
|
|
||||||
|
- **Same machine:** `http://localhost:8080`
|
||||||
|
- **Other devices on your network:** `http://YOUR_SERVER_IP:8080`
|
||||||
|
|
||||||
|
### About Internet & Privacy
|
||||||
|
|
||||||
|
N.O.M.A.D. is designed for offline use. Internet is only needed:
|
||||||
|
- During initial installation
|
||||||
|
- When downloading additional content
|
||||||
|
|
||||||
|
There is **zero telemetry** — your data stays on your device.
|
||||||
|
|
||||||
|
### About Security
|
||||||
|
|
||||||
|
N.O.M.A.D. has no built-in authentication — it's designed to be open and accessible. If you expose it on a network, consider using firewall rules to control which ports are accessible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## After Installation
|
||||||
|
|
||||||
|
### 1. Run the Easy Setup Wizard
|
||||||
|
|
||||||
|
If this is your first time using N.O.M.A.D., the Easy Setup wizard will help you:
|
||||||
|
- Choose which apps to enable
|
||||||
|
- Download map regions for your area
|
||||||
|
- Select knowledge collections (Wikipedia, medical references, etc.)
|
||||||
|
|
||||||
|
**[Launch Easy Setup →](/easy-setup)**
|
||||||
|
|
||||||
|
The wizard walks you through four simple steps:
|
||||||
|
1. **Apps** — Choose additional tools like CyberChef or FlatNotes
|
||||||
|
2. **Maps** — Select geographic regions for offline maps
|
||||||
|
3. **ZIM Files** — Choose reference collections (Wikipedia, medical, survival guides)
|
||||||
|
4. **Review** — Confirm your selections and start downloading
|
||||||
|
|
||||||
|
### 2. Wait for Downloads to Complete
|
||||||
|
|
||||||
|
Depending on what you selected, downloads may take a while. You can:
|
||||||
|
- Monitor progress in the Settings area
|
||||||
|
- Continue using features that are already installed
|
||||||
|
- Leave your server running overnight for large downloads
|
||||||
|
|
||||||
|
### 3. Explore Your Content
|
||||||
|
|
||||||
|
Once downloads complete, you're ready to go. Your content works offline whenever you need it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Understanding Your Tools
|
||||||
|
|
||||||
|
### Kiwix — Your Offline Library
|
||||||
|
|
||||||
|
Kiwix stores compressed versions of websites and references that work without internet.
|
||||||
|
|
||||||
|
**What's included:**
|
||||||
|
- Full Wikipedia (millions of articles)
|
||||||
|
- Medical references and first aid guides
|
||||||
|
- How-to guides and survival information
|
||||||
|
- Classic books from Project Gutenberg
|
||||||
|
|
||||||
|
**How to use it:**
|
||||||
|
1. Click **Kiwix** from the Command Center
|
||||||
|
2. Choose a collection (like Wikipedia)
|
||||||
|
3. Search or browse just like the regular website
|
||||||
|
|
||||||
|
**[Open Kiwix →](/kiwix)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Kolibri — Offline Education
|
||||||
|
|
||||||
|
Kolibri provides complete educational courses that work offline.
|
||||||
|
|
||||||
|
**What's included:**
|
||||||
|
- Khan Academy video courses
|
||||||
|
- Math, science, reading, and more
|
||||||
|
- Progress tracking for learners
|
||||||
|
- Works for all ages
|
||||||
|
|
||||||
|
**How to use it:**
|
||||||
|
1. Click **Kolibri** from the Command Center
|
||||||
|
2. Sign in or create a learner account
|
||||||
|
3. Browse courses and start learning
|
||||||
|
|
||||||
|
**Tip:** Kolibri supports multiple users. Create accounts for each family member to track individual progress.
|
||||||
|
|
||||||
|
**[Open Kolibri →](/kolibri)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Open WebUI — Your AI Assistant
|
||||||
|
|
||||||
|
Chat with a local AI that runs entirely on your server — no internet needed.
|
||||||
|
|
||||||
|
**What can it do:**
|
||||||
|
- Answer questions on any topic
|
||||||
|
- Explain complex concepts simply
|
||||||
|
- Help with writing and editing
|
||||||
|
- Brainstorm ideas
|
||||||
|
- Assist with problem-solving
|
||||||
|
|
||||||
|
**How to use it:**
|
||||||
|
1. Click **Open WebUI** from the Command Center
|
||||||
|
2. Type your question or request
|
||||||
|
3. The AI responds in conversational style
|
||||||
|
|
||||||
|
**Tip:** Be specific in your questions. Instead of "tell me about plants," try "what vegetables grow well in shade?"
|
||||||
|
|
||||||
|
**[Open AI Chat →](/openwebui)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Maps — Offline Navigation
|
||||||
|
|
||||||
|
View maps without internet. Download the regions you need before going offline.
|
||||||
|
|
||||||
|
**How to use it:**
|
||||||
|
1. Click **Maps** from the Command Center
|
||||||
|
2. Navigate by dragging and zooming
|
||||||
|
3. Search for locations using the search bar
|
||||||
|
|
||||||
|
**To add more map regions:**
|
||||||
|
1. Go to **Settings → Maps Manager**
|
||||||
|
2. Select the regions you need
|
||||||
|
3. Click Download
|
||||||
|
|
||||||
|
**Tip:** Download maps for areas you travel to frequently, plus neighboring regions just in case.
|
||||||
|
|
||||||
|
**[Open Maps →](/maps)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Managing Your Server
|
||||||
|
|
||||||
|
### Adding More Content
|
||||||
|
|
||||||
|
As your needs change, you can add more content anytime:
|
||||||
|
|
||||||
|
- **More apps:** Settings → Apps
|
||||||
|
- **More references:** Settings → ZIM Manager
|
||||||
|
- **More map regions:** Settings → Maps Manager
|
||||||
|
- **More educational content:** Through Kolibri's built-in content browser
|
||||||
|
|
||||||
|
### Keeping Things Updated
|
||||||
|
|
||||||
|
While you have internet, periodically check for updates:
|
||||||
|
|
||||||
|
1. Go to **Settings → Check for Updates**
|
||||||
|
2. If updates are available, click to install
|
||||||
|
3. Wait for the update to complete (your server will restart)
|
||||||
|
|
||||||
|
Content updates (Wikipedia, maps, etc.) can be managed separately from software updates.
|
||||||
|
|
||||||
|
### Monitoring System Health
|
||||||
|
|
||||||
|
Check on your server anytime:
|
||||||
|
|
||||||
|
1. Go to **Settings → System**
|
||||||
|
2. View CPU, memory, and storage usage
|
||||||
|
3. Check system uptime and status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips for Best Results
|
||||||
|
|
||||||
|
### Before Going Offline
|
||||||
|
|
||||||
|
- **Update everything** — Run software and content updates
|
||||||
|
- **Download what you need** — Maps, references, educational content
|
||||||
|
- **Test it** — Make sure features work while you still have internet to troubleshoot
|
||||||
|
|
||||||
|
### Storage Management
|
||||||
|
|
||||||
|
Your server has limited storage. Prioritize:
|
||||||
|
- Content you'll actually use
|
||||||
|
- Critical references (medical, survival)
|
||||||
|
- Maps for your region
|
||||||
|
- Educational content matching your needs
|
||||||
|
|
||||||
|
Check storage usage in **Settings → System**.
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
- **In-app docs:** You're reading them now
|
||||||
|
- **AI assistant:** Ask Open WebUI for help with almost anything
|
||||||
|
- **Release notes:** See what's new in each version
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
You're ready to use N.O.M.A.D. Here are some things to try:
|
||||||
|
|
||||||
|
1. **Look something up** — Search for a topic in Kiwix
|
||||||
|
2. **Learn something** — Start a Khan Academy course in Kolibri
|
||||||
|
3. **Ask a question** — Chat with the AI in Open WebUI
|
||||||
|
4. **Explore maps** — Find your neighborhood in the Maps viewer
|
||||||
|
|
||||||
|
Enjoy your offline knowledge server!
|
||||||
|
|
@ -1,71 +1,67 @@
|
||||||
# Lorem Ipsum Markdown Showcase
|
# Welcome to Project N.O.M.A.D.
|
||||||
|
|
||||||
|
Your personal offline knowledge server is ready to use.
|
||||||
|
|
||||||
|
## What is N.O.M.A.D.?
|
||||||
|
|
||||||
|
**N.O.M.A.D.** stands for **Node for Offline Media, Archives, and Data**. It's your personal server for accessing knowledge, education, and AI assistance — even when you have no internet connection.
|
||||||
|
|
||||||
|
Think of it as having Wikipedia, Khan Academy, an AI assistant, and offline maps all in one place, running on hardware you control.
|
||||||
|
|
||||||
|
## What Can You Do?
|
||||||
|
|
||||||
|
### Browse Offline Knowledge
|
||||||
|
Access millions of Wikipedia articles, medical references, how-to guides, and ebooks — all stored locally on your server. No internet required.
|
||||||
|
|
||||||
|
**[Open Kiwix →](/kiwix)**
|
||||||
|
|
||||||
|
### Learn Something New
|
||||||
|
Khan Academy courses covering math, science, economics, and more. Complete with videos and exercises, all available offline.
|
||||||
|
|
||||||
|
**[Open Kolibri →](/kolibri)**
|
||||||
|
|
||||||
|
### Chat with AI
|
||||||
|
Ask questions, get explanations, brainstorm ideas, or get help with writing. Your local AI assistant works completely offline.
|
||||||
|
|
||||||
|
**[Open AI Chat →](/openwebui)**
|
||||||
|
|
||||||
|
### View Offline Maps
|
||||||
|
Navigate and explore maps without an internet connection. Download regions you need before going offline.
|
||||||
|
|
||||||
|
**[Open Maps →](/maps)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Getting Started
|
||||||
|
|
||||||
This document serves as a comprehensive example of **Markdown's various formatting possibilities**, using the classic *Lorem Ipsum* text as its content. From basic text styling to lists, code blocks, and tables, you'll find a demonstration of common Markdown features here.
|
**New to N.O.M.A.D.?** Use the Easy Setup wizard to configure your server and download content collections.
|
||||||
|
|
||||||
|
**[Run Easy Setup →](/easy-setup)**
|
||||||
|
|
||||||
|
Or explore the **[Getting Started Guide](/docs/getting-started)** for a walkthrough of all features.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Basic Text Formatting
|
## Quick Links
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
| I want to... | Go here |
|
||||||
|
|--------------|---------|
|
||||||
* This text is **bold**.
|
| Download more content | [Install Apps](/apps) |
|
||||||
* This text is *italic*.
|
| Add Wikipedia/reference content | [ZIM Manager](/settings/zim-manager) |
|
||||||
* This text is ***bold and italic***.
|
| Download map regions | [Maps Manager](/settings/maps-manager) |
|
||||||
* This text is ~~struck through~~.
|
| Check for updates | [System Update](/settings/updates) |
|
||||||
* You can also use `backticks` for `inline code`.
|
| View system status | [Settings](/settings) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Headers
|
## Keeping Your Server Updated
|
||||||
|
|
||||||
Markdown supports up to six levels of headers.
|
N.O.M.A.D. works best when kept up to date while you have internet access. This ensures you have the latest:
|
||||||
|
- Software features and bug fixes
|
||||||
|
- Wikipedia and reference content
|
||||||
|
- Educational materials
|
||||||
|
- AI model improvements
|
||||||
|
|
||||||
# Header 1
|
When you go offline, you'll have everything you need — the last synced versions of all your content.
|
||||||
## Header 2
|
|
||||||
### Header 3
|
|
||||||
#### Header 4
|
|
||||||
##### Header 5
|
|
||||||
###### Header 6
|
|
||||||
|
|
||||||
---
|
**[Check for Updates →](/settings/updates)**
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
### Unordered List
|
|
||||||
|
|
||||||
* Lorem ipsum dolor sit amet.
|
|
||||||
* Consectetur adipiscing elit.
|
|
||||||
* Sed do eiusmod tempor.
|
|
||||||
* Incididunt ut labore et dolore magna.
|
|
||||||
* Aliqua ut enim ad minim veniam.
|
|
||||||
|
|
||||||
### Ordered List
|
|
||||||
|
|
||||||
1. Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
||||||
2. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
||||||
3. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Blockquotes
|
|
||||||
|
|
||||||
> "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
|
||||||
>
|
|
||||||
> — John Doe, *Lorem Ipsum Anthology*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Blocks
|
|
||||||
|
|
||||||
```python
|
|
||||||
def fibonacci(n):
|
|
||||||
a, b = 0, 1
|
|
||||||
for i in range(n):
|
|
||||||
print(a, end=" ")
|
|
||||||
a, b = b, a + b
|
|
||||||
|
|
||||||
fibonacci(10)
|
|
||||||
|
|
|
||||||
190
admin/docs/use-cases.md
Normal file
190
admin/docs/use-cases.md
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
# What Can You Do With N.O.M.A.D.?
|
||||||
|
|
||||||
|
N.O.M.A.D. is designed to be your information lifeline when internet isn't available. Here's how different people use it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Emergency Preparedness
|
||||||
|
|
||||||
|
When disasters strike, internet and cell service often go down first. N.O.M.A.D. keeps critical information at your fingertips.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Look up first aid and emergency medical procedures
|
||||||
|
- Access survival guides and emergency protocols
|
||||||
|
- Find information about water purification, food storage, shelter building
|
||||||
|
- Use offline maps to navigate when GPS services are degraded
|
||||||
|
- Research plant identification, weather patterns, radio frequencies
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Medical Library ZIM collection
|
||||||
|
- Survival/Prepper reference guides
|
||||||
|
- Maps for your region and evacuation routes
|
||||||
|
- Wikipedia (searchable for almost any topic)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Homeschooling and Education
|
||||||
|
|
||||||
|
Teach your children anywhere, with or without internet. Complete curriculum available offline.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Access Khan Academy's full course library (math, science, reading, history)
|
||||||
|
- Track progress for multiple students
|
||||||
|
- Supplement with Wikipedia for research projects
|
||||||
|
- Use the AI as a patient tutor for any subject
|
||||||
|
- Access classic literature through Project Gutenberg
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Khan Academy courses via Kolibri
|
||||||
|
- Wikipedia for Schools (curated for younger learners)
|
||||||
|
- Project Gutenberg (classic books)
|
||||||
|
- Educational ZIM collections
|
||||||
|
|
||||||
|
**Tip:** Create separate Kolibri accounts for each child to track their individual progress.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Off-Grid Living
|
||||||
|
|
||||||
|
Living away from reliable internet doesn't mean living without information.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Research DIY projects and repairs
|
||||||
|
- Look up gardening, animal husbandry, food preservation
|
||||||
|
- Access medical references for remote healthcare
|
||||||
|
- Learn new skills through educational videos
|
||||||
|
- Get AI help with planning and problem-solving
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- How-to and DIY reference collections
|
||||||
|
- Medical and first aid guides
|
||||||
|
- Agricultural and homesteading references
|
||||||
|
- Maps for your rural area
|
||||||
|
- Practical skills courses in Kolibri
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Remote Work Sites
|
||||||
|
|
||||||
|
Construction sites, research stations, ships, and remote facilities often lack reliable internet.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Access technical references and documentation
|
||||||
|
- Use AI for writing assistance and analysis
|
||||||
|
- Look up regulations, standards, and procedures
|
||||||
|
- Provide educational resources for workers
|
||||||
|
- Maintain communication records with note-taking apps
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Industry-specific technical references
|
||||||
|
- Relevant Wikipedia categories
|
||||||
|
- Maps of work areas
|
||||||
|
- Documentation and compliance guides
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Travel and Expeditions
|
||||||
|
|
||||||
|
International travel, cruises, camping trips — stay informed anywhere.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Access maps without expensive roaming data
|
||||||
|
- Research destinations, history, and culture
|
||||||
|
- Translate concepts with AI assistance
|
||||||
|
- Identify plants, animals, and geological features
|
||||||
|
- Access travel health information
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Maps for destination countries/regions
|
||||||
|
- Wikipedia in relevant languages
|
||||||
|
- Medical/health references
|
||||||
|
- Cultural and historical content
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Privacy-Conscious Users
|
||||||
|
|
||||||
|
Some people simply prefer to keep their searches and questions private.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Search Wikipedia without being tracked
|
||||||
|
- Ask AI questions that stay on your own hardware
|
||||||
|
- Learn about sensitive topics privately
|
||||||
|
- Keep your intellectual curiosity to yourself
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- All data stays on your server
|
||||||
|
- No search history sent to companies
|
||||||
|
- No AI conversations leave your network
|
||||||
|
- You control your own information
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Medical Reference
|
||||||
|
|
||||||
|
When you can't reach a doctor, having reliable medical information can be critical.
|
||||||
|
|
||||||
|
**What you can access:**
|
||||||
|
- NHS Medicines A-Z (drug information and interactions)
|
||||||
|
- Medical Library (field medicine, emergency procedures)
|
||||||
|
- First aid guides
|
||||||
|
- Anatomy and physiology references
|
||||||
|
- Disease and symptom information
|
||||||
|
|
||||||
|
**Important:** Medical references are for information only. They don't replace professional medical care. In emergencies, always seek professional help when possible.
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Medical Essentials ZIM collection
|
||||||
|
- NHS Medicines reference
|
||||||
|
- First aid and emergency medicine guides
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Academic Research
|
||||||
|
|
||||||
|
Students and researchers can work without depending on university networks.
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- Access Wikipedia's extensive article database
|
||||||
|
- Use AI for research assistance and summarization
|
||||||
|
- Work on papers and projects offline
|
||||||
|
- Cross-reference multiple sources
|
||||||
|
- Take notes with built-in tools
|
||||||
|
|
||||||
|
**Recommended content:**
|
||||||
|
- Full Wikipedia
|
||||||
|
- Academic and educational references
|
||||||
|
- Subject-specific ZIM collections
|
||||||
|
- Note-taking apps (FlatNotes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setting Up for Your Use Case
|
||||||
|
|
||||||
|
### Step 1: Identify Your Needs
|
||||||
|
What situations might you face without internet? What information would you need?
|
||||||
|
|
||||||
|
### Step 2: Prioritize Content
|
||||||
|
Storage is limited. Focus on:
|
||||||
|
1. Critical safety information (medical, emergency)
|
||||||
|
2. Content matching your primary use case
|
||||||
|
3. General reference (Wikipedia)
|
||||||
|
4. Nice-to-have additions
|
||||||
|
|
||||||
|
### Step 3: Download While You Can
|
||||||
|
Keep your server updated while you have internet. You never know when you'll need to go offline.
|
||||||
|
|
||||||
|
### Step 4: Practice
|
||||||
|
Try using N.O.M.A.D. before you need it. Familiarity with the tools makes them more useful in a crisis.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Need Something Specific?
|
||||||
|
|
||||||
|
N.O.M.A.D. content is customizable. If you don't see what you need:
|
||||||
|
|
||||||
|
1. **Check ZIM Remote Explorer** — Thousands of ZIM files are available
|
||||||
|
2. **Browse Kolibri channels** — Educational content for many subjects
|
||||||
|
3. **Request features** — Let us know what content would help you
|
||||||
|
|
||||||
|
Your offline server, your content choices.
|
||||||
88
admin/inertia/components/CategoryCard.tsx
Normal file
88
admin/inertia/components/CategoryCard.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { formatBytes } from '~/lib/util'
|
||||||
|
import DynamicIcon, { DynamicIconName } from './DynamicIcon'
|
||||||
|
import { CuratedCategory, CategoryTier } from '../../types/downloads'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { IconChevronRight, IconCircleCheck } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
export interface CategoryCardProps {
|
||||||
|
category: CuratedCategory
|
||||||
|
selectedTier?: CategoryTier | null
|
||||||
|
onClick?: (category: CuratedCategory) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoryCard: React.FC<CategoryCardProps> = ({ category, selectedTier, onClick }) => {
|
||||||
|
// Calculate total size range across all tiers
|
||||||
|
const getTierTotalSize = (tier: CategoryTier, allTiers: CategoryTier[]): number => {
|
||||||
|
let total = tier.resources.reduce((acc, r) => acc + r.size_mb * 1024 * 1024, 0)
|
||||||
|
|
||||||
|
// Add included tier sizes recursively
|
||||||
|
if (tier.includesTier) {
|
||||||
|
const includedTier = allTiers.find(t => t.slug === tier.includesTier)
|
||||||
|
if (includedTier) {
|
||||||
|
total += getTierTotalSize(includedTier, allTiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
const minSize = getTierTotalSize(category.tiers[0], category.tiers)
|
||||||
|
const maxSize = getTierTotalSize(category.tiers[category.tiers.length - 1], category.tiers)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col bg-desert-green rounded-lg p-6 text-white border shadow-sm hover:shadow-lg transition-shadow cursor-pointer h-80',
|
||||||
|
selectedTier ? 'border-lime-400 border-2' : 'border-desert-green'
|
||||||
|
)}
|
||||||
|
onClick={() => onClick?.(category)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center mb-4">
|
||||||
|
<div className="flex justify-between w-full items-center">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<DynamicIcon icon={category.icon as DynamicIconName} className="w-6 h-6 mr-2" />
|
||||||
|
<h3 className="text-lg font-semibold">{category.name}</h3>
|
||||||
|
</div>
|
||||||
|
{selectedTier ? (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<IconCircleCheck className="w-5 h-5 text-lime-400" />
|
||||||
|
<span className="text-lime-400 text-sm ml-1">{selectedTier.name}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<IconChevronRight className="w-5 h-5 text-white opacity-70" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-gray-200 grow">{category.description}</p>
|
||||||
|
|
||||||
|
<div className="mt-4 pt-4 border-t border-white/20">
|
||||||
|
<p className="text-sm text-gray-300 mb-2">
|
||||||
|
{category.tiers.length} tiers available
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{category.tiers.map((tier) => (
|
||||||
|
<span
|
||||||
|
key={tier.slug}
|
||||||
|
className={classNames(
|
||||||
|
'text-xs px-2 py-1 rounded',
|
||||||
|
tier.recommended
|
||||||
|
? 'bg-lime-500/30 text-lime-200'
|
||||||
|
: 'bg-white/10 text-gray-300',
|
||||||
|
selectedTier?.slug === tier.slug && 'ring-2 ring-lime-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tier.name}
|
||||||
|
{tier.recommended && ' *'}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-300 text-xs mt-3">
|
||||||
|
Size: {formatBytes(minSize, 1)} - {formatBytes(maxSize, 1)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryCard
|
||||||
122
admin/inertia/components/StorageProjectionBar.tsx
Normal file
122
admin/inertia/components/StorageProjectionBar.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
import classNames from '~/lib/classNames'
|
||||||
|
import { formatBytes } from '~/lib/util'
|
||||||
|
import { IconAlertTriangle, IconServer } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
interface StorageProjectionBarProps {
|
||||||
|
totalSize: number // Total disk size in bytes
|
||||||
|
currentUsed: number // Currently used space in bytes
|
||||||
|
projectedAddition: number // Additional space that will be used in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StorageProjectionBar({
|
||||||
|
totalSize,
|
||||||
|
currentUsed,
|
||||||
|
projectedAddition,
|
||||||
|
}: StorageProjectionBarProps) {
|
||||||
|
const projectedTotal = currentUsed + projectedAddition
|
||||||
|
const currentPercent = (currentUsed / totalSize) * 100
|
||||||
|
const projectedPercent = (projectedAddition / totalSize) * 100
|
||||||
|
const projectedTotalPercent = (projectedTotal / totalSize) * 100
|
||||||
|
const remainingAfter = totalSize - projectedTotal
|
||||||
|
const willExceed = projectedTotal > totalSize
|
||||||
|
|
||||||
|
// Determine warning level based on projected total
|
||||||
|
const getProjectedColor = () => {
|
||||||
|
if (willExceed) return 'bg-desert-red'
|
||||||
|
if (projectedTotalPercent >= 90) return 'bg-desert-orange'
|
||||||
|
if (projectedTotalPercent >= 75) return 'bg-desert-tan'
|
||||||
|
return 'bg-desert-olive'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProjectedGlow = () => {
|
||||||
|
if (willExceed) return 'shadow-desert-red/50'
|
||||||
|
if (projectedTotalPercent >= 90) return 'shadow-desert-orange/50'
|
||||||
|
if (projectedTotalPercent >= 75) return 'shadow-desert-tan/50'
|
||||||
|
return 'shadow-desert-olive/50'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-desert-stone-lighter/30 rounded-lg p-4 border border-desert-stone-light">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<IconServer size={20} className="text-desert-green" />
|
||||||
|
<span className="font-semibold text-desert-green">Storage</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-desert-stone-dark font-mono">
|
||||||
|
{formatBytes(projectedTotal, 1)} / {formatBytes(totalSize, 1)}
|
||||||
|
{projectedAddition > 0 && (
|
||||||
|
<span className="text-desert-stone ml-2">
|
||||||
|
(+{formatBytes(projectedAddition, 1)} selected)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress bar */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="h-8 bg-desert-green-lighter/20 rounded-lg border border-desert-stone-light overflow-hidden">
|
||||||
|
{/* Current usage - darker/subdued */}
|
||||||
|
<div
|
||||||
|
className="absolute h-full bg-desert-stone transition-all duration-300"
|
||||||
|
style={{ width: `${Math.min(currentPercent, 100)}%` }}
|
||||||
|
/>
|
||||||
|
{/* Projected addition - highlighted */}
|
||||||
|
{projectedAddition > 0 && (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute h-full transition-all duration-300 shadow-lg',
|
||||||
|
getProjectedColor(),
|
||||||
|
getProjectedGlow()
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
left: `${Math.min(currentPercent, 100)}%`,
|
||||||
|
width: `${Math.min(projectedPercent, 100 - currentPercent)}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Percentage label */}
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute top-1/2 -translate-y-1/2 font-bold text-sm',
|
||||||
|
projectedTotalPercent > 15
|
||||||
|
? 'left-3 text-desert-white drop-shadow-md'
|
||||||
|
: 'right-3 text-desert-green'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{Math.round(projectedTotalPercent)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend and warnings */}
|
||||||
|
<div className="flex items-center justify-between mt-3">
|
||||||
|
<div className="flex items-center gap-4 text-xs">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="w-3 h-3 rounded bg-desert-stone" />
|
||||||
|
<span className="text-desert-stone-dark">Current ({formatBytes(currentUsed, 1)})</span>
|
||||||
|
</div>
|
||||||
|
{projectedAddition > 0 && (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className={classNames('w-3 h-3 rounded', getProjectedColor())} />
|
||||||
|
<span className="text-desert-stone-dark">
|
||||||
|
Selected (+{formatBytes(projectedAddition, 1)})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{willExceed ? (
|
||||||
|
<div className="flex items-center gap-1.5 text-desert-red text-xs font-medium">
|
||||||
|
<IconAlertTriangle size={14} />
|
||||||
|
<span>Exceeds available space by {formatBytes(projectedTotal - totalSize, 1)}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-xs text-desert-stone">
|
||||||
|
{formatBytes(remainingAfter, 1)} will remain free
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
205
admin/inertia/components/TierSelectionModal.tsx
Normal file
205
admin/inertia/components/TierSelectionModal.tsx
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
|
import { IconX, IconCheck, IconInfoCircle } from '@tabler/icons-react'
|
||||||
|
import { CuratedCategory, CategoryTier, CategoryResource } from '../../types/downloads'
|
||||||
|
import { formatBytes } from '~/lib/util'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import DynamicIcon, { DynamicIconName } from './DynamicIcon'
|
||||||
|
|
||||||
|
interface TierSelectionModalProps {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
category: CuratedCategory | null
|
||||||
|
selectedTierSlug?: string | null
|
||||||
|
onSelectTier: (category: CuratedCategory, tier: CategoryTier) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TierSelectionModal: React.FC<TierSelectionModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
category,
|
||||||
|
selectedTierSlug,
|
||||||
|
onSelectTier,
|
||||||
|
}) => {
|
||||||
|
if (!category) return null
|
||||||
|
|
||||||
|
// Get all resources for a tier (including inherited resources)
|
||||||
|
const getAllResourcesForTier = (tier: CategoryTier): CategoryResource[] => {
|
||||||
|
const resources = [...tier.resources]
|
||||||
|
|
||||||
|
if (tier.includesTier) {
|
||||||
|
const includedTier = category.tiers.find(t => t.slug === tier.includesTier)
|
||||||
|
if (includedTier) {
|
||||||
|
resources.unshift(...getAllResourcesForTier(includedTier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTierTotalSize = (tier: CategoryTier): number => {
|
||||||
|
return getAllResourcesForTier(tier).reduce((acc, r) => acc + r.size_mb * 1024 * 1024, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black/50" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="w-full max-w-4xl transform overflow-hidden rounded-lg bg-white shadow-xl transition-all">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-desert-green px-6 py-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<DynamicIcon
|
||||||
|
icon={category.icon as DynamicIconName}
|
||||||
|
className="w-8 h-8 text-white mr-3"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Dialog.Title className="text-xl font-semibold text-white">
|
||||||
|
{category.name}
|
||||||
|
</Dialog.Title>
|
||||||
|
<p className="text-sm text-gray-200">{category.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-white/70 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<IconX size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-6">
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
Select a tier based on your storage capacity and needs. Higher tiers include all content from lower tiers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{category.tiers.map((tier, index) => {
|
||||||
|
const allResources = getAllResourcesForTier(tier)
|
||||||
|
const totalSize = getTierTotalSize(tier)
|
||||||
|
const isSelected = selectedTierSlug === tier.slug
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={tier.slug}
|
||||||
|
onClick={() => onSelectTier(category, tier)}
|
||||||
|
className={classNames(
|
||||||
|
'border-2 rounded-lg p-5 cursor-pointer transition-all',
|
||||||
|
isSelected
|
||||||
|
? 'border-desert-green bg-desert-green/5 shadow-md'
|
||||||
|
: 'border-gray-200 hover:border-desert-green/50 hover:shadow-sm',
|
||||||
|
tier.recommended && !isSelected && 'border-lime-500/50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
|
{tier.name}
|
||||||
|
</h3>
|
||||||
|
{tier.recommended && (
|
||||||
|
<span className="text-xs bg-lime-500 text-white px-2 py-0.5 rounded">
|
||||||
|
Recommended
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{tier.includesTier && (
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
(includes {category.tiers.find(t => t.slug === tier.includesTier)?.name})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-sm mb-3">{tier.description}</p>
|
||||||
|
|
||||||
|
{/* Resources preview */}
|
||||||
|
<div className="bg-gray-50 rounded p-3">
|
||||||
|
<p className="text-xs text-gray-500 mb-2 font-medium">
|
||||||
|
{allResources.length} resources included:
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{allResources.map((resource, idx) => (
|
||||||
|
<div key={idx} className="flex items-start text-sm">
|
||||||
|
<IconCheck size={14} className="text-desert-green mr-1.5 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-700">{resource.title}</span>
|
||||||
|
<span className="text-gray-400 text-xs ml-1">
|
||||||
|
({formatBytes(resource.size_mb * 1024 * 1024, 0)})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-4 text-right flex-shrink-0">
|
||||||
|
<div className="text-lg font-semibold text-gray-900">
|
||||||
|
{formatBytes(totalSize, 1)}
|
||||||
|
</div>
|
||||||
|
<div className={classNames(
|
||||||
|
'w-6 h-6 rounded-full border-2 flex items-center justify-center mt-2 ml-auto',
|
||||||
|
isSelected
|
||||||
|
? 'border-desert-green bg-desert-green'
|
||||||
|
: 'border-gray-300'
|
||||||
|
)}>
|
||||||
|
{isSelected && <IconCheck size={16} className="text-white" />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info note */}
|
||||||
|
<div className="mt-6 flex items-start gap-2 text-sm text-gray-500 bg-blue-50 p-3 rounded">
|
||||||
|
<IconInfoCircle size={18} className="text-blue-500 flex-shrink-0 mt-0.5" />
|
||||||
|
<p>
|
||||||
|
You can change your selection at any time. Downloads will begin when you complete the setup wizard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="bg-gray-50 px-6 py-4 flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 text-gray-700 hover:text-gray-900 transition-colors"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TierSelectionModal
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
import { Head, router } from '@inertiajs/react'
|
import { Head, router } from '@inertiajs/react'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState, useMemo } from 'react'
|
||||||
import AppLayout from '~/layouts/AppLayout'
|
import AppLayout from '~/layouts/AppLayout'
|
||||||
import StyledButton from '~/components/StyledButton'
|
import StyledButton from '~/components/StyledButton'
|
||||||
import api from '~/lib/api'
|
import api from '~/lib/api'
|
||||||
import { ServiceSlim } from '../../../types/services'
|
import { ServiceSlim } from '../../../types/services'
|
||||||
import CuratedCollectionCard from '~/components/CuratedCollectionCard'
|
import CuratedCollectionCard from '~/components/CuratedCollectionCard'
|
||||||
|
import CategoryCard from '~/components/CategoryCard'
|
||||||
|
import TierSelectionModal from '~/components/TierSelectionModal'
|
||||||
import LoadingSpinner from '~/components/LoadingSpinner'
|
import LoadingSpinner from '~/components/LoadingSpinner'
|
||||||
import Alert from '~/components/Alert'
|
import Alert from '~/components/Alert'
|
||||||
import { IconCheck, IconChevronDown, IconChevronUp } from '@tabler/icons-react'
|
import { IconCheck, IconChevronDown, IconChevronUp } from '@tabler/icons-react'
|
||||||
|
import StorageProjectionBar from '~/components/StorageProjectionBar'
|
||||||
import { useNotifications } from '~/context/NotificationContext'
|
import { useNotifications } from '~/context/NotificationContext'
|
||||||
import useInternetStatus from '~/hooks/useInternetStatus'
|
import useInternetStatus from '~/hooks/useInternetStatus'
|
||||||
|
import { useSystemInfo } from '~/hooks/useSystemInfo'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import { CuratedCategory, CategoryTier, CategoryResource } from '../../../types/downloads'
|
||||||
|
|
||||||
// Capability definitions - maps user-friendly categories to services
|
// Capability definitions - maps user-friendly categories to services
|
||||||
interface Capability {
|
interface Capability {
|
||||||
|
|
@ -102,6 +107,21 @@ type WizardStep = 1 | 2 | 3 | 4
|
||||||
|
|
||||||
const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections'
|
const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections'
|
||||||
const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections'
|
const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections'
|
||||||
|
const CURATED_CATEGORIES_KEY = 'curated-categories'
|
||||||
|
const CATEGORIES_URL =
|
||||||
|
'https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/feature/tiered-collections/collections/kiwix-categories.json'
|
||||||
|
|
||||||
|
// Helper to get all resources for a tier (including inherited resources)
|
||||||
|
const getAllResourcesForTier = (tier: CategoryTier, allTiers: CategoryTier[]): CategoryResource[] => {
|
||||||
|
const resources = [...tier.resources]
|
||||||
|
if (tier.includesTier) {
|
||||||
|
const includedTier = allTiers.find((t) => t.slug === tier.includesTier)
|
||||||
|
if (includedTier) {
|
||||||
|
resources.unshift(...getAllResourcesForTier(includedTier, allTiers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
export default function EasySetupWizard(props: { system: { services: ServiceSlim[] } }) {
|
export default function EasySetupWizard(props: { system: { services: ServiceSlim[] } }) {
|
||||||
const [currentStep, setCurrentStep] = useState<WizardStep>(1)
|
const [currentStep, setCurrentStep] = useState<WizardStep>(1)
|
||||||
|
|
@ -111,14 +131,21 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [showAdditionalTools, setShowAdditionalTools] = useState(false)
|
const [showAdditionalTools, setShowAdditionalTools] = useState(false)
|
||||||
|
|
||||||
|
// Category/tier selection state
|
||||||
|
const [selectedTiers, setSelectedTiers] = useState<Map<string, CategoryTier>>(new Map())
|
||||||
|
const [tierModalOpen, setTierModalOpen] = useState(false)
|
||||||
|
const [activeCategory, setActiveCategory] = useState<CuratedCategory | null>(null)
|
||||||
|
|
||||||
const { addNotification } = useNotifications()
|
const { addNotification } = useNotifications()
|
||||||
const { isOnline } = useInternetStatus()
|
const { isOnline } = useInternetStatus()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const { data: systemInfo } = useSystemInfo({ enabled: true })
|
||||||
|
|
||||||
const anySelectionMade =
|
const anySelectionMade =
|
||||||
selectedServices.length > 0 ||
|
selectedServices.length > 0 ||
|
||||||
selectedMapCollections.length > 0 ||
|
selectedMapCollections.length > 0 ||
|
||||||
selectedZimCollections.length > 0
|
selectedZimCollections.length > 0 ||
|
||||||
|
selectedTiers.size > 0
|
||||||
|
|
||||||
const { data: mapCollections, isLoading: isLoadingMaps } = useQuery({
|
const { data: mapCollections, isLoading: isLoadingMaps } = useQuery({
|
||||||
queryKey: [CURATED_MAP_COLLECTIONS_KEY],
|
queryKey: [CURATED_MAP_COLLECTIONS_KEY],
|
||||||
|
|
@ -136,6 +163,20 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
const allServices = props.system.services
|
const allServices = props.system.services
|
||||||
|
|
||||||
// Services that can still be installed (not already installed)
|
// Services that can still be installed (not already installed)
|
||||||
|
// Fetch curated categories with tiers
|
||||||
|
const { data: categories, isLoading: isLoadingCategories } = useQuery({
|
||||||
|
queryKey: [CURATED_CATEGORIES_KEY],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(CATEGORIES_URL)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch categories')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
return data.categories as CuratedCategory[]
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
})
|
||||||
|
|
||||||
const availableServices = props.system.services.filter(
|
const availableServices = props.system.services.filter(
|
||||||
(service) => !service.installed && service.installation_status !== 'installing'
|
(service) => !service.installed && service.installation_status !== 'installing'
|
||||||
)
|
)
|
||||||
|
|
@ -163,6 +204,85 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Category/tier handlers
|
||||||
|
const handleCategoryClick = (category: CuratedCategory) => {
|
||||||
|
if (!isOnline) return
|
||||||
|
setActiveCategory(category)
|
||||||
|
setTierModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTierSelect = (category: CuratedCategory, tier: CategoryTier) => {
|
||||||
|
setSelectedTiers((prev) => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
// If same tier is selected, deselect it
|
||||||
|
if (prev.get(category.slug)?.slug === tier.slug) {
|
||||||
|
newMap.delete(category.slug)
|
||||||
|
} else {
|
||||||
|
newMap.set(category.slug, tier)
|
||||||
|
}
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeTierModal = () => {
|
||||||
|
setTierModalOpen(false)
|
||||||
|
setActiveCategory(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all resources from selected tiers for downloading
|
||||||
|
const getSelectedTierResources = (): CategoryResource[] => {
|
||||||
|
if (!categories) return []
|
||||||
|
const resources: CategoryResource[] = []
|
||||||
|
selectedTiers.forEach((tier, categorySlug) => {
|
||||||
|
const category = categories.find((c) => c.slug === categorySlug)
|
||||||
|
if (category) {
|
||||||
|
resources.push(...getAllResourcesForTier(tier, category.tiers))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total projected storage from all selections
|
||||||
|
const projectedStorageBytes = useMemo(() => {
|
||||||
|
let totalBytes = 0
|
||||||
|
|
||||||
|
// Add tier resources
|
||||||
|
const tierResources = getSelectedTierResources()
|
||||||
|
totalBytes += tierResources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0)
|
||||||
|
|
||||||
|
// Add map collections
|
||||||
|
if (mapCollections) {
|
||||||
|
selectedMapCollections.forEach((slug) => {
|
||||||
|
const collection = mapCollections.find((c) => c.slug === slug)
|
||||||
|
if (collection) {
|
||||||
|
totalBytes += collection.resources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ZIM collections
|
||||||
|
if (zimCollections) {
|
||||||
|
selectedZimCollections.forEach((slug) => {
|
||||||
|
const collection = zimCollections.find((c) => c.slug === slug)
|
||||||
|
if (collection) {
|
||||||
|
totalBytes += collection.resources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalBytes
|
||||||
|
}, [selectedTiers, selectedMapCollections, selectedZimCollections, categories, mapCollections, zimCollections])
|
||||||
|
|
||||||
|
// Get primary disk/filesystem info for storage projection
|
||||||
|
// Try disk array first (Linux/production), fall back to fsSize (Windows/dev)
|
||||||
|
const primaryDisk = systemInfo?.disk?.[0]
|
||||||
|
const primaryFs = systemInfo?.fsSize?.[0]
|
||||||
|
const storageInfo = primaryDisk
|
||||||
|
? { totalSize: primaryDisk.totalSize, totalUsed: primaryDisk.totalUsed }
|
||||||
|
: primaryFs
|
||||||
|
? { totalSize: primaryFs.size, totalUsed: primaryFs.used }
|
||||||
|
: null
|
||||||
|
|
||||||
const canProceedToNextStep = () => {
|
const canProceedToNextStep = () => {
|
||||||
if (!isOnline) return false // Must be online to proceed
|
if (!isOnline) return false // Must be online to proceed
|
||||||
if (currentStep === 1) return true // Can skip app installation
|
if (currentStep === 1) return true // Can skip app installation
|
||||||
|
|
@ -200,9 +320,12 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
|
|
||||||
await Promise.all(installPromises)
|
await Promise.all(installPromises)
|
||||||
|
|
||||||
|
// Download collections and individual tier resources
|
||||||
|
const tierResources = getSelectedTierResources()
|
||||||
const downloadPromises = [
|
const downloadPromises = [
|
||||||
...selectedMapCollections.map((slug) => api.downloadMapCollection(slug)),
|
...selectedMapCollections.map((slug) => api.downloadMapCollection(slug)),
|
||||||
...selectedZimCollections.map((slug) => api.downloadZimCollection(slug)),
|
...selectedZimCollections.map((slug) => api.downloadZimCollection(slug)),
|
||||||
|
...tierResources.map((resource) => api.downloadRemoteZimFile(resource.url)),
|
||||||
]
|
]
|
||||||
|
|
||||||
await Promise.all(downloadPromises)
|
await Promise.all(downloadPromises)
|
||||||
|
|
@ -263,7 +386,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
const steps = [
|
const steps = [
|
||||||
{ number: 1, label: 'Apps' },
|
{ number: 1, label: 'Apps' },
|
||||||
{ number: 2, label: 'Maps' },
|
{ number: 2, label: 'Maps' },
|
||||||
{ number: 3, label: 'ZIM Files' },
|
{ number: 3, label: 'Content' },
|
||||||
{ number: 4, label: 'Review' },
|
{ number: 4, label: 'Review' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -577,44 +700,79 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
const renderStep3 = () => (
|
const renderStep3 = () => (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">Choose ZIM Files</h2>
|
<h2 className="text-3xl font-bold text-gray-900 mb-2">Choose Content Collections</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Select ZIM file collections for offline knowledge. You can always download more later.
|
Select content categories for offline knowledge. Click a category to choose your preferred tier based on storage capacity.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{isLoadingZims ? (
|
|
||||||
|
{/* Curated Categories with Tiers */}
|
||||||
|
{isLoadingCategories ? (
|
||||||
<div className="flex justify-center py-12">
|
<div className="flex justify-center py-12">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
) : zimCollections && zimCollections.length > 0 ? (
|
) : categories && categories.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<>
|
||||||
{zimCollections.map((collection) => (
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<div
|
{categories.map((category) => (
|
||||||
key={collection.slug}
|
<CategoryCard
|
||||||
onClick={() =>
|
key={category.slug}
|
||||||
isOnline && !collection.all_downloaded && toggleZimCollection(collection.slug)
|
category={category}
|
||||||
}
|
selectedTier={selectedTiers.get(category.slug) || null}
|
||||||
className={classNames(
|
onClick={handleCategoryClick}
|
||||||
'relative',
|
/>
|
||||||
selectedZimCollections.includes(collection.slug) &&
|
))}
|
||||||
'ring-4 ring-desert-green rounded-lg',
|
</div>
|
||||||
collection.all_downloaded && 'opacity-75',
|
|
||||||
!isOnline && 'opacity-50 cursor-not-allowed'
|
{/* Tier Selection Modal */}
|
||||||
)}
|
<TierSelectionModal
|
||||||
>
|
isOpen={tierModalOpen}
|
||||||
<CuratedCollectionCard collection={collection} size="large" />
|
onClose={closeTierModal}
|
||||||
{selectedZimCollections.includes(collection.slug) && (
|
category={activeCategory}
|
||||||
<div className="absolute top-2 right-2 bg-desert-green rounded-full p-1">
|
selectedTierSlug={activeCategory ? selectedTiers.get(activeCategory.slug)?.slug : null}
|
||||||
<IconCheck size={32} className="text-white" />
|
onSelectTier={handleTierSelect}
|
||||||
</div>
|
/>
|
||||||
)}
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Legacy flat collections - show if available and no categories */}
|
||||||
|
{(!categories || categories.length === 0) && (
|
||||||
|
<>
|
||||||
|
{isLoadingZims ? (
|
||||||
|
<div className="flex justify-center py-12">
|
||||||
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
))}
|
) : zimCollections && zimCollections.length > 0 ? (
|
||||||
</div>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
) : (
|
{zimCollections.map((collection) => (
|
||||||
<div className="text-center py-12">
|
<div
|
||||||
<p className="text-gray-600 text-lg">No ZIM collections available at this time.</p>
|
key={collection.slug}
|
||||||
</div>
|
onClick={() =>
|
||||||
|
isOnline && !collection.all_downloaded && toggleZimCollection(collection.slug)
|
||||||
|
}
|
||||||
|
className={classNames(
|
||||||
|
'relative',
|
||||||
|
selectedZimCollections.includes(collection.slug) &&
|
||||||
|
'ring-4 ring-desert-green rounded-lg',
|
||||||
|
collection.all_downloaded && 'opacity-75',
|
||||||
|
!isOnline && 'opacity-50 cursor-not-allowed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CuratedCollectionCard collection={collection} size="large" />
|
||||||
|
{selectedZimCollections.includes(collection.slug) && (
|
||||||
|
<div className="absolute top-2 right-2 bg-desert-green rounded-full p-1">
|
||||||
|
<IconCheck size={32} className="text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-gray-600 text-lg">No content collections available at this time.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -623,7 +781,8 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
const hasSelections =
|
const hasSelections =
|
||||||
selectedServices.length > 0 ||
|
selectedServices.length > 0 ||
|
||||||
selectedMapCollections.length > 0 ||
|
selectedMapCollections.length > 0 ||
|
||||||
selectedZimCollections.length > 0
|
selectedZimCollections.length > 0 ||
|
||||||
|
selectedTiers.size > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -700,6 +859,39 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{selectedTiers.size > 0 && (
|
||||||
|
<div className="bg-white rounded-lg border-2 border-desert-stone-light p-6">
|
||||||
|
<h3 className="text-xl font-semibold text-gray-900 mb-4">
|
||||||
|
Content Categories ({selectedTiers.size})
|
||||||
|
</h3>
|
||||||
|
{Array.from(selectedTiers.entries()).map(([categorySlug, tier]) => {
|
||||||
|
const category = categories?.find((c) => c.slug === categorySlug)
|
||||||
|
if (!category) return null
|
||||||
|
const resources = getAllResourcesForTier(tier, category.tiers)
|
||||||
|
return (
|
||||||
|
<div key={categorySlug} className="mb-4 last:mb-0">
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<IconCheck size={20} className="text-desert-green mr-2" />
|
||||||
|
<span className="text-gray-900 font-medium">
|
||||||
|
{category.name} - {tier.name}
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-500 text-sm ml-2">
|
||||||
|
({resources.length} files)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul className="ml-7 space-y-1">
|
||||||
|
{resources.map((resource, idx) => (
|
||||||
|
<li key={idx} className="text-sm text-gray-600">
|
||||||
|
{resource.title}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
title="Ready to Start"
|
title="Ready to Start"
|
||||||
message="Click 'Complete Setup' to begin installing apps and downloading content. This may take some time depending on your internet connection and the size of the downloads."
|
message="Click 'Complete Setup' to begin installing apps and downloading content. This may take some time depending on your internet connection and the size of the downloads."
|
||||||
|
|
@ -727,6 +919,15 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
||||||
<div className="max-w-7xl mx-auto px-4 py-8">
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
<div className="bg-white rounded-md shadow-md">
|
<div className="bg-white rounded-md shadow-md">
|
||||||
{renderStepIndicator()}
|
{renderStepIndicator()}
|
||||||
|
{storageInfo && (
|
||||||
|
<div className="px-6 pt-4">
|
||||||
|
<StorageProjectionBar
|
||||||
|
totalSize={storageInfo.totalSize}
|
||||||
|
currentUsed={storageInfo.totalUsed}
|
||||||
|
projectedAddition={projectedStorageBytes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="p-6 min-h-fit">
|
<div className="p-6 min-h-fit">
|
||||||
{currentStep === 1 && renderStep1()}
|
{currentStep === 1 && renderStep1()}
|
||||||
{currentStep === 2 && renderStep2()}
|
{currentStep === 2 && renderStep2()}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const NotificationsProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeNotification = (id: string) => {
|
const removeNotification = (id: string) => {
|
||||||
setNotifications(notifications.filter((n) => n.id !== id))
|
setNotifications((prev) => prev.filter((n) => n.id !== id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeAllNotifications = () => {
|
const removeAllNotifications = () => {
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,33 @@ export type DownloadJobWithProgress = {
|
||||||
filepath: string
|
filepath: string
|
||||||
filetype: string
|
filetype: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tiered category types for curated collections UI
|
||||||
|
export type CategoryResource = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
size_mb: number
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CategoryTier = {
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
description: string
|
||||||
|
recommended?: boolean
|
||||||
|
includesTier?: string
|
||||||
|
resources: CategoryResource[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CuratedCategory = {
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
icon: string
|
||||||
|
description: string
|
||||||
|
language: string
|
||||||
|
tiers: CategoryTier[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CuratedCategoriesFile = {
|
||||||
|
categories: CuratedCategory[]
|
||||||
|
}
|
||||||
|
|
|
||||||
107
collections/CATEGORIES-TODO.md
Normal file
107
collections/CATEGORIES-TODO.md
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Kiwix Categories To-Do List
|
||||||
|
|
||||||
|
Potential categories to add to the tiered collections system in `kiwix-categories.json`.
|
||||||
|
|
||||||
|
## Current Categories (Completed)
|
||||||
|
- [x] Medicine - Medical references, first aid, emergency care
|
||||||
|
- [x] Survival & Preparedness - Food prep, prepper videos, repair guides
|
||||||
|
- [x] Education & Reference - Wikipedia, textbooks, TED talks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High Priority
|
||||||
|
|
||||||
|
### Technology & Programming
|
||||||
|
Stack Overflow, developer documentation, coding tutorials
|
||||||
|
- Stack Overflow (multiple tags available)
|
||||||
|
- DevDocs documentation
|
||||||
|
- freeCodeCamp
|
||||||
|
- Programming language references
|
||||||
|
|
||||||
|
### Children & Family
|
||||||
|
Age-appropriate educational content for kids
|
||||||
|
- Wikipedia for Schools
|
||||||
|
- Wikibooks Children's Bookshelf
|
||||||
|
- Khan Academy Kids (via Kolibri - separate system)
|
||||||
|
- Storybooks, fairy tales
|
||||||
|
|
||||||
|
### Trades & Vocational
|
||||||
|
Practical skills for building, fixing, and maintaining
|
||||||
|
- Electrical wiring guides
|
||||||
|
- Plumbing basics
|
||||||
|
- Automotive repair
|
||||||
|
- Woodworking
|
||||||
|
- Welding fundamentals
|
||||||
|
|
||||||
|
### Agriculture & Gardening
|
||||||
|
Food production and farming (expand beyond what's in Survival)
|
||||||
|
- Practical Plants database
|
||||||
|
- Permaculture guides
|
||||||
|
- Seed saving
|
||||||
|
- Animal husbandry
|
||||||
|
- Composting and soil management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Medium Priority
|
||||||
|
|
||||||
|
### Languages & Reference
|
||||||
|
Dictionaries, language learning, translation
|
||||||
|
- Wiktionary (multiple languages)
|
||||||
|
- Language learning resources
|
||||||
|
- Translation dictionaries
|
||||||
|
- Grammar guides
|
||||||
|
|
||||||
|
### History & Culture
|
||||||
|
Historical knowledge and cultural encyclopedias
|
||||||
|
- Wikipedia History portal content
|
||||||
|
- Historical documents
|
||||||
|
- Cultural archives
|
||||||
|
- Biographies
|
||||||
|
|
||||||
|
### Legal & Civic
|
||||||
|
Laws, rights, and civic procedures
|
||||||
|
- Legal references
|
||||||
|
- Constitutional documents
|
||||||
|
- Civic procedures
|
||||||
|
- Rights and responsibilities
|
||||||
|
|
||||||
|
### Communications
|
||||||
|
Emergency and amateur radio, networking
|
||||||
|
- Ham radio guides
|
||||||
|
- Emergency communication protocols
|
||||||
|
- Basic networking/IT
|
||||||
|
- Signal procedures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nice To Have
|
||||||
|
|
||||||
|
### Entertainment
|
||||||
|
Recreational reading and activities
|
||||||
|
- Project Gutenberg (fiction categories)
|
||||||
|
- Chess tutorials
|
||||||
|
- Puzzles and games
|
||||||
|
- Music theory
|
||||||
|
|
||||||
|
### Religion & Philosophy
|
||||||
|
Spiritual and philosophical texts
|
||||||
|
- Religious texts (various traditions)
|
||||||
|
- Philosophy references
|
||||||
|
- Ethics guides
|
||||||
|
|
||||||
|
### Regional/Non-English Bundles
|
||||||
|
Content in other languages
|
||||||
|
- Spanish language bundle
|
||||||
|
- French language bundle
|
||||||
|
- Other major languages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Each category should have 3 tiers: Essential, Standard, Comprehensive
|
||||||
|
- Higher tiers include all content from lower tiers via `includesTier`
|
||||||
|
- Check Kiwix catalog for available ZIM files: https://download.kiwix.org/zim/
|
||||||
|
- Consider storage constraints - Essential tiers should be <500MB ideally
|
||||||
|
- Mark one tier as `recommended: true` (usually Essential)
|
||||||
325
collections/kiwix-categories.json
Normal file
325
collections/kiwix-categories.json
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "Medicine",
|
||||||
|
"slug": "medicine",
|
||||||
|
"icon": "IconStethoscope",
|
||||||
|
"description": "Medical references, guides, and encyclopedias for healthcare information and emergency preparedness.",
|
||||||
|
"language": "en",
|
||||||
|
"tiers": [
|
||||||
|
{
|
||||||
|
"name": "Essential",
|
||||||
|
"slug": "medicine-essential",
|
||||||
|
"description": "Core medical references for first aid, medications, and emergency care. Start here.",
|
||||||
|
"recommended": true,
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Medical Library",
|
||||||
|
"description": "Field and emergency medicine books and guides",
|
||||||
|
"url": "https://download.kiwix.org/zim/other/zimgit-medicine_en_2024-08.zim",
|
||||||
|
"size_mb": 67
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "NHS Medicines A to Z",
|
||||||
|
"description": "How medicines work, dosages, side effects, and interactions",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/nhs.uk_en_medicines_2025-12.zim",
|
||||||
|
"size_mb": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Military Medicine",
|
||||||
|
"description": "Tactical and field medicine manuals",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/fas-military-medicine_en_2025-06.zim",
|
||||||
|
"size_mb": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CDC Health Information",
|
||||||
|
"description": "Disease prevention, travel health, and outbreak information",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/wwwnc.cdc.gov_en_all_2024-11.zim",
|
||||||
|
"size_mb": 170
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Standard",
|
||||||
|
"slug": "medicine-standard",
|
||||||
|
"description": "Comprehensive medical encyclopedia with detailed health information. Includes everything in Essential.",
|
||||||
|
"includesTier": "medicine-essential",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "MedlinePlus",
|
||||||
|
"description": "NIH's consumer health encyclopedia - diseases, conditions, drugs, supplements",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/medlineplus.gov_en_all_2025-01.zim",
|
||||||
|
"size_mb": 1800
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comprehensive",
|
||||||
|
"slug": "medicine-comprehensive",
|
||||||
|
"description": "Professional-level medical references and textbooks. Includes everything in Standard.",
|
||||||
|
"includesTier": "medicine-standard",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Wikipedia Medicine",
|
||||||
|
"description": "Curated medical articles from Wikipedia with images",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_medicine_maxi_2026-01.zim",
|
||||||
|
"size_mb": 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Medicine",
|
||||||
|
"description": "Open-source medical textbooks and educational content",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_med_2025-01.zim",
|
||||||
|
"size_mb": 1100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibrePathology",
|
||||||
|
"description": "Pathology reference for disease identification",
|
||||||
|
"url": "https://download.kiwix.org/zim/other/librepathology_en_all_maxi_2025-09.zim",
|
||||||
|
"size_mb": 76
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Survival & Preparedness",
|
||||||
|
"slug": "survival",
|
||||||
|
"icon": "IconShieldCheck",
|
||||||
|
"description": "Emergency preparedness, self-sufficiency, food storage, and practical survival skills.",
|
||||||
|
"language": "en",
|
||||||
|
"tiers": [
|
||||||
|
{
|
||||||
|
"name": "Essential",
|
||||||
|
"slug": "survival-essential",
|
||||||
|
"description": "Core food preparation and cooking guides. Lightweight text-based resources to get started.",
|
||||||
|
"recommended": true,
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Food for Preppers",
|
||||||
|
"description": "Recipes and techniques for food preservation and preparation",
|
||||||
|
"url": "https://download.kiwix.org/zim/other/zimgit-food-preparation_en_2025-04.zim",
|
||||||
|
"size_mb": 98
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "FOSS Cooking",
|
||||||
|
"description": "Quick and easy cooking guides and recipes",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/foss.cooking_en_all_2025-11.zim",
|
||||||
|
"size_mb": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Based.Cooking",
|
||||||
|
"description": "Simple, practical recipes from the community",
|
||||||
|
"url": "https://download.kiwix.org/zim/zimit/based.cooking_en_all_2025-11.zim",
|
||||||
|
"size_mb": 16
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Standard",
|
||||||
|
"slug": "survival-standard",
|
||||||
|
"description": "Video guides for winter survival, bug-out gear, plus gardening and cooking Q&A. Includes Essential.",
|
||||||
|
"includesTier": "survival-essential",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Canadian Prepper: Winter Prepping",
|
||||||
|
"description": "Video guides for winter survival and cold weather emergencies",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_winterprepping_en_2025-11.zim",
|
||||||
|
"size_mb": 1340
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Canadian Prepper: Bug Out Roll",
|
||||||
|
"description": "Essential gear selection for your bug-out bag",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutroll_en_2025-08.zim",
|
||||||
|
"size_mb": 975
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Gardening Q&A",
|
||||||
|
"description": "Stack Exchange Q&A for growing your own food",
|
||||||
|
"url": "https://download.kiwix.org/zim/stack_exchange/gardening.stackexchange.com_en_all_2025-12.zim",
|
||||||
|
"size_mb": 923
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Cooking Q&A",
|
||||||
|
"description": "Stack Exchange Q&A for cooking techniques and food safety",
|
||||||
|
"url": "https://download.kiwix.org/zim/stack_exchange/cooking.stackexchange.com_en_all_2025-12.zim",
|
||||||
|
"size_mb": 236
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comprehensive",
|
||||||
|
"slug": "survival-comprehensive",
|
||||||
|
"description": "Full prepper video library plus repair guides and DIY skills. Includes Standard.",
|
||||||
|
"includesTier": "survival-standard",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Urban Prepper",
|
||||||
|
"description": "Comprehensive urban emergency preparedness video series",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/urban-prepper_en_all_2025-11.zim",
|
||||||
|
"size_mb": 2240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Canadian Prepper: Prepping Food",
|
||||||
|
"description": "Long-term food storage and survival meal preparation",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_preppingfood_en_2025-09.zim",
|
||||||
|
"size_mb": 2160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Canadian Prepper: Bug Out Concepts",
|
||||||
|
"description": "Strategies and planning for emergency evacuation",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutconcepts_en_2025-11.zim",
|
||||||
|
"size_mb": 2890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Learning Self-Reliance",
|
||||||
|
"description": "Prepping, survival skills, beekeeping, and homesteading",
|
||||||
|
"url": "https://download.kiwix.org/zim/videos/lrnselfreliance_en_all_2025-12.zim",
|
||||||
|
"size_mb": 3970
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "iFixit Repair Guides",
|
||||||
|
"description": "Step-by-step repair guides for electronics, appliances, and vehicles",
|
||||||
|
"url": "https://download.kiwix.org/zim/ifixit/ifixit_en_all_2025-12.zim",
|
||||||
|
"size_mb": 3570
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "DIY & Home Improvement Q&A",
|
||||||
|
"description": "Stack Exchange Q&A for home repairs and construction",
|
||||||
|
"url": "https://download.kiwix.org/zim/stack_exchange/diy.stackexchange.com_en_all_2025-12.zim",
|
||||||
|
"size_mb": 1900
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Education & Reference",
|
||||||
|
"slug": "education",
|
||||||
|
"icon": "IconSchool",
|
||||||
|
"description": "Encyclopedias, textbooks, tutorials, and educational videos for self-directed learning.",
|
||||||
|
"language": "en",
|
||||||
|
"tiers": [
|
||||||
|
{
|
||||||
|
"name": "Essential",
|
||||||
|
"slug": "education-essential",
|
||||||
|
"description": "Core reference materials - Wikipedia's best articles and open textbooks. Lightweight, text-focused.",
|
||||||
|
"recommended": true,
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Wikipedia Top 45,000 Articles",
|
||||||
|
"description": "The 45,000 best Wikipedia articles, optimized for size (no images)",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_top_nopic_2025-12.zim",
|
||||||
|
"size_mb": 1880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Wikibooks",
|
||||||
|
"description": "Open-content textbooks covering math, science, computing, and more",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_nopic_2025-10.zim",
|
||||||
|
"size_mb": 3100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Standard",
|
||||||
|
"slug": "education-standard",
|
||||||
|
"description": "Adds educational videos, university-level tutorials, and STEM textbooks. Includes Essential.",
|
||||||
|
"includesTier": "education-essential",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "TED-Ed",
|
||||||
|
"description": "Educational video lessons on science, history, literature, and more",
|
||||||
|
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-ed_2025-07.zim",
|
||||||
|
"size_mb": 5610
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Wikiversity",
|
||||||
|
"description": "Tutorials, courses, and learning materials for all levels",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikiversity/wikiversity_en_all_maxi_2025-11.zim",
|
||||||
|
"size_mb": 2370
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Mathematics",
|
||||||
|
"description": "Open-source math textbooks from algebra to calculus",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2025-01.zim",
|
||||||
|
"size_mb": 831
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Physics",
|
||||||
|
"description": "Physics courses and textbooks",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2025-01.zim",
|
||||||
|
"size_mb": 560
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Chemistry",
|
||||||
|
"description": "Chemistry courses and textbooks",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_chem_2025-01.zim",
|
||||||
|
"size_mb": 2180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Biology",
|
||||||
|
"description": "Biology courses and textbooks",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_bio_2025-01.zim",
|
||||||
|
"size_mb": 2240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Project Gutenberg: Education",
|
||||||
|
"description": "Classic educational texts and resources",
|
||||||
|
"url": "https://download.kiwix.org/zim/gutenberg/gutenberg_en_education_2025-12.zim",
|
||||||
|
"size_mb": 606
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comprehensive",
|
||||||
|
"slug": "education-comprehensive",
|
||||||
|
"description": "Complete educational library with full Wikipedia, enhanced textbooks, and TED talks. Includes Standard.",
|
||||||
|
"includesTier": "education-standard",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"title": "Wikipedia (Full, No Images)",
|
||||||
|
"description": "Complete English Wikipedia - over 6 million articles",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_all_mini_2025-12.zim",
|
||||||
|
"size_mb": 11400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Wikibooks (With Images)",
|
||||||
|
"description": "Open textbooks with full illustrations and diagrams",
|
||||||
|
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_maxi_2025-10.zim",
|
||||||
|
"size_mb": 5400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TED Conference",
|
||||||
|
"description": "Main TED conference talks on ideas worth spreading",
|
||||||
|
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-conference_2025-08.zim",
|
||||||
|
"size_mb": 16500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Humanities",
|
||||||
|
"description": "Literature, philosophy, history, and social sciences",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_human_2025-01.zim",
|
||||||
|
"size_mb": 3730
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Geosciences",
|
||||||
|
"description": "Earth science, geology, and environmental studies",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_geo_2025-01.zim",
|
||||||
|
"size_mb": 1190
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Engineering",
|
||||||
|
"description": "Engineering courses and technical references",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_eng_2025-01.zim",
|
||||||
|
"size_mb": 678
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LibreTexts Business",
|
||||||
|
"description": "Business, economics, and management textbooks",
|
||||||
|
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_biz_2025-01.zim",
|
||||||
|
"size_mb": 840
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user