keyboard working with magic sidebar

This commit is contained in:
Andras Bacsai
2023-05-12 10:45:28 +02:00
parent 7b9d1284aa
commit 4a5ee9342e
3 changed files with 185 additions and 159 deletions

View File

@@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Spatie\Activitylog\Contracts\Activity; use Spatie\Activitylog\Contracts\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
if (!function_exists('generalErrorHandler')) { if (!function_exists('generalErrorHandler')) {
function generalErrorHandler(\Throwable $e, $that = null, $isJson = false) function generalErrorHandler(\Throwable $e, $that = null, $isJson = false)
@@ -185,12 +185,9 @@ if (!function_exists('getLatestVersionOfCoolify')) {
if (!function_exists('generateRandomName')) { if (!function_exists('generateRandomName')) {
function generateRandomName() function generateRandomName()
{ {
$generator = new \Nubs\RandomNameGenerator\All( $generator = \Nubs\RandomNameGenerator\All::create();
[ $cuid = new Cuid2(7);
new \Nubs\RandomNameGenerator\Alliteration() return Str::kebab("{$generator->getName()}-{$cuid}");
]
);
return Str::kebab($generator->getName());
} }
} }

View File

@@ -1,12 +1,16 @@
<div x-data="magicsearchbar"> <div x-data="magicsearchbar">
{{-- Main --}} {{-- Main --}}
<input x-ref="mainSearch" x-cloak x-show="!serverMenu && !destinationMenu && !projectMenu && !environmentMenu" <template x-cloak x-if="!serverMenu && !destinationMenu && !projectMenu && !environmentMenu">
x-model="mainSearch" class="w-96" x-on:click="checkMainMenu" x-on:click.outside="closeMainMenu" <div>
placeholder="🪄 Search for anything... magically..." /> <input x-ref="search" x-model="search" class="w-96" x-on:click="checkMainMenu" x-on:click.outside="closeMenus"
placeholder="🪄 Search for anything... magically..." x-on:keyup.down="focusNext(items.length)"
x-on:keyup.up="focusPrev(items.length)"
x-on:keyup.enter="await set('server',filteredItems()[focusedIndex].name)" />
<div x-cloak x-show="mainMenu" class="absolute text-sm top-11 w-[25rem] bg-neutral-800"> <div x-cloak x-show="mainMenu" class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="item in filteredItems" :key="item.name"> <template x-for="(item,index) in filteredItems" :key="item.name">
<div x-on:click="await next('server',item.name, item.disabled ?? false)" <div x-on:click="await set('server',item.name)" :class="focusedIndex === index && 'bg-neutral-700'"
:class="item.disabled && 'text-neutral-500 bg-neutral-900 hover:bg-neutral-900 cursor-not-allowed opacity-60'" :class="item.disabled &&
'text-neutral-500 bg-neutral-900 hover:bg-neutral-900 cursor-not-allowed opacity-60'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-green-600 rounded" x-show="item.type === 'Add'" <span class="px-2 mr-1 text-xs bg-green-600 rounded" x-show="item.type === 'Add'"
x-text="item.type"></span> x-text="item.type"></span>
@@ -16,12 +20,18 @@
</div> </div>
</template> </template>
</div> </div>
</div>
</template>
{{-- Servers --}} {{-- Servers --}}
<div x-cloak x-show="serverMenu" x-on:click.outside="closeServerMenu"> <template x-cloak x-if="serverMenu">
<input x-ref="serverSearch" x-model="serverSearch" class="w-96" placeholder="Select a server" /> <div x-on:click.outside="closeMenus">
<input x-ref="search" x-model="search" class="w-96" placeholder="Select a server..."
x-on:keyup.down="focusNext(servers.length)" x-on:keyup.up="focusPrev(servers.length)"
x-on:keyup.enter="await set('destination',filteredServers()[focusedIndex].uuid)" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800"> <div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="server in filteredServers" :key="server.name ?? server"> <template x-for="(server,index) in filteredServers" :key="server.name ?? server">
<div x-on:click="await next('destination',server.uuid)" <div x-on:click="await set('destination',server.uuid)"
:class="focusedIndex === index && 'bg-neutral-700'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-600 rounded">Server</span> <span class="px-2 mr-1 text-xs bg-purple-600 rounded">Server</span>
<span x-text="server.name"></span> <span x-text="server.name"></span>
@@ -29,13 +39,17 @@
</template> </template>
</div> </div>
</div> </div>
</template>
{{-- Destinations --}} {{-- Destinations --}}
<div x-cloak x-show="destinationMenu" x-on:click.outside="closeDestinationMenu"> <template x-cloak x-if="destinationMenu">
<input x-ref="destinationSearch" x-model="destinationSearch" class="w-96" <div x-on:click.outside="closeMenus">
placeholder="Select a destination" /> <input x-ref="search" x-model="search" class="w-96" placeholder="Select a destination..."
x-on:keyup.down="focusNext(destinations.length)" x-on:keyup.up="focusPrev(destinations.length)"
x-on:keyup.enter="await set('project',filteredDestinations()[focusedIndex].uuid)" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800"> <div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="destination in filteredDestinations" :key="destination.name ?? destination"> <template x-for="(destination,index) in filteredDestinations" :key="destination.name ?? destination">
<div x-on:click="await next('project',destination.uuid)" <div x-on:click="await set('project',destination.uuid)"
:class="focusedIndex === index && 'bg-neutral-700'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Destination</span> <span class="px-2 mr-1 text-xs bg-purple-700 rounded">Destination</span>
<span x-text="destination.name"></span> <span x-text="destination.name"></span>
@@ -43,16 +57,22 @@
</template> </template>
</div> </div>
</div> </div>
</template>
{{-- Projects --}} {{-- Projects --}}
<div x-cloak x-show="projectMenu" x-on:click.outside="closeProjectMenu"> <template x-cloak x-if="projectMenu">
<input x-ref="projectSearch" x-model="projectSearch" class="w-96" placeholder="Type your project name..." /> <div x-on:click.outside="closeMenus">
<input x-ref="search" x-model="search" class="w-96" placeholder="Type your project name..."
x-on:keyup.down="focusNext(projects.length + 1)" x-on:keyup.up="focusPrev(projects.length + 1)"
x-on:keyup.enter="await set('environment',filteredProjects()[focusedIndex - 1]?.uuid)" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800"> <div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<div x-on:click="await newProject" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> <div x-on:click="await newProject" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"
:class="focusedIndex === 0 && 'bg-neutral-700'">
<span>New Project</span> <span>New Project</span>
<span x-text="projectSearch"></span> <span x-text="search"></span>
</div> </div>
<template x-for="project in filteredProjects" :key="project.name ?? project"> <template x-for="(project,index) in filteredProjects" :key="project.name ?? project">
<div x-on:click="await next('environment',project.uuid)" <div x-on:click="await set('environment',project.uuid)"
:class="focusedIndex === index + 1 && 'bg-neutral-700'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Project</span> <span class="px-2 mr-1 text-xs bg-purple-700 rounded">Project</span>
<span x-text="project.name"></span> <span x-text="project.name"></span>
@@ -60,17 +80,22 @@
</template> </template>
</div> </div>
</div> </div>
</template>
{{-- Environments --}} {{-- Environments --}}
<div x-cloak x-show="environmentMenu" x-on:click.outside="closeEnvironmentMenu"> <template x-cloak x-if="environmentMenu">
<input x-ref="environmentSearch" x-model="environmentSearch" class="w-96" <div x-on:click.outside="closeMenus">
placeholder="Select a environment" /> <input x-ref="search" x-model="search" class="w-96" placeholder="Select a environment..."
x-on:keyup.down="focusNext(environments.length + 1)" x-on:keyup.up="focusPrev(environments.length + 1)"
x-on:keyup.enter="await set('jump',filteredEnvironments()[focusedIndex - 1]?.name)" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800"> <div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<div x-on:click="await newEnvironment" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> <div x-on:click="await newEnvironment" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"
:class="focusedIndex === 0 && 'bg-neutral-700'">
<span>New Environment</span> <span>New Environment</span>
<span x-text="environmentSearch"></span> <span x-text="search"></span>
</div> </div>
<template x-for="environment in filteredEnvironments" :key="environment.name ?? environment"> <template x-for="(environment,index) in filteredEnvironments" :key="environment.name ?? environment">
<div x-on:click="await next('jump',environment.name)" <div x-on:click="await set('jump',environment.name)"
:class="focusedIndex === index + 1 && 'bg-neutral-700'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700"> class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Env</span> <span class="px-2 mr-1 text-xs bg-purple-700 rounded">Env</span>
<span x-text="environment.name"></span> <span x-text="environment.name"></span>
@@ -78,34 +103,33 @@
</template> </template>
</div> </div>
</div> </div>
</template>
</div> </div>
<script> <script>
document.addEventListener('alpine:init', () => { document.addEventListener('alpine:init', () => {
Alpine.data('magicsearchbar', () => ({ Alpine.data('magicsearchbar', () => ({
focus() {
if (this.$refs.search) this.$refs.search.focus()
},
init() { init() {
this.$watch('mainMenu', (value) => { this.$watch('search', () => {
if (value) this.$refs.mainSearch.focus() this.focusedIndex = ""
}) })
this.$watch('serverMenu', (value) => { this.$watch('mainMenu', () => {
this.$nextTick(() => { this.focus()
this.$refs.serverSearch.focus()
}) })
this.$watch('serverMenu', () => {
this.focus()
}) })
this.$watch('destinationMenu', (value) => { this.$watch('destinationMenu', () => {
this.$nextTick(() => { this.focus()
this.$refs.destinationSearch.focus()
}) })
this.$watch('projectMenu', () => {
this.focus()
}) })
this.$watch('projectMenu', (value) => { this.$watch('environmentMenu', () => {
this.$nextTick(() => { this.focus()
this.$refs.projectSearch.focus()
})
})
this.$watch('environmentMenu', (value) => {
this.$nextTick(() => {
this.$refs.environmentSearch.focus()
})
}) })
}, },
mainMenu: false, mainMenu: false,
@@ -113,12 +137,7 @@
destinationMenu: false, destinationMenu: false,
projectMenu: false, projectMenu: false,
environmentMenu: false, environmentMenu: false,
search: '',
mainSearch: '',
serverSearch: '',
destinationSearch: '',
projectSearch: '',
environmentSearch: '',
selectedAction: '', selectedAction: '',
selectedServer: '', selectedServer: '',
@@ -130,6 +149,8 @@
destinations: ['Loading...'], destinations: ['Loading...'],
projects: ['Loading...'], projects: ['Loading...'],
environments: ['Loading...'], environments: ['Loading...'],
focusedIndex: "",
items: [{ items: [{
name: 'Public Repository', name: 'Public Repository',
type: 'Add', type: 'Add',
@@ -156,92 +177,101 @@
type: 'Jump', type: 'Jump',
} }
], ],
focusPrev(maxLength) {
if (this.focusedIndex === "") {
this.focusedIndex = maxLength - 1
} else {
if (this.focusedIndex > 0) {
this.focusedIndex = this.focusedIndex - 1
}
}
},
focusNext(maxLength) {
if (this.focusedIndex === "") {
this.focusedIndex = 0
} else {
if (maxLength > this.focusedIndex + 1) {
this.focusedIndex = this.focusedIndex + 1
}
}
},
closeMenus() {
this.focusedIndex = ''
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = false
this.environmentMenu = false
this.search = ''
},
checkMainMenu() { checkMainMenu() {
if (this.serverMenu) return if (this.serverMenu) return
this.mainMenu = true this.mainMenu = true
}, },
closeMainMenu() {
this.mainMenu = false
this.mainSearch = ''
},
closeServerMenu() {
this.serverMenu = false
this.serverSearch = ''
},
closeDestinationMenu() {
this.destinationMenu = false
this.destinationSearch = ''
},
closeProjectMenu() {
this.projectMenu = false
this.projectSearch = ''
},
closeEnvironmentMenu() {
this.environmentMenu = false
this.environmentSearch = ''
},
filteredItems() { filteredItems() {
if (this.mainSearch === '') return this.items if (this.search === '') return this.items
return this.items.filter(item => { return this.items.filter(item => {
return item.name.toLowerCase().includes(this.mainSearch.toLowerCase()) return item.name.toLowerCase().includes(this.search.toLowerCase())
}) })
}, },
filteredServers() { filteredServers() {
if (this.serverSearch === '') return this.servers if (this.search === '') return this.servers
return this.servers.filter(server => { return this.servers.filter(server => {
return server.name.toLowerCase().includes(this.serverSearch return server.name.toLowerCase().includes(this.search
.toLowerCase()) .toLowerCase())
}) })
}, },
filteredDestinations() { filteredDestinations() {
if (this.destinationSearch === '') return this.destinations if (this.search === '') return this.destinations
return this.destinations.filter(destination => { return this.destinations.filter(destination => {
return destination.name.toLowerCase().includes(this.destinationSearch return destination.name.toLowerCase().includes(this.search
.toLowerCase()) .toLowerCase())
}) })
}, },
filteredProjects() { filteredProjects() {
if (this.projectSearch === '') return this.projects if (this.search === '') return this.projects
return this.projects.filter(project => { return this.projects.filter(project => {
return project.name.toLowerCase().includes(this.projectSearch return project.name.toLowerCase().includes(this.search
.toLowerCase()) .toLowerCase())
}) })
}, },
filteredEnvironments() { filteredEnvironments() {
if (this.environmentSearch === '') return this.environments if (this.search === '') return this.environments
return this.environments.filter(environment => { return this.environments.filter(environment => {
return environment.name.toLowerCase().includes(this.environmentSearch return environment.name.toLowerCase().includes(this.search
.toLowerCase()) .toLowerCase())
}) })
}, },
async newProject() { async newProject() {
const response = await fetch('/magic?server=' + this.selectedServer + const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination + '&destination=' + this.selectedDestination +
'&project=new&name=' + this.projectSearch); '&project=new&name=' + this.search);
if (response.ok) { if (response.ok) {
const { const {
project_uuid project_uuid
} = await response.json(); } = await response.json();
this.next('environment', project_uuid) console.log(project_uuid);
this.next('jump', 'production') this.set('environment', project_uuid)
this.set('jump', 'production')
} }
}, },
async newEnvironment() { async newEnvironment() {
console.log('new environment')
const response = await fetch('/magic?server=' + this.selectedServer + const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination + '&destination=' + this.selectedDestination +
'&project=' + this.selectedProject + '&environment=new&name=' + this '&project=' + this.selectedProject + '&environment=new&name=' + this
.environmentSearch); .search);
if (response.ok) { if (response.ok) {
this.next('jump', this.environmentSearch) const {
environment_name
} = await response.json();
this.set('jump', environment_name)
} }
}, },
async next(action, id, isDisabled) { async set(action, id) {
if (isDisabled) return
let response = null let response = null
switch (action) { switch (action) {
case 'server': case 'server':
this.mainMenu = false
this.serverMenu = true
this.items.find((item, index) => { this.items.find((item, index) => {
if (item.name.toLowerCase() === id if (item.name.toLowerCase() === id
.toLowerCase()) { .toLowerCase()) {
@@ -255,16 +285,14 @@
} = await response.json(); } = await response.json();
this.servers = servers; this.servers = servers;
} }
this.closeMenus()
this.serverMenu = true
break break
case 'destination': case 'destination':
this.selectedServer = id
if (this.items[this.selectedAction].type === "Jump") { if (this.items[this.selectedAction].type === "Jump") {
return window.location = '/server/' + id return window.location = '/server/' + id
} }
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = true
this.selectedServer = id
response = await fetch('/magic?server=' + this response = await fetch('/magic?server=' + this
.selectedServer + .selectedServer +
'&destinations=true'); '&destinations=true');
@@ -274,12 +302,10 @@
} = await response.json(); } = await response.json();
this.destinations = destinations; this.destinations = destinations;
} }
this.closeMenus()
this.destinationMenu = true
break break
case 'project': case 'project':
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = true
this.selectedDestination = id this.selectedDestination = id
response = await fetch('/magic?server=' + this response = await fetch('/magic?server=' + this
.selectedServer + .selectedServer +
@@ -291,15 +317,16 @@
} = await response.json(); } = await response.json();
this.projects = projects; this.projects = projects;
} }
this.closeMenus()
this.projectMenu = true
break break
case 'environment': case 'environment':
this.mainMenu = false if (this.focusedIndex === 0) {
this.serverMenu = false this.focusedIndex = ''
this.destinationMenu = false return await this.newProject()
this.projectMenu = false }
this.environmentMenu = true
this.selectedProject = id
this.selectedProject = id
response = await fetch('/magic?server=' + this response = await fetch('/magic?server=' + this
.selectedServer + .selectedServer +
@@ -312,13 +339,15 @@
} = await response.json(); } = await response.json();
this.environments = environments; this.environments = environments;
} }
this.closeMenus()
this.environmentMenu = true
break break
case 'jump': case 'jump':
this.mainMenu = false if (this.focusedIndex === 0) {
this.serverMenu = false this.focusedIndex = ''
this.destinationMenu = false return await this.newEnvironment()
this.projectMenu = false }
this.environmentMenu = false
this.selectedEnvironment = id this.selectedEnvironment = id
if (this.selectedAction === 0) { if (this.selectedAction === 0) {
@@ -333,7 +362,7 @@
} else if (this.selectedAction === 3) { } else if (this.selectedAction === 3) {
console.log('new Database') console.log('new Database')
} }
this.closeMenus()
break break
} }
} }

View File

@@ -81,7 +81,7 @@ Route::middleware(['auth'])->group(function () {
]); ]);
} }
return response()->json([ return response()->json([
'environment' => $environment->name 'environment_name' => $environment->name
]); ]);
} }
return response()->json([ return response()->json([