fix: resolve content clipping on mobile by moving tab padding to scroll container
Moves dynamic pt-20/pt-40 padding from perspective-container-wrapper (which shrank the content area) to the inner scroll container via computed style. Removes spacer divs in CloudFolder, AppDetails, MarketplaceAppDetails. Reduces excessive bottom padding in Marketplace. Hides Cloud/Network tabs in CloudFolder detail view. Teleports mobile back buttons to body to escape CSS transform containing block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
255b52eb6d
commit
11cee9dc70
@ -1,199 +1,75 @@
|
||||
# Archipelago Overnight Loop Plan
|
||||
# Fix Content Clipping + Hide Network Tabs in CloudFolder
|
||||
|
||||
## Context
|
||||
Content clips/cuts off before reaching the bottom across CloudFolder, Marketplace, AppDetails — both desktop and mobile. Fixed UI elements (toolbars, search) must stay sticky while scrollable content extends to the tab bar (mobile) or viewport bottom (desktop). Also: hide Cloud/Network switcher on mobile in CloudFolder detail view.
|
||||
|
||||
After getting Claude Max OAuth working on the live server, hardening the deploy script, and setting up automatic token refresh, the user wants a comprehensive overnight automation plan. This plan covers UI fixes, service wiring, feature additions, and strategic research. The plan is informed by a detailed StartOS/Start9 transcript comparison.
|
||||
## Root Cause
|
||||
`.perspective-container-wrapper` has `height: 100%` + `overflow: hidden` + dynamic `pt-20`/`pt-40`. With `box-sizing: border-box`, the padding shrinks the content area by 80-160px on mobile.
|
||||
|
||||
**Key constraint**: Deploy after each task with `./scripts/deploy-to-target.sh --live` and verify at `http://192.168.1.228`.
|
||||
```
|
||||
<main flex-1 overflow-hidden pb-20> ← reserves tab bar space (correct)
|
||||
.perspective-container-wrapper h-100% pt-20 ← padding SHRINKS content area (BUG)
|
||||
.perspective-container h-100% overflow-hidden
|
||||
.view-wrapper absolute inset-0
|
||||
content div overflow-y-auto h-full ← scroll viewport 80-160px too small
|
||||
```
|
||||
|
||||
---
|
||||
## Solution: Move padding from wrapper to scroll container
|
||||
|
||||
## Phase 1: Quick UI Fixes (Tasks 1-9) — ~2 hours
|
||||
### File 1: `neode-ui/src/views/Dashboard.vue` [DONE]
|
||||
|
||||
### Task 1: AIUI close button margin fix [DONE]
|
||||
- **Files**: `neode-ui/src/style.css` (`.chat-mode-pill` rule)
|
||||
- **Change**: Increase mobile insets from `0.75rem` to `1.25rem` for both `top` and `right`
|
||||
- **Verify**: Open `/dashboard/chat` on mobile viewport, pill not flush against edges
|
||||
**A. Add computed** (~line 440):
|
||||
```ts
|
||||
const mobileTabPaddingTop = computed(() => {
|
||||
if (typeof window === 'undefined' || window.innerWidth >= 768) return 0
|
||||
if (showAppsTabs.value && showNetworkTabs.value) return 160
|
||||
if (showAppsTabs.value || showNetworkTabs.value) return 80
|
||||
return 0
|
||||
})
|
||||
```
|
||||
|
||||
### Task 2: AIUI close button mobile UX [DONE]
|
||||
- **Files**: `neode-ui/src/views/Chat.vue`, `neode-ui/src/style.css`
|
||||
- **Change**: On mobile (`md:hidden`), move close pill to bottom-center using `useMobileBackButton` composable. Hide top-right pill on mobile (`hidden md:flex`). Add a second close button at bottom that's thumb-reachable.
|
||||
- **Verify**: Mobile: close button at bottom (easy reach). Desktop: pill at top-right unchanged.
|
||||
**B. Remove padding from wrapper** (line 258):
|
||||
Remove `'pt-40': showAppsTabs && showNetworkTabs, 'pt-20': showAppsTabs !== showNetworkTabs` from `:class`.
|
||||
|
||||
### Task 3: Mobile viewport scaling fix [DONE]
|
||||
- **Files**: `neode-ui/index.html`
|
||||
- **Change**: Update viewport meta to: `width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content`
|
||||
- **Verify**: On phone, keyboard pushes content up rather than scaling/shrinking the page
|
||||
**C. Apply offset to content div instead** (lines 269-277):
|
||||
```html
|
||||
<div v-else
|
||||
:class="['px-4 pt-4 md:pt-8 md:px-8 overflow-y-auto h-full',
|
||||
needsMobileBackButtonSpace
|
||||
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-8'
|
||||
: 'pb-4 md:pb-8'
|
||||
]"
|
||||
:style="mobileTabPaddingTop ? { paddingTop: (mobileTabPaddingTop + 16) + 'px' } : undefined"
|
||||
>
|
||||
```
|
||||
- When mobile tabs showing: `:style` overrides `pt-4` with dynamic value
|
||||
- When no tabs (or desktop): `:style` is undefined, Tailwind `pt-4 md:pt-8` applies
|
||||
- Bottom padding reduced from `pb-28 md:pb-24` to `pb-4 md:pb-8` (main's `pb-20` already handles tab bar)
|
||||
|
||||
### Task 4: PWA icons and installability fix [DONE]
|
||||
- **Files**: `neode-ui/index.html`, `neode-ui/vite.config.ts`, `neode-ui/public/manifest.json`
|
||||
- **Change**: Ensure `<link rel="manifest" href="/manifest.webmanifest">` is in index.html (Vite PWA generates this). Verify all icon files exist at referenced paths. Check HTTPS is working (PWA requires secure context). Remove conflicting static manifest.json if Vite PWA plugin generates its own.
|
||||
- **Verify**: On phone (HTTPS), check DevTools > Application for manifest. Test Add to Home Screen.
|
||||
**D. Hide Cloud/Network tabs in CloudFolder** (line 451):
|
||||
```ts
|
||||
if (route.name === 'cloud-folder') return false
|
||||
```
|
||||
|
||||
### Task 5: Remove mobile headings on Cloud/Network/Apps/Store [DONE]
|
||||
- **Files**: `neode-ui/src/views/Marketplace.vue`, `neode-ui/src/views/Home.vue`
|
||||
- **Change**: Marketplace.vue line 76: add `hidden md:flex` to header container. Home.vue: reduce `mb-8` to `mb-4 md:mb-8` on welcome header. Apps.vue, Cloud.vue, Server.vue already have `hidden md:block` — confirmed correct.
|
||||
- **Verify**: All views on mobile viewport — headings hidden, no wasted space
|
||||
### File 2: `neode-ui/src/views/CloudFolder.vue` [DONE]
|
||||
Delete spacer div at line 156.
|
||||
|
||||
### Task 6: Verify mobile back button positioning [DONE]
|
||||
- **Files**: `neode-ui/src/views/Dashboard.vue` (check for `data-mobile-tab-bar` attribute)
|
||||
- **Change**: Ensure tab bar element has `data-mobile-tab-bar` attribute so `useMobileBackButton` composable works. CloudFolder.vue already uses this correctly.
|
||||
- **Verify**: CloudFolder on mobile — "Back to Cloud" button floats 8px above tab bar
|
||||
### File 3: `neode-ui/src/views/AppDetails.vue` [DONE]
|
||||
Delete spacer div at line 377.
|
||||
|
||||
### Task 7: Cloud homepage real data [DONE]
|
||||
- **Files**: `neode-ui/src/views/Home.vue`, `neode-ui/src/api/filebrowser-client.ts`
|
||||
- **Change**: Add `getUsage()` method to filebrowser-client. In Home.vue, replace hardcoded "2.4 GB" and "5" folders with real data from FileBrowser API. Add `formatBytes()` helper. Show loading state while fetching.
|
||||
- **Verify**: Home Cloud card shows real storage numbers from FileBrowser
|
||||
### File 4: `neode-ui/src/views/MarketplaceAppDetails.vue` [DONE]
|
||||
Delete spacer div at line 324.
|
||||
|
||||
### Task 8: Cloud file drag-and-drop upload [DONE]
|
||||
- **Files**: `neode-ui/src/views/CloudFolder.vue`, `neode-ui/src/style.css`
|
||||
- **Change**: Add drag-and-drop overlay with `@dragover.prevent` and `@drop.prevent`. Show visual drop zone when dragging. Extract files from `dataTransfer` and call existing `handleUpload()`.
|
||||
- **Verify**: Drag a file over CloudFolder — drop zone overlay appears, dropping uploads file
|
||||
### File 5: `neode-ui/src/views/Marketplace.vue` [DONE]
|
||||
Change `pb-48` → `pb-4` on line 121.
|
||||
|
||||
### Task 9: Route persistence on refresh [DONE]
|
||||
- **Files**: `neode-ui/src/router/index.ts`
|
||||
- **Change**: In `router.beforeEach`, when `checkSessionWithTimeout` times out, check `store.isAuthenticated` from localStorage. If true, allow navigation and revalidate in background. Only redirect to `/login` if genuinely no stored token.
|
||||
- **Verify**: Navigate to `/dashboard/cloud`, hit browser refresh — stays on cloud page
|
||||
## Safety
|
||||
- 3D transitions preserved: `overflow: hidden` stays on wrapper + perspective-container
|
||||
- Chat view unaffected: has its own branch bypassing the content div
|
||||
- Desktop unaffected: `mobileTabPaddingTop` returns 0
|
||||
- Back button space preserved: `needsMobileBackButtonSpace` still adds extra bottom padding
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Service Wiring & Search (Tasks 10-16) — ~2.5 hours
|
||||
|
||||
### Task 10: CMD-K app search and launch [DONE]
|
||||
- **Files**: `neode-ui/src/components/SpotlightSearch.vue`
|
||||
- **Change**: Import `useAppStore` and `useAppLauncherStore`. Create computed `dynamicAppItems` from `store.packages`. Merge with static help tree items in Fuse search. When app item selected, call `launchApp()`.
|
||||
- **Verify**: CMD+K, type app name, appears in results, click to launch
|
||||
|
||||
### Task 11: Setup/Dashboard tabs on Home [DONE]
|
||||
- **Files**: `neode-ui/src/views/Home.vue`, `neode-ui/src/style.css`
|
||||
- **Change**: Add tab bar with "Dashboard" and "Setup" tabs below welcome header. Dashboard tab: current overview cards. Setup tab: EasyHome goal cards (already imported). After completing all setup wizards, default to Dashboard. Use `.mode-switcher` pattern for tab styling.
|
||||
- **Verify**: Both tabs visible, Dashboard shows live data, Setup shows goal wizards
|
||||
|
||||
### Task 12: Wire up Home.vue Network card with real data [DONE]
|
||||
- **Files**: `neode-ui/src/views/Home.vue`, `neode-ui/src/views/Server.vue`
|
||||
- **Change**: Replace hardcoded "All Running", "Connected", "12" with computed values from `useAppStore`. Check `runningCount === appCount` for services status. Use `store.isConnected` for connectivity.
|
||||
- **Verify**: Network card reflects actual service states
|
||||
|
||||
### Task 13: Full app interface wiring audit [DONE]
|
||||
- **Files**: `core/archipelago/src/api/rpc/package.rs`, `core/archipelago/src/container/docker_packages.rs`, `image-recipe/configs/nginx-archipelago.conf`
|
||||
- **Change**: Compare `get_app_config()` port mappings with nginx proxies. Add missing nginx proxies for: Grafana (3000), Jellyfin (8096), Uptime Kuma (3001), Portainer (9000), OnlyOffice (9980). Add to both HTTP and HTTPS blocks. Verify `extract_lan_address()` correctness.
|
||||
- **Verify**: Each app launches correctly from Apps page
|
||||
|
||||
### Task 14: Local search within Apps view [DONE]
|
||||
- **Files**: `neode-ui/src/views/Apps.vue`
|
||||
- **Change**: Add search input with `ref('')`. Filter `sortedPackageEntries` by query against `manifest.title` and `manifest.description.short`. Style like Marketplace search.
|
||||
- **Verify**: Type in search — only matching apps shown
|
||||
|
||||
### Task 15: AIUI context broker integration test [DONE]
|
||||
- **Files**: `neode-ui/src/services/contextBroker.ts`
|
||||
- **Change**: Verify each category (apps, system, network, bitcoin, wallet, media, files, search, ai-local, notes) returns real data. Wire any that send placeholder/empty data to real store data.
|
||||
- **Verify**: Chat mode, ask AI about installed apps, gets real context
|
||||
|
||||
### Task 16: Deploy script improvements [DONE]
|
||||
- **Files**: `scripts/deploy-to-target.sh`
|
||||
- **Change**: Add SSH connectivity pre-flight check. Add `--frontend-only` flag for CSS/Vue-only deploys (skip Rust build + container rebuilds). Add timing output per section.
|
||||
- **Verify**: `--frontend-only` works, `--live` still works fully
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Hardening & Features (Tasks 17-22) — ~2.5 hours
|
||||
|
||||
### Task 17: Web5 DID creation functionality [DONE]
|
||||
- **Files**: `neode-ui/src/views/Web5.vue`
|
||||
- **Change**: Add "Create DID" button calling backend DID RPC endpoint. Display DID once created. Show Nostr relay status. Store DID in localStorage until backend persistence ready.
|
||||
- **Verify**: Web5 page, Create DID, DID displayed
|
||||
|
||||
### Task 18: Fedimint setup wizard [DONE]
|
||||
- **Files**: `neode-ui/src/data/goals.ts`
|
||||
- **Change**: Add `setup-fedimint` goal with steps: (1) Install Fedimint, (2) Access Guardian UI (port 8175), (3) Configure federation name, (4) Share invite code. Use "Create a Community" vernacular. Each step checks app state.
|
||||
- **Verify**: New Fedimint goal appears in goals, wizard steps work
|
||||
|
||||
### Task 19: Security hardening audit [DONE]
|
||||
- **Files**: `core/archipelago/src/api/rpc/package.rs`
|
||||
- **Change**: Add security flags to default container `run_args`: `--read-only` (with tmpfs for /tmp), `--cap-drop=ALL`, `--security-opt=no-new-privileges:true`. Create per-app capability mapping for apps that need specific caps.
|
||||
- **Verify**: Install an app, `podman inspect` shows security constraints
|
||||
|
||||
### Task 20: ISO build script sync [DONE]
|
||||
- **Files**: `image-recipe/configs/nginx-archipelago.conf`, `image-recipe/configs/archipelago.service`
|
||||
- **Change**: Ensure all recent nginx changes (new app proxies, AIUI proxies) are in the image-recipe config. Verify systemd service is current. Check that the auto-installer includes dummy content files for Cloud sections.
|
||||
- **Verify**: Configs match live server state
|
||||
|
||||
### Task 21: AIUI re-integration test loop [DONE]
|
||||
- **Files**: Multiple (deploy + test cycle)
|
||||
- **Change**: Deploy latest AIUI build. Test chat mode end-to-end. If broken, fix and redeploy. Verify: (1) AIUI loads in iframe, (2) Claude responds via proxy, (3) Context broker sends real data, (4) Close button works on mobile and desktop.
|
||||
- **Verify**: Full chat conversation works without errors
|
||||
|
||||
### Task 22: Security report [DONE]
|
||||
- **Change**: Audit all new features (cloud file upload, AIUI iframe, context broker, filebrowser proxy). Check for XSS in file names, path traversal in filebrowser-client, origin validation in context broker postMessage, CSRF in RPC endpoints. Produce report.
|
||||
- **Verify**: Written report with findings and mitigations
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Research & Strategic Planning (Tasks 23-25) — ~1.5 hours
|
||||
|
||||
### Task 23: Native browser wrapper research [DONE]
|
||||
- **Research**: Capacitor vs TWA for wrapping PWA in native shell. Capacitor supports iOS + Android, requires `@capacitor/core`. TWA is Android-only but simpler. Given PWA is well-configured, Capacitor would wrap with minimal changes. Document setup steps.
|
||||
|
||||
### Task 24: StartOS architecture comparison [DONE]
|
||||
Compare Archipelago vs StartOS on:
|
||||
- **Package format**: StartOS `.s9pk` vs Archy raw Docker/Podman
|
||||
- **Service discovery**: StartOS built-in discovery vs Archy manual port mapping
|
||||
- **Update mechanism**: StartOS signed updates vs Archy manual deploy
|
||||
- **Backup/restore**: StartOS full system backup vs Archy per-app data dirs
|
||||
- **Networking**: StartOS Tor + clearnet publishing ("a couple buttons") vs Archy Tor + nginx proxy
|
||||
- **AI integration**: StartOS planning agent-driven management with MCP server on CLI + live WebSocket state in side drawer — Archy has AIUI iframe with context broker
|
||||
- **Router**: StartOS building StartWRT (OpenWrt-based) for sovereign networking — Archy could integrate with MikroTik
|
||||
- **Key insight from transcript**: "We can match cloud computing UX except forgot password" — aligns with Archy's seed phrase backup flow
|
||||
- **Key insight**: Matt Hill on configurable nodes — node operators (and their representatives like Start9/Umbrel/Archy) should control defaults, not protocol devs. This validates Archy's approach of curated app defaults.
|
||||
- **Key insight**: "Express desire, have it fulfilled by superintelligence" — the AIUI agent integration is exactly this vision applied to sovereign computing
|
||||
|
||||
### Task 25: 2-Year Product Roadmap [DONE]
|
||||
|
||||
**Q2 2026 — Foundation**
|
||||
- Package format standardization (`.apkg` manifest + signed container images)
|
||||
- Health check system (per-app health endpoints, auto-restart, dashboard indicators)
|
||||
- Backup/restore system (full system + per-app, encrypted, downloadable)
|
||||
- `--frontend-only` deploy for faster iteration
|
||||
|
||||
**Q3 2026 — Networking & AI**
|
||||
- Router integration (MikroTik REST API management from UI)
|
||||
- Clearnet publishing (reverse proxy + Let's Encrypt from UI, "a couple buttons" like StartOS)
|
||||
- AI agent with MCP server on top of Archy CLI (inspired by StartOS approach)
|
||||
- Local inference with Ollama (already have app, need agent integration)
|
||||
|
||||
**Q4 2026 — Sovereignty**
|
||||
- App dependency graph with auto-start (Bitcoin -> LND -> BTCPay chain)
|
||||
- Tor v3 per-app onion services (already partially implemented)
|
||||
- Bitcoin domain names (Handshake/BNS research, DNS integration)
|
||||
- Fedimint communities (ecash payments between nodes for services)
|
||||
|
||||
**Q1 2027 — Mobile & Scale**
|
||||
- Capacitor native app shell (iOS + Android)
|
||||
- Remote management via Tor (manage server from anywhere)
|
||||
- App auto-updates with Cosign signature verification
|
||||
- ARM64 optimization (RPi 5, Orange Pi)
|
||||
|
||||
**Q2-Q3 2027 — Ecosystem**
|
||||
- Multi-user support (family/community server, per-user permissions)
|
||||
- Community app registry (third-party apps, review system)
|
||||
- Web5 DWN sync across nodes (decentralized web nodes)
|
||||
- P2P service marketplace (nodes pay each other with ecash for bandwidth, storage, compute)
|
||||
|
||||
**Q4 2027 — Maturity**
|
||||
- Enterprise hardening (SELinux, formal security audit)
|
||||
- Clustering (multi-server, distributed storage)
|
||||
- Sovereign AI assistant (local LLM with root access, MCP tools, privacy-first)
|
||||
- "Express desire, have it fulfilled" UX (natural language server management)
|
||||
|
||||
---
|
||||
|
||||
## Execution Notes
|
||||
|
||||
- **Total estimated time**: ~8 hours
|
||||
- **Deploy cadence**: After every task, `./scripts/deploy-to-target.sh --live`
|
||||
- **Failure handling**: If a task fails deploy/verify, log error and move to next. Retry failed tasks at end.
|
||||
- **Phase 4 is research-only**: No code changes, documentation only
|
||||
- **Priority order**: Phase 1 > Phase 2 > Phase 3 > Phase 4
|
||||
- **MikroTik note**: User has a MikroTik router — plan networking integration around RouterOS v7 REST API
|
||||
- **Bitcoin domain names**: Long-term research, start with Handshake feasibility study
|
||||
- **Nostr**: Wire up existing relay integration in Web5.vue, use for node discovery
|
||||
## Verification [DONE]
|
||||
1. `cd neode-ui && npm run build`
|
||||
2. `./scripts/deploy-to-target.sh --live`
|
||||
3. Test all views on mobile + desktop: CloudFolder, Marketplace, AppDetails, Chat, Home, page transitions
|
||||
|
||||
@ -180,7 +180,7 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* On mobile, leave room for close button + tab bar below AIUI */
|
||||
/* On mobile, pad iframe so AIUI content ends above the tab bar */
|
||||
@media (max-width: 767px) {
|
||||
.chat-iframe-mobile {
|
||||
padding-bottom: calc(var(--mobile-tab-bar-height, 72px) + 52px);
|
||||
|
||||
@ -8,16 +8,18 @@
|
||||
{{ backButtonText }}
|
||||
</button>
|
||||
|
||||
<!-- Mobile Full-Width Back Button -->
|
||||
<button
|
||||
@click="goBack"
|
||||
class="md:hidden mobile-back-btn glass-button px-6 py-3 rounded-lg font-medium shadow-2xl flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>{{ backButtonText }}</span>
|
||||
</button>
|
||||
<!-- Mobile Full-Width Back Button (teleported to escape CSS transform containing block) -->
|
||||
<Teleport to="body">
|
||||
<button
|
||||
@click="goBack"
|
||||
class="md:hidden mobile-back-btn glass-button px-6 py-3 rounded-lg font-medium shadow-2xl flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>{{ backButtonText }}</span>
|
||||
</button>
|
||||
</Teleport>
|
||||
|
||||
<div v-if="pkg">
|
||||
<!-- Compact Hero Section -->
|
||||
@ -371,9 +373,6 @@
|
||||
<p class="text-white/70">The requested application could not be found</p>
|
||||
</div>
|
||||
|
||||
<!-- Spacer for mobile back button -->
|
||||
<div class="md:hidden h-[calc(var(--mobile-tab-bar-height,_64px)+96px)]"></div>
|
||||
|
||||
<!-- Uninstall Confirmation Modal -->
|
||||
<Transition name="modal">
|
||||
<div
|
||||
|
||||
@ -54,21 +54,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile close bar (teleported to escape CSS transform containing block) -->
|
||||
<Teleport to="body">
|
||||
<div class="md:hidden mobile-back-btn flex items-center justify-center">
|
||||
<button
|
||||
class="w-full glass-button px-6 py-2.5 rounded-lg font-medium flex items-center justify-center gap-2 text-sm"
|
||||
style="background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);"
|
||||
@click="closeChat"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
Close Chat
|
||||
</button>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -9,16 +9,18 @@
|
||||
Back to Cloud
|
||||
</button>
|
||||
|
||||
<!-- Mobile Back Button -->
|
||||
<button
|
||||
@click="goBack"
|
||||
class="md:hidden mobile-back-btn glass-button px-6 py-3 rounded-lg font-medium shadow-2xl flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Back to Cloud</span>
|
||||
</button>
|
||||
<!-- Mobile Back Button (teleported to escape CSS transform containing block) -->
|
||||
<Teleport to="body">
|
||||
<button
|
||||
@click="goBack"
|
||||
class="md:hidden mobile-back-btn glass-button px-6 py-3 rounded-lg font-medium shadow-2xl flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Back to Cloud</span>
|
||||
</button>
|
||||
</Teleport>
|
||||
|
||||
<!-- Folder Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
@ -150,8 +152,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Spacer for mobile back button -->
|
||||
<div class="md:hidden h-[calc(var(--mobile-tab-bar-height,_64px)+96px)]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -255,7 +255,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="perspective-container-wrapper glass-piece" :class="{ 'pt-40': showAppsTabs && showNetworkTabs, 'pt-20': showAppsTabs !== showNetworkTabs, 'glass-throw-content': showZoomIn && !isHomeRoute }">
|
||||
<div class="perspective-container-wrapper glass-piece" :class="{ 'glass-throw-content': showZoomIn && !isHomeRoute }">
|
||||
<div class="perspective-container">
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<Transition :name="getTransitionName(route)">
|
||||
@ -269,11 +269,12 @@
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
'px-4 pt-4 pb-28 md:px-8 md:pt-8 md:pb-24 overflow-y-auto h-full',
|
||||
'px-4 pt-4 md:pt-8 md:px-8 overflow-y-auto h-full',
|
||||
needsMobileBackButtonSpace
|
||||
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-24'
|
||||
: undefined
|
||||
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-8'
|
||||
: 'pb-4 md:pb-8'
|
||||
]"
|
||||
:style="mobileTabPaddingTop ? { paddingTop: (mobileTabPaddingTop + 16) + 'px' } : undefined"
|
||||
>
|
||||
<component :is="Component" class="view-container" />
|
||||
</div>
|
||||
@ -451,9 +452,18 @@ const showAppsTabs = computed(() => {
|
||||
const showNetworkTabs = computed(() => {
|
||||
if (typeof window === 'undefined') return false
|
||||
if (window.innerWidth >= 768) return false
|
||||
if (route.name === 'cloud-folder') return false
|
||||
return route.path.includes('/server') || route.path.includes('/cloud')
|
||||
})
|
||||
|
||||
// Top padding for content div to clear fixed mobile tab overlays
|
||||
const mobileTabPaddingTop = computed(() => {
|
||||
if (typeof window === 'undefined' || window.innerWidth >= 768) return 0
|
||||
if (showAppsTabs.value && showNetworkTabs.value) return 160
|
||||
if (showAppsTabs.value || showNetworkTabs.value) return 80
|
||||
return 0
|
||||
})
|
||||
|
||||
function updateTabBarHeight() {
|
||||
if (typeof window === 'undefined') return
|
||||
if (mobileTabBar.value) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4 md:mb-8">
|
||||
<div class="mb-4 md:mb-8 flex items-start justify-between gap-4">
|
||||
<div class="min-h-[4.5rem]">
|
||||
<h1 class="text-3xl font-bold text-white mb-2 drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]">
|
||||
{{ line1Text }}<span v-if="showCaretLine1" class="typing-caret"></span>
|
||||
@ -9,11 +9,24 @@
|
||||
{{ line2Text }}<span v-if="showCaretLine2" class="typing-caret"></span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Desktop: tabs inline with header -->
|
||||
<div
|
||||
v-if="!uiMode.isChat"
|
||||
class="hidden md:flex mode-switcher flex-shrink-0 transition-opacity duration-500"
|
||||
:class="{ 'opacity-0 pointer-events-none': showWelcomeBlock && !animateCards }"
|
||||
>
|
||||
<button class="mode-switcher-btn" :class="{ 'mode-switcher-btn-active': homeTab === 'dashboard' }" @click="homeTab = 'dashboard'">Dashboard</button>
|
||||
<button class="mode-switcher-btn" :class="{ 'mode-switcher-btn-active': homeTab === 'setup' }" @click="homeTab = 'setup'">Setup</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab bar + content (all non-chat modes) -->
|
||||
<template v-if="!uiMode.isChat">
|
||||
<div class="mode-switcher mb-6 max-w-xs">
|
||||
<!-- Mobile: full-width tabs -->
|
||||
<div
|
||||
class="md:hidden mode-switcher mb-6 w-full transition-opacity duration-500"
|
||||
:class="{ 'opacity-0 pointer-events-none': showWelcomeBlock && !animateCards }"
|
||||
>
|
||||
<button class="mode-switcher-btn" :class="{ 'mode-switcher-btn-active': homeTab === 'dashboard' }" @click="homeTab = 'dashboard'">Dashboard</button>
|
||||
<button class="mode-switcher-btn" :class="{ 'mode-switcher-btn-active': homeTab === 'setup' }" @click="homeTab = 'setup'">Setup</button>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="marketplace-container flex flex-col h-full overflow-hidden -mt-4 md:mt-0">
|
||||
<div class="marketplace-container flex flex-col h-full overflow-hidden md:-mt-4">
|
||||
<!-- Fixed Header Section -->
|
||||
<div class="flex-shrink-0 -mt-4 md:mt-0">
|
||||
<div class="flex-shrink-0 md:-mt-4">
|
||||
<!-- Installation Progress Banner - Multiple Apps -->
|
||||
<div v-if="installingApps.size > 0" class="mb-6 space-y-3">
|
||||
<div
|
||||
@ -106,19 +106,19 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar (Mobile - placeholder for later) -->
|
||||
<div class="md:hidden mb-6">
|
||||
<!-- Search Bar (Mobile) -->
|
||||
<div class="md:hidden mb-4">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search apps..."
|
||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
|
||||
class="w-full px-4 py-3 md:py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Apps Section -->
|
||||
<div class="flex-1 overflow-y-auto pr-2 -mr-2 pb-48">
|
||||
<div class="flex-1 overflow-y-auto pr-2 -mr-2 pb-4">
|
||||
<!-- Apps Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div
|
||||
@ -203,19 +203,18 @@
|
||||
</div>
|
||||
<!-- End Scrollable Apps Section -->
|
||||
|
||||
<!-- Floating Filter Button (Mobile only) -->
|
||||
<button
|
||||
@click="showFilterModal = true"
|
||||
class="md:hidden fixed right-4 z-40 w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl"
|
||||
:style="{
|
||||
bottom: bottomPosition,
|
||||
filter: 'drop-shadow(0 10px 25px rgba(0, 0, 0, 0.5))'
|
||||
}"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Floating Filter Button (teleported to escape CSS transform containing block) -->
|
||||
<Teleport to="body">
|
||||
<button
|
||||
@click="showFilterModal = true"
|
||||
class="md:hidden fixed right-4 z-40 w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-back-btn"
|
||||
style="left: auto;"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</Teleport>
|
||||
|
||||
<!-- Filter Modal (Mobile only) -->
|
||||
<Transition name="modal">
|
||||
@ -304,13 +303,11 @@ import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||
import { useMobileBackButton } from '@/composables/useMobileBackButton'
|
||||
import { useModalKeyboard } from '@/composables/useModalKeyboard'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useAppStore()
|
||||
const { setCurrentApp } = useMarketplaceApp()
|
||||
const { bottomPosition } = useMobileBackButton()
|
||||
|
||||
// Category state
|
||||
const selectedCategory = ref('all')
|
||||
|
||||
@ -320,8 +320,6 @@
|
||||
<p class="text-white/70">The requested application could not be found in the marketplace</p>
|
||||
</div>
|
||||
|
||||
<!-- Spacer for mobile back button -->
|
||||
<div class="md:hidden h-[calc(var(--mobile-tab-bar-height,_64px)+96px)]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user