diff --git a/README.md b/README.md index 2cb7799..045a912 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,77 @@ npm start http://127.0.0.1:48912 ``` +### Method 3: Android/Termux Installation + +1. Install Termux from [F-Droid](https://f-droid.org/packages/com.termux/) (Google Play version is outdated). + +2. Install the required packages: + +```bash +pkg update && pkg upgrade +pkg install nodejs-lts git binutils build-essential python libvips imagemagick +``` + +> **What these packages are for:** +> - `nodejs-lts` — Node.js runtime +> - `git` — clone the repository +> - `binutils`, `build-essential` — C++ compiler toolchain (for building native modules) +> - `python` — required by `node-gyp` (used by `better-sqlite3` and `sharp`) +> - `libvips` — image processing library (required by `sharp`) +> - `imagemagick` — additional image format support (required by `libvips`) + +3. Apply the gyp fix (prevents native module build errors): + +```bash +mkdir -p ~/.gyp && echo "{ 'variables': { 'android_ndk_path': '' } }" > ~/.gyp/include.gypi +``` + +4. Clone the repository: + +```bash +git clone https://github.com/dmitryplyaskin/SillyInnkeeper.git +cd SillyInnkeeper +``` + +5. Install server dependencies: + +```bash +cd server +npm install +``` + +6. Install client dependencies: + +```bash +cd ../client +npm install +``` + +7. Build the client: + +```bash +cd ../client +npm run build +``` + +8. Start the server: + +```bash +cd ../server +npm start +``` + +9. Open your browser and navigate to: + +``` +http://127.0.0.1:48912 +``` + +> ⚠️ **Note on Android limitations**: +> - The folder picker icon is hidden — type the full path manually (e.g., `/storage/emulated/0/Download/characters`). +> - The "Open in Explorer" button and context menu item in card details are also hidden. +> - These features are disabled because system GUI dialogs are not available from a server/browser context on Android/Termux. + ### Configuration (.env) You can configure **host/port** and SillyTavern integration via a root `.env` file. diff --git a/client/package.json b/client/package.json index 4c12fef..b3d6109 100644 --- a/client/package.json +++ b/client/package.json @@ -10,9 +10,9 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@mantine/core": "8.3.10", + "@mantine/core": "^8.3.10", "@mantine/form": "^8.3.10", - "@mantine/hooks": "8.3.10", + "@mantine/hooks": "^8.3.10", "@mantine/notifications": "^8.3.10", "@tabler/icons-react": "^3.36.0", "dompurify": "^3.3.1", diff --git a/client/src/entities/cards/ui/card-actions-menu.tsx b/client/src/entities/cards/ui/card-actions-menu.tsx index 0d862ff..51e4163 100644 --- a/client/src/entities/cards/ui/card-actions-menu.tsx +++ b/client/src/entities/cards/ui/card-actions-menu.tsx @@ -59,6 +59,7 @@ export function CardActionsMenu({ const exportPngUrl = `/api/cards/${encodeURIComponent(cardId)}/export.png?download=1`; const canUseFile = Boolean(filePath?.trim()); + const isAndroid = /android/i.test(navigator.userAgent); return ( @@ -122,19 +123,21 @@ export function CardActionsMenu({ - : - } - disabled={!canUseFile || isOpeningInExplorer} - onClick={(e) => { - e.stopPropagation(); - if (!filePath) return; - onOpenInExplorer({ filePath }); - }} - > - {t("cardDetails.openInExplorer")} - + {!isAndroid && ( + : + } + disabled={!canUseFile || isOpeningInExplorer} + onClick={(e) => { + e.stopPropagation(); + if (!filePath) return; + onOpenInExplorer({ filePath }); + }} + > + {t("cardDetails.openInExplorer")} + + )} } diff --git a/client/src/features/card-details/ui/components/CardDetailsActionsPanel.tsx b/client/src/features/card-details/ui/components/CardDetailsActionsPanel.tsx index cf4f3d4..86aa994 100644 --- a/client/src/features/card-details/ui/components/CardDetailsActionsPanel.tsx +++ b/client/src/features/card-details/ui/components/CardDetailsActionsPanel.tsx @@ -99,6 +99,8 @@ export function CardDetailsActionsPanel({ $settings, ]); + const isAndroid = /android/i.test(navigator.userAgent); + function canonicalizeForCompare(value: unknown): unknown { if (value === null || value === undefined) return null; if (Array.isArray(value)) return value.map(canonicalizeForCompare); @@ -281,6 +283,7 @@ export function CardDetailsActionsPanel({ const fp = (p ?? "").trim(); if (!fp) return; if (openingDuplicatePath) return; + if (isAndroid) return; setOpeningDuplicatePath(fp); try { await showFile(fp); @@ -419,20 +422,22 @@ export function CardDetailsActionsPanel({ {i18n.t("cardDetails.download")} - + {!isAndroid && ( + + )}