diff --git a/neode-ui/src/App.vue b/neode-ui/src/App.vue index 97d47ab1..6e1bdd07 100644 --- a/neode-ui/src/App.vue +++ b/neode-ui/src/App.vue @@ -9,6 +9,9 @@ + + + @@ -59,11 +62,13 @@ import { useRouter, useRoute } from 'vue-router' import SplashScreen from './components/SplashScreen.vue' import PWAUpdatePrompt from './components/PWAUpdatePrompt.vue' import SpotlightSearch from './components/SpotlightSearch.vue' +import CLIPopup from './components/CLIPopup.vue' import AppLauncherOverlay from './components/AppLauncherOverlay.vue' import Screensaver from './components/Screensaver.vue' import HelpGuideModal from './components/HelpGuideModal.vue' import { useControllerNav } from '@/composables/useControllerNav' import { useSpotlightStore } from '@/stores/spotlight' +import { useCLIStore } from '@/stores/cli' import { useMessageToast } from '@/composables/useMessageToast' import { useAppStore } from '@/stores/app' import { useScreensaverStore } from '@/stores/screensaver' @@ -71,6 +76,7 @@ import { useScreensaverStore } from '@/stores/screensaver' const router = useRouter() const screensaverStore = useScreensaverStore() const spotlightStore = useSpotlightStore() +const cliStore = useCLIStore() const appStore = useAppStore() const messageToast = useMessageToast() const toastMessage = messageToast.toastMessage @@ -108,6 +114,12 @@ function onKeyDown(e: KeyboardEvent) { spotlightStore.toggle() return } + // Cmd+Shift+` / Ctrl+Shift+` or plain C - CLI popup + if ((mod && e.shiftKey && e.key === '`') || ((e.key === 'c' || e.key === 'C') && !isInput)) { + e.preventDefault() + cliStore.toggle() + return + } // 's' key activates screensaver when authenticated (skip if typing in input) if (e.key === 's' || e.key === 'S') { const target = e.target as HTMLElement diff --git a/neode-ui/src/components/AppSwitcher.vue b/neode-ui/src/components/AppSwitcher.vue new file mode 100644 index 00000000..9b2b8832 --- /dev/null +++ b/neode-ui/src/components/AppSwitcher.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/neode-ui/src/components/CLIPopup.vue b/neode-ui/src/components/CLIPopup.vue new file mode 100644 index 00000000..ae0ad953 --- /dev/null +++ b/neode-ui/src/components/CLIPopup.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/neode-ui/src/components/SpotlightSearch.vue b/neode-ui/src/components/SpotlightSearch.vue index 415fee3a..d3e04eba 100644 --- a/neode-ui/src/components/SpotlightSearch.vue +++ b/neode-ui/src/components/SpotlightSearch.vue @@ -114,10 +114,12 @@ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue' import { useRouter } from 'vue-router' import Fuse from 'fuse.js' import { useSpotlightStore } from '@/stores/spotlight' +import { useCLIStore } from '@/stores/cli' import { helpTree, flattenForSearch, type SearchableItem } from '@/data/helpTree' const router = useRouter() const spotlightStore = useSpotlightStore() +const cliStore = useCLIStore() const inputRef = ref(null) const panelRef = ref(null) @@ -208,7 +210,9 @@ function selectItem(item: SearchableItem) { type: item.type, }) spotlightStore.close() - if (item.path) { + if (item.path === '__cli__') { + cliStore.open() + } else if (item.path) { router.push(item.path) } else if (item.content) { spotlightStore.showHelpModal({ title: item.label, content: item.content, relatedPath: item.relatedPath }) @@ -224,7 +228,9 @@ function selectHelpItem(section: { id: string }, item: { id: string; label: stri type, }) spotlightStore.close() - if (item.path) { + if (item.path === '__cli__') { + cliStore.open() + } else if (item.path) { router.push(item.path) } else if (item.content) { spotlightStore.showHelpModal({ title: item.label, content: item.content, relatedPath: item.relatedPath }) @@ -233,6 +239,10 @@ function selectHelpItem(section: { id: string }, item: { id: string; label: stri function selectRecent(item: { id: string; label: string; path?: string; type: 'navigate' | 'learn' | 'action' }) { spotlightStore.close() + if (item.path === '__cli__') { + cliStore.open() + return + } if (item.path) { router.push(item.path) return diff --git a/neode-ui/src/data/helpTree.ts b/neode-ui/src/data/helpTree.ts index 33355ba5..c5ec0907 100644 --- a/neode-ui/src/data/helpTree.ts +++ b/neode-ui/src/data/helpTree.ts @@ -64,6 +64,7 @@ export const helpTree: HelpSection[] = [ id: 'actions', label: 'Actions', items: [ + { id: 'open-cli', label: 'Open CLI', path: '__cli__' }, { id: 'install-app', label: 'Install an App', path: '/dashboard/marketplace' }, { id: 'manage-apps', label: 'Manage My Apps', path: '/dashboard/apps' }, { id: 'network-settings', label: 'Network Settings', path: '/dashboard/server' }, diff --git a/neode-ui/src/stores/cli.ts b/neode-ui/src/stores/cli.ts new file mode 100644 index 00000000..dfcf5d8a --- /dev/null +++ b/neode-ui/src/stores/cli.ts @@ -0,0 +1,25 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useCLIStore = defineStore('cli', () => { + const isOpen = ref(false) + + function open() { + isOpen.value = true + } + + function close() { + isOpen.value = false + } + + function toggle() { + isOpen.value = !isOpen.value + } + + return { + isOpen, + open, + close, + toggle, + } +}) diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue index 46f7b093..a9b2fff7 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -130,6 +130,11 @@ class="flex-1 overflow-hidden relative pb-20 md:pb-0 glass-piece z-10" :class="{ 'glass-throw-main': showZoomIn }" > + +
+ +
+
@@ -304,6 +309,7 @@ import { RouterLink, RouterView, useRouter, useRoute } from 'vue-router' import { useAppStore } from '../stores/app' import { useLoginTransitionStore } from '../stores/loginTransition' import AnimatedLogo from '@/components/AnimatedLogo.vue' +import AppSwitcher from '@/components/AppSwitcher.vue' import ControllerIndicator from '@/components/ControllerIndicator.vue' import { playDashboardLoadOomph } from '@/composables/useLoginSounds'